Android 综合技术

本篇主要介绍 Android 应用异常捕获、DEX 方法数越界、动态加载技术以及反编译相关的知识点。

使用 CrashHandler 来获取应用的 crash 信息

通过设置 Thead.setDefaultUncaughtExceptionHandler(handler) 的默认异常处理器就可以实现应用的异常捕获:

1
2
3
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}

完整的 CrashHandler 示例可以点击这里

使用 multidex 来解决方法数越界

Android 中单个 DEX 文件内可引用的方法总数限制在 65,536,如果方法数超过了最大值,那么编译会报 DexIndexOverflowException。此外,在 Android 2.x 版本上由于 LinearAlloc 缓冲区分配过小也有可能导致安装失败。

解决 64K 限制

使用 Google 2014 年提出 multidex 解决方案,步骤如下:

  1. 如果 minSdkVersion 设置为 21 或更高值,只需在模块级 build.gradle 文件中将 multiDexEnabled 设置为 true 即可,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    android {
    defaultConfig {
    ...
    minSdkVersion 21
    targetSdkVersion 28
    multiDexEnabled true
    }
    ...
    }
  2. 如果 minSdkVersion 低于 21,除了将 multiDexEnabled 设置为 true,还需要添加对 multidex jar 包的依赖:

    1
    2
    3
    dependencies {
    compile 'com.android.support:multidex:1.0.3'
    }
  3. 同时还需要修改代码以适配 multidex,以下方式三选一:

    • AndroidManifest.xml 中指定 ApplicationMultiDexApplication

      1
      2
      3
      4
      5
      6
      7
      8
      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.myapp">
      <application
      android:name="android.support.multidex.MultiDexApplication" >
      ...
      </application>
      </manifest>
    • 让应用的 Application 继承 MultiDexApplication

      1
      public class MyApplication extends MultiDexApplication { ... }
    • 此外,还可以重写 attachBaseContext 方法:

      1
      2
      3
      4
      5
      6
      7
      public class MyApplication extends SomeOtherApplication {
      @Override
      protected void attachBaseContext(Context base) {
      super.attachBaseContext(base);
      MultiDex.install(this);
      }
      }
声明主 DEX 文件中需要的类

为 Dalvik 分包构建每个 DEX 文件时,构建工具会执行复杂的决策制定来确定主 DEX 文件中需要的类,以便应用能够成功启动。如果启动期间需要的某个类未在主 DEX 文件中提供,那么应用将崩溃并出现错误 java.lang.NoClassDefFoundError。针对这种情况,我们可以使用构建类型中的 multiDexKeepFilemultiDexKeepProguard 属性声明它们,以手动将这些类指定为主 DEX 文件中的必需项,当然《Android 开发艺术探索》中提到的 --main-dex-list 方式也是同样的原理。

  1. multiDexKeepFile 属性
    创建一个名为 multidex-config.txt 的文件,如下所示:

    1
    2
    com/example/MyClass.class
    com/example/MyOtherClass.class

    然后在构建类型声明该文件即可:

    1
    2
    3
    4
    5
    6
    7
    8
    android {
    buildTypes {
    release {
    multiDexKeepFile file 'multidex-config.txt'
    ...
    }
    }
    }
  2. multiDexKeepProguard 属性
    建一个名为 multidex-config.pro 的文件,如下所示:

    1
    2
    -keep class com.example.MyClass
    -keep class com.example.MyClassToo

    如果想要指定包中的所有类,文件可以如下所示:

    1
    -keep class com.example.** { *; }

    最后在构建类型声明该文件:

    1
    2
    3
    4
    5
    6
    7
    8
    android {
    buildTypes {
    release {
    multiDexKeepProguard 'multidex-config.pro'
    ...
    }
    }
    }
Multidex 方案可能带来的问题:
  1. 应用启动速度会降低。因为应用启动的时候会加载额外的 DEX 文件,所以要尽量避免生成较大的 DEX 文件;
  2. 由于 Dalvik LinearAlloc 的 bug,可能导致采用 multidex 方案的应用无法在 Android 4.0 以前的手机上运行,当然这种现象极少遇到。

Android 的动态加载技术

动态加载技术(插件化技术),即通过插件化减轻应用的内存和 CPU 占用,还可以在不发布新版本的情况下更新某些模块。不同的插件化方案各有特色,但是都需要解决三个基础性问题:资源访问,Activity 生命周期管理和插件 ClassLoader 的管理。

资源访问

宿主程序调起未安装的插件 apk,插件中凡是 R 开头的资源都不能访问了,因为宿主程序中并没有插件的资源,通过 R 来访问插件的资源是行不通的。由于 Activity 的资源访问都是通过 ContextImpl 来完成的,它有两个方法 getAssets()getResources(),它们是用来加载资源的。 具体实现方式是通过反射,调用 AssetManager 的 addAssetPath 方法添加插件的路径,然后将插件 apk 中的资源加载到 Resources 对象中即可。

Activity 生命周期管理

管理 Activity 生命周期的方式各种各样,有两种常见的方式:反射方式和接口方式。反射方式就是通过反射去获取 Activity 的各个生命周期方法,然后在代理 Activity 中去调用插件 Activity 对应的生命周期方法即可。使用反射方式代码复杂,性能开销大。接口方式将 Activity 的生命周期方法提取出来作为一个接口,然后通过代理 Activity 去调用插件 Activity 的生命周期方法,这样就完成了插件 Activity 的生命周期管理。

插件 ClassLoader 的管理

为了更好地对多插件进行支持,需要合理地去管理各个插件的 DexClassLoader,这样同一个插件就可以采用同一个 ClassLoader 去加载类,从而避免了多个 ClassLoader 加载同一个类时所引起的类型转换错误。

更多信息可以参考开源项目 DL-Apk动态加载框架

反编译初步

三个常用的反编译工具如下:

  1. Apktoolhttps://ibotpeaches.github.io/Apktool/
  2. dex2jarhttps://github.com/pxb1988/dex2jar
  3. JD-GUIhttp://jd.benow.ca/

反编译流程:一种方式是将 apk 解压,提取出 classes.dex 文件,接着通过 dex2jar 反编译,然后通过 JD-GUI 打开反编译后的 jar 包。或者直接使用 apktool 对 apk 进行解包/二次打包,以及使用 jarsigner 完成 apk 的签名。

参考资料

  1. https://developer.android.google.cn/studio/build/multidex