Android5.0以下MultiDex下启动丝般柔滑

使用MultiDex官方解决方案

  1. minSdkVersion >=21: 只需要添加multiDexEnabled true就OK了,其他的不用瞎搞搞
    android {
        defaultConfig {
            ...
            minSdkVersion 21 
            targetSdkVersion 26
            multiDexEnabled true //只要添加这行就万事大吉,一马平川了
        }
        ...
    }
    
  2. minSdkVersion <21: 那就要稍微麻烦点
    • 如上述第1条一样,添加multiDexEnabled true
    • compile 'com.android.support:multidex:1.0.1'
    • 使用MultiDexApplication或者在自定义application中添加Multidex.install(this);如下所示
      public class MyApplication extends SomeOtherApplication {
          @Override
          protected void attachBaseContext(Context base) {
              super.attachBaseContext(context);
              Multidex.install(this);
          }
      }
      

minSdkVersion<21情况下遇到的坑

  1. Android5.0及以上的设备还好,Multidex.install(this);并不会占用多少时间,基本上都是秒执行的,因为Android5.0及以上的设备在安装apk的时候就优化好了Multidex,所以在首次打开的时候执行,Multidex.install(this);并不会占用多少时间
  2. 但是在Android5.0以下的设备下,Multidex.install(this);却花费好几秒的时间(在我的程序中花费了4秒左右),意味着软件首次打开至少要白屏4秒才能进入首页(先把鞋子放下,当然有解决方案,在后面)
  3. 在性能更弱的手机中,Multidex.install(this);执行的时间更长,有一定的几率出现ANR(毕竟在主线程执行了这么长时间)

分析

  1. Android5.0以下设备中Multidex.install(this)肯定是要执行的,要不然不能加载除主Dex之外的其他Dex文件
  2. 既然在主线程中执行会出现ARN,那就在新进程里初始化呗.

解决

  • MyApplication.class
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && TextUtils.equals(applicationContext?.packageName, processName)) {
            val multiDexPreference = getSharedPreferences("MultiDexPreference", Context.MODE_PRIVATE)
            if (multiDexPreference.getString(BuildConfig.VERSION_NAME, "") != the2thDexSHA1) {//没有优化过multiDex
                val lock = java.lang.Object()
                thread {
                    Looper.prepare()
                    val handler = object : Handler(Looper.myLooper()) {
                        override fun handleMessage(msg: Message?) {
                            super.handleMessage(msg)
                            synchronized(lock) {
                                lock.notify()
                                multiDexPreference.edit().putString(BuildConfig.VERSION_NAME, the2thDexSHA1).apply()
                                if (null != Looper.myLooper()) Looper.myLooper().quit()
                            }
                        }
                    }
                    startService( //开启新进程初始化multiDex
                            Intent(applicationContext, MultiDexService::class.java)
                                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                                    .putExtra("MESSENGER", Messenger(handler))
                    )
                    Looper.loop()
                }
                synchronized(lock) {
                    try {
                        lock.wait(30000)
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    }
                }
            }
        }
        if (!processName.endsWith("multiDex")) {
            //如果是Android5.0以下的设备,执行到这里说明已经在其他进程初始过MultiDex了,再次执行不会消耗多少时间
            MultiDex.install(this)
        }
    }
    
    private val the2thDexSHA1: String? by lazy {
        val ai = applicationInfo
        val source = ai?.sourceDir
        try {
            val jar = JarFile(source)
            val mf = jar.manifest
            val map = mf.entries
            val a = map["classes2.dex"]
            a?.getValue("SHA1-Digest").toString()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        null
    }
    
  • MultiDexService.class
    class MultiDexService : IntentService("MultiDexService") {
        override fun onHandleIntent(intent: Intent?) {
            val messenger: Messenger? = intent?.getParcelableExtra("MESSENGER")
            MultiDex.install(application)
            messenger?.send(Message())
            android.os.Process.killProcess(android.os.Process.myPid())
    
        }
    }
    
  • AndroidManifest.xml
    <service
            android:name=".MultiDexService"
            android:process=":multiDex" />
    

小小结

  • 如此一来,就避免了低端机型上的ARN了,至少不会死了...

新坑&分析

  • 但是软件打开后还是会有很长时间的白屏/黑屏,一点都不丝般柔滑
  • 这个坑简单,只要设置LaunchActivitytheme就行,设置windowBackground默认壁纸

丝般柔滑解决方案

  • style
    <style name="splashTheme" parent="Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@drawable/splash_placeholder</item>
    </style>
    
  • drawable/splash_placeholder
    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@color/normal_background_color" />
        <item>
            <bitmap
                android:src="@drawable/ic_launcher" />
        </item>
    </layer-list>
    
  • AndroidManifest.xml,修改theme如下例子
    <activity
            android:name=".LauncherActivity"
            android:theme="@style/splashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
    </activity>
    

小结

这下就能丝般柔滑了

参考

必知必会 | Android 性能优化的方面方面都在这儿 @鸿洋
Android MultiDex初次启动APP优化
其实你不知道MultiDex到底有多坑

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 139,860评论 20 593
  • 写在前面 前几天,开发中遇到一个问题,Log信息如下: 从报错信息来看,是没有找到DateTakenCompara...
    ConnorLin阅读 19,838评论 2 33
  • 昨天健身教练跟我说,要我每天早起快走15分钟 我六点半准时迎着太阳起来,喝完水下楼 楼下有很多老爷爷老奶奶,年轻人...
    袁慧_3c1b阅读 36评论 0 0
  • 一、 早晨,我突然宣布一件让我自己都不敢相信的事:我要去剪短...
    不爱叫的猫阅读 16评论 0 0
  • 今天是11月25曰,天气多云天空雾蒙蒙的,气温零下二零度,冻得人直搓手,中午时分太阳从云层钻出,人顿时觉得暖和了不...
    S三叶草s阅读 60评论 0 0