Android 的应用冷优化

1,045 阅读3分钟

应用启动分类

冷启动

用户点击屏幕上的应用图标,经过展示启动窗口创建进程展示应用几个过程

热启动

用户进程已经创建,如果响应了低内存事件,例如在 onTrimMemory 中清除资源,则需要重新初始化

如何检测应用启动时长

  • 通过 adb shell am start -S -W $packageName/.MainActivity 启动检测
  • 通过显示调用 reportFullDrawn 根据业务需求,通知系统完成加载
  • 通过 android-sdk/platform-tools/systracesystrace.py 通过 Python systrace.py --time=10 -o launch.html sched gfx view wm 查看每个函数的耗时
  • 通过 Android Studio Profiler 启动应用查看

为什么会出现白屏

在应用启动过程中 ActivityStackstartActivityLocked 方法中会判断当前应用的启动模式,若为冷启动,则调用 ActivityRecordwindowContainerControllershowStartingWindow 方法,添加启动白屏页

2018-04-26_start_activity.png

通过 WindowMangerService 调用 mPolicyaddSplashScreen 方法,创建了一个 PhoneWindow 添加到窗口上

关键代码

if (theme != context.getThemeResId() || labelRes != 0) {
    try {
        context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
        context.setTheme(theme);
    } catch (PackageManager.NameNotFoundException e) {
        // Ignore
    }
}

final PhoneWindow win = new PhoneWindow(context);
win.setIsStartingWindow(true);

win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
        WindowManager.LayoutParams.MATCH_PARENT);

wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();

if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
    + packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));

wm.addView(view, params);

如何优化

启动优化步骤分类

  • 白屏的视觉优化
  • MultiDex 优化
  • 逻辑代码优化
  • 首页布局优化

白屏的优化

对于白屏启动页面的优化,根据上述的代码分析,可以通过设置主题,为 windowBackground 添加与启动页一致的图片,视觉上可以骗过用户

MultiDex 优化

着应用的不断迭代,内部的方法数会不断增加,最终超过方法数上限。而官方也退出了 MultiDex的方案来解决,也就是意味着分包。在低版本手机 DVM 上,我们需要手动调用 MultiDex.install 加载主 Dex 以外的文件,可能造成 ANR

低版本启动的优化

面对这个问题,我们可以采用 Facebook 提出的方案。创建一个新的进程在 ApplicationattachBaseContext 中调用 MultiDex.install,主进程可以通过多种手段阻塞等到加载结束之后再进入应用

class App : Application() {

    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)

        if (isAsyncLaunchProcess().not()) {
            val thread = DexInstallDeamonThread(base)
            thread.start()

            synchronized(lock) {
                try {
                    lock.wait()
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }

            thread.exit()
        }
    }

    fun isAsyncLaunchProcess(): Boolean {
        val processName = SystemUtils.getCurrentProcessName(this)
        return processName != null && processName.contains(":install")
    }

    private class DexInstallDeamonThread(private val base: Context) :
        Thread() {
        private var handler: Handler? = null
        private var looper: Looper? = null

        @SuppressLint("HandlerLeak")
        override fun run() {
            Looper.prepare()
            looper = Looper.myLooper()
            handler = object : Handler() {
                override fun handleMessage(msg: Message) {
                    synchronized(lock) { lock.notify() }
                }
            }
            val messenger = Messenger(handler)
            val intent = Intent(base, MultiInstallActivity::class.java)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            intent.putExtra("MESSENGER", messenger)
            base.startActivity(intent)
            Looper.loop()
        }

        fun exit() {
            if (looper != null) looper!!.quit()
        }
    }
}
class MultiInstallActivity : AppCompatActivity() {
    private var messenger: Messenger? = null
    public override fun onCreate(savedInstanceState: Bundle?) {
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        super.onCreate(savedInstanceState)
        window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
        setContentView(R.layout.activity_install)
        val from = intent
        messenger = from.getParcelableExtra("MESSENGER")
        thread {
            try {
                Log.d(TAG, "start install")
                MultiDex.install(application)
                Thread.sleep(5000L)
                Log.d(TAG, "finish install")
                messenger?.send(Message.obtain())
            } catch (e: Exception) {
                e.printStackTrace()
            }
            runOnUiThread {
                finish()
                exitProcess(0)
            }
        }
    }

    override fun onBackPressed() {
        //无法退出
    }

    companion object {
        private const val TAG = "MultiInstallActivity"
    }
}
<activity
    android:name="com.weex.multidexinstall.MultiInstallActivity"
    android:launchMode="singleTask"
    android:process=":install" />

逻辑代码优化

在应用启动过程中,我们一般会在 ApplicationonCreate 中加载 SDK 以及读取 SharedPreference 中的值等耗时操作

  • 对于 SharedPreference 可以替换为 MMKV
  • 对于其他的业务逻辑加载,可以参考 Alpha 构建属于自己的异步启动框架

首页布局优化

对于布局优化,比较常见的一些技术如

  • ViewStub
  • merge 标签
  • 使用ConstraintLayout 替换原来的布局标签