使用MultiDex官方解决方案
-
minSdkVersion >=21
: 只需要添加multiDexEnabled true
就OK了,其他的不用瞎搞搞android { defaultConfig { ... minSdkVersion 21 targetSdkVersion 26 multiDexEnabled true //只要添加这行就万事大吉,一马平川了 } ... }
-
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); } }
- 如上述第1条一样,添加
在minSdkVersion<21
情况下遇到的坑
-
Android5.0
及以上的设备还好,Multidex.install(this);
并不会占用多少时间,基本上都是秒执行的,因为Android5.0
及以上的设备在安装apk
的时候就优化好了Multidex
,所以在首次打开的时候执行,Multidex.install(this);
并不会占用多少时间 - 但是在
Android5.0
以下的设备下,Multidex.install(this);
却花费好几秒的时间(在我的程序中花费了4秒左右),意味着软件首次打开至少要白屏4秒才能进入首页(先把鞋子放下,当然有解决方案,在后面) - 在性能更弱的手机中,
Multidex.install(this);
执行的时间更长,有一定的几率出现ANR
(毕竟在主线程执行了这么长时间)
分析
- 在
Android5.0
以下设备中Multidex.install(this)
肯定是要执行的,要不然不能加载除主Dex
之外的其他Dex
文件 - 既然在主线程中执行会出现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了,至少不会死了…
新坑&分析
- 但是软件打开后还是会有很长时间的白屏/黑屏,一点都不丝般柔滑
- 这个坑简单,只要设置
Launch
的Activity
的theme
就行,设置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到底有多坑