APP启动时白屏优化及multidex优化

参考

https://juejin.im/post/5d95f4a4f265da5b8f10714b

https://blog.csdn.net/suyimin2010/article/details/80635579

https://www.cnblogs.com/whycxb/p/9312914.html

问题说明

当打开一个Activity时,如果这个Activity所属Application还没有在运行,系统会为这个Activity的创建一个进程(每开启一个进程都会有一个Application,所以Application的onCreate()可能会被调用多次),但进程的创建与初始化都需要时间,在这个动作完成之前,如果初始化的时间过长,屏幕上可能没有任何动静,用户会以为没有点到按钮。所以既不能停在原来的地方又没到显示新的界面,怎么办呢?这就有了StartingWindow(也称之为PreviewWindow)的出现,这样看起来就像Activity已经启动起来了,只是数据内容还没有初始化好。

StartingWindow一般出现在应用程序进程创建并初始化成功前,所以它是个临时窗口,对应的WindowType是TYPE_APPLICATION_STARTING。目的是告诉用户,系统已经接受到操作,正在响应,在程序初始化完成后显示目的UI,同时移除这个窗口。

这个StartingWindow就是我们要讨论的白屏和黑屏的"元凶"。

设置Theme

怎么解决呢?

市面上的常用app的StartingWindow 的处理方式有三种:

  1. 使用系统默认的 StartingWindow :用户点了应用图标启动应用,马上弹出系统默认的 StartingWindow(就是做动画的那个 Window) ,等应用加载好第一帧之后,StartingWindow 消失,显示应用第一帧,无缝衔接,体验还不错,这也是通常大部分 Android 应用的场景;比如大部分 Android 系统的自带应用,即刻、汽车之家等
  1. 自己定制简单的 StartingWindow :用户点了应用图标启动应用,弹出应用自己定制的StartingWindow,等应用加载好第一帧之后,定制的 StartingWindow 消失,显示应用主界面,由于 StartingWindow 是自己定制的,启动的时候 Decode Bitmap 或者 Inflate 自定义 Layout 会有一定的耗时,但是总的来说与系统默认的差别不大,用户体验优;这样的应用包括淘宝、京东、微博、今日头条、美团等
  1. 把 StartingWindow 禁掉或者设置透明 :用户点了应用图标启动应用,由于 StartingWindow 被禁掉或者被设置透明,所以会出现点击图标后,除了图标黑一下之外没有任何响应,过个 1-N 秒(取决于应用第一帧的加载速度),直接显示应用主界面。这样的毒瘤应用包括:微信、微信读书、UC 浏览器、支付宝、工商银行、米家等。

一般我们使用第二种处理方式:

我们会对Application和Activity设置Theme,系统会根据设置的Theme初始化StartingWindow。

Window布局的顶层是DecorView,StartingWindow显示一个空DecorView,但是会给这个DecorView应用要到开的Activity指定的Theme,如果这个Activity没有指定Theme就用Application的(Application系统要求必须设置Theme)。

首先创建一个应用启动页(StartingWindow)的theme

<style name="AppTheme.StartingWindowTheme">
    <!-- 可以设置成纯颜色(设置一个和Activity UI相似的背景) -->
    <!--<item name="android:windowBackground">@color/startingwindow_bgcolor</item>-->
     <!--也可以设置成一张图片 -->
    <item name="android:windowBackground">@drawable/startingwindow_bg</item>
</style>

在主Activity上应用上边创建的theme

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.why.project.androidstartingwindowdemo">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--将首页的them设置成自定义的样式-->
        <activity android:name=".MainActivity"
                  android:theme="@style/AppTheme.StartingWindowTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

在主activity启动后恢复原有的theme

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme);//恢复原有的样式
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    ...
}

还有一种效果是 主activity的theme不设置其他theme,然后主activity布局文件中将背景(android:background)设置为透明,那么这样就可以实现APP启动后StartingWindow和 主activity 是同一张背景图片。

MultiDex 的优化

https://juejin.im/post/5d95f4a4f265da5b8f10714b#heading-10

看这个之前需要线了解一下multidex的原理,在另一个文档里。

当需要多dex支持时,需要使用到multidex的support库,但这个库在4.4的机器上第一次启动时比较耗时的(5.0及以上都默认支持多dex),所以这个是另一个白屏/黑屏问题的原因。

在Android 4.4的机器打印MultiDex.install(context)耗时如下:

MultiDex.install 耗时:1320

应用进程不存在的情况下,从点击桌面应用图标,到应用启动(冷启动),大概会经历以下流程:

  1. Launcher startActivity
  2. AMS startActivity
  3. Zygote fork 进程
  4. ActivityThread main()
    1. ActivityThread.attach
    2. handleBindApplication
    3. Application.attachBaseContext
    4. ContentProvider.installContentProviders
    5. Application.onCreate
  5. ActivityThread 进入loop循环
  6. Activity生命周期回调,onCreate、onStart、onResume...

逻辑如下:
1. 创建临时文件,作为判断MultiDex是否加载完的条件
2. 启动LoadDexActivity去加载MultiDex(LoadDexActivity在单独进程),加载完会删除临时文件
3. 开启while循环,直到临时文件不存在才跳出循环,进入Application的onCreate

MyApplication

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);

    boolean isMainProcess = isMainProcess(base);

    //主进程并且vm不支持多dex的情况下才使用 MultiDex
    if (isMainProcess && !SystemUtil.isVMMultidexCapable()){
        //在里边如果安装multidex,就会有个while循环来检查,此时主进程就卡在 此处。
        loadMultiDex(base);
    }
}
private void loadMultiDex(Context context) {
    newTempFile(context); //创建临时文件

    //启动另一个进程去加载MultiDex,LoadMultiDexActivity在清单文件中设置了另一个进程。
    Intent intent = new Intent(context, LoadMultiDexActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);

    //检查刚创建的newTempFile,如果不存在那么表示MultiDex安装完(安装完会删除临时文件)
    checkUntilLoadDexSuccess(context);

    //另一个进程以及加载 MultiDex,有缓存了,所以主进程再加载就很快了。
    //第二次MultiDex.install, 为什么主进程要再加载,因为每个进程都有一个ClassLoader
    MultiDex.install(context);

    preNewActivity();
}

对象第一次创建的时候,java虚拟机首先检查类对应的Class 对象是否已经加载。
如果没有加载,jvm会根据类名查找.class文件,将其Class对象载入。
同一个类第二次new的时候就不需要加载类对象,而是直接实例化,创建时间就缩短了。
private void preNewActivity() {
    long startTime = System.currentTimeMillis();
    MainActivity mainActivity = new MainActivity();
    Log.d(TAG, "preNewActivity 耗时: " + (System.currentTimeMillis() - startTime));
}

//创建一个临时文件,MultiDex install 成功后删除
private void newTempFile(Context context) {
    try {
        File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
        if (!file.exists()) {
            Log.d(TAG, "newTempFile: ");
            file.createNewFile();
        }
    } catch (Throwable th) {
        th.printStackTrace();
    }
}

/**
 * 检查MultiDex是否安装完,通过判断临时文件是否被删除,此方法里导致主进程在这里边卡住,不进行执行。
 * @param context
 * @return
 */
private void checkUntilLoadDexSuccess(Context context) {
    File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
    int i = 0;
    int waitTime = 100; //睡眠时间
    try {
        while (file.exists()) {
            Thread.sleep(waitTime);
            Log.d(TAG, "checkUntilLoadDexSuccess: sleep count = " + ++i);
            if (i > 40) {
                Log.d(TAG, "checkUntilLoadDexSuccess: 超时,等待时间: " + (waitTime * i));
                break;
            }
        }
    }catch (Exception e){
        e.printStackTrace();
    }

}

接着上边启动LoadMultiDexActivity,此activity组件是运行在子进程中的。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_load_multi_dex);

    Thread thread = new Thread() {
        @Override
        public void run() {

            loadMultiDex();
        }
    };
    thread.setName("multi_dex");
    thread.start();

    // 显示一个dialog
    showLoadingDialog();
}

private void loadMultiDex(){

    MultiDex.install(LoadMultiDexActivity.this);

    try {
        //模拟MultiDex耗时很久的情况
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    aftetMultiDex();
}

private void aftetMultiDex() {

    deleteTempFile(this);

    //将这个进程杀死
    Log.d(TAG, "aftetMultiDex: ");
    finish();
    Process.killProcess(Process.myPid());
}

private void deleteTempFile(Context context) {
    try {
        File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
        if (file.exists()) {
            file.delete();
            Log.d(TAG, "deleteTempFile: ");
        }
    } catch (Throwable th) {
        th.printStackTrace();
    }
}

private void showLoadingDialog(){
    new AlertDialog.Builder(this)
            .setMessage("加载中,请稍后...")
            .show();
}

当子进程处理完成后主进程attachBaseContext就会继续往下执行。

其实这里便做了两个优化,

  • 一个就是multidex的加载放在子进程中,
  • 还有一个就是提前对主activity进行创建,静态初始化。

主线程长时间循环检测文件时,为什么不会卡?

是因为主进程的主线程确实卡在检查文件处,但因为启动了子进程,而子进程也有自己的主线程(ui线程),那么此时只要不在子线程的主线程上做耗时操作,那么就可以使得子进程可以像主进程一样响应用户。

原文地址:https://www.cnblogs.com/muouren/p/11741309.html

时间: 2024-11-29 04:15:59

APP启动时白屏优化及multidex优化的相关文章

APP启动时白屏或出现标题

<application         android:name="com.zdkj.zdexpress.MyApplication"         android:allowBackup="true"         android:icon="@drawable/ic_launcher"         android:label="@string/app_name"         android:theme=

Xamarin.Android splash页面瞬间响应_避免APP启动闪白屏

Application和Activity中的onCreate都进行了优化,基本没有耗时操作,但是启动应用之后还是会闪现一下白色背景,然后才进入Splash页面,对比了一下QQ.微信.微博等客户端,点击之后都是瞬间响应Splash启动页,差别在哪里呢. 其实就算你onCreate啥都不做,仍然会闪一下白屏,因为初始化解析界面时需要一定时间,解决方法是自定义Theme. 自定义如下 <style name="AppSplash" parent="android:Theme&

android开发之提高应用启动速度_splash页面瞬间响应_避免APP启动闪白屏

Application和Activity中的onCreate都进行了优化,基本没有耗时操作,但是启动应用之后还是会闪现一下白色背景,然后才进入Splash页面,对比了一下QQ.微信.微博等客户端,点击之后都是瞬间响应Splash启动页,差别在哪里呢. 其实就算你onCreate啥都不做,仍然会闪一下白屏,因为初始化解析界面时需要一定时间,解决方法是自定义Theme. 自定义如下 <style name="AppSplash" parent="android:Theme&

react-native启动时红屏报错:Unable to load script.Make sure you&#39;re either running a metro server or that ....

一.报错信息内容 我是在Android Studio中运行启动react-native项目时报的这个错误 1.报错提示:Unable to load script.Make sure you're either running a metro server( run 'react-native start' ) or that your bundle 'index.android.bundle' is packaged correctly for release. 2.中文翻译:无法加载脚本.请

Node升级 启动RN报错:react-native启动时红屏报错:Unable to load script.Make sure you&#39;re either running a metro server or that

1. 项目中在android/app/src/main/创建文件夹  assets 2.项目中执行命令 1. 项目中在android/app/src/main/创建文件夹  assets 2.项目中执行命令 react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle

Android App 启动时显示正在加载图片(源码)

微信.QQ.天天动听等程序,在打开时显示了一张图片,然后跳转到相关界面.本文实现这个功能,其实很简单.... 新建两个Activity,LoadingActivity,MainActivity,将LoadingActivity设置为android.intent.action.MAIN.使用TimerTesk,或者Thread将LoadingActivity显示几秒后跳转到MainActivity界面. LoadingActivity: new Timer().schedule(new Timer

loadView在App启动时到底都干了些什么?

loadView在App启动时到底都干了些什么? 查阅苹果官方文档如下: 1. 当你访问一个ViewController的view属性时,如果此时view的值是nil,那么,ViewController就会自动调用loadView这个方法.这个方法就会加载nib文件或者创建一个空的view对象(self.view = nil). 2.使用nib文件创建view时,没必要重载loadView方法,因为loadView的作用就是加载nib.如果你非要重载,那么必须调用[super loadView]

MTK-Android APP启动时瞬间闪现黑屏(Theme &amp; Style)

闪屏原因:主要是我们启动Activity的时候,需要跑完onCreate和onResume:Android系统需要处理一些数据后,才会显示.按照这种思路,是不是我把初始化的工作尽量减少就可以避免黑屏?事实是,就算你onCreate啥都不做,仍然会闪一下黑屏,因为初始化解析界面时需要一定时间,下面是解决办法: 1.自定义Theme设置背景图Theme<style name="Theme.AppStartLoad" parent="android:Theme"&g

【Android端 APP 启动时长获取】启动时长获取方案及具体实施

一.什么是启动时长? 1.启动时长一般包括三种场景,分别是:新装包的首次启动时长,冷启动时长.热启动时长 冷启动 和 热启动 : (1)冷启动:当启动应用时,后台没有该程序的进程,此时启动的话系统会分配一个新的进程给应用. (2)热启动:程序的进程依然存在,启动时通过已有进程启动进入到Activity显示页面的,就是热启动,或者从Android官网来看我们获取到的其实是温启动时长,就是Activity不存在的情况. (3)新装包的启动时长: 新装包的启动时长,预估是最长的,并且在5.0以下及5.