App启动原理及过程详解+闪屏界面+引导页面

App启动原理及过程详解

一、APP启动概述
在 Android 中把在 Launch 界面点击 App 图标(或快捷方式图标)到加载完成第一个 Activity 为止称为 APP 的启动,这是直观上的描述 APP 启动。在同一台手机上同一个 APP,两次启动一个 APP 所花的时间有可能不同,第一次会稍慢些第二次就比第一次稍快些,这是因为 Android 做了个巧妙的设计,把启动分为冷启动和热启动、首次启动(本质上也是冷启动)三大类:

1、冷启动 —— 当启动应用时,系统后台没有该应用的进程(对应的内存空间),这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化 Application 类,再创建和初始化 Activity 类,最后加载 Activity 对应的布局并显示在界面上。

2、热启动 —— 当启动应用时,后台已有该应用的进程(比如说按 back 键、home 键,应用虽然会从前台退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走 Application 这步了,而是直接走 Activity,所以热启动的过程不会创建和初始化 Application,因为一个应用从新进程的创建到进程的销毁,Application 只会初始化一次。

3、首次启动 —— 首次启动严格划分是冷启动中的一种特殊情况,之所以把首次启动单独列出来,一般来说,首次启动时间会比非首次启动要久,首次启动会做一些系统初始化工作,如缓存目录的生产、数据库的建立、SharedPreference 的初始化,如果存在多 dex 和插件的情况下,首次启动会有一些特殊需要处理的逻辑,而且对启动速度有很大的影响,所以首次启动的速度非常重要,毕竟影响用户对 App 的第一印象。

二、APP启动过程
说到 APP 启动过程就离不开三个概念:启动时间、白屏和黑屏
1、启动时间
从代码角度上来看,用户点击 Launcher 界面的 APP 图标到展示真正的 Activity 界面,这期间系统都是需要一定的时间准备(系统需要去分配对应的进程空间并加载对应的资源到内存中,而且一般来说时间不会很长,但是你在 APP 做了很多初始化工作就有可能变得很长,从一定程度上来说你的编码水平决定着启动时间的长短),这段时间就叫做启动时间。

2、白屏和黑屏
因为启动时间因 APP 而异,系统为了避免造成卡顿的误会,如果 APP 继承的主题是android:Theme.Light,则系统会主动预显示出一个白色背景的界面,如果不进行优化每次冷启动的时候都有可能先显示出白屏过了一段时间之后才真正跳转到我们的真正的 Activity,即所谓的白屏现象(至于为啥是白色的,是因为系统源码的样式资源里定义的颜色就是白色的,ctrl + 点击对应的主题一层层往下找就会发现 "Theme.AppCompat.Light" → "Base.Theme.AppCompat.Light" → "Base.V7.Theme.AppCompat.Light" → "Platform.AppCompat.Light" → "android:Theme.Holo.Light")

<style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowActionBar">false</item>
    <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
    <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
    <item name="android:borderlessButtonStyle">?attr/borderlessButtonStyle</item>
    <!-- Window colors -->
    <item name="android:colorForeground">@color/foreground_material_light</item>
    <item name="android:colorForegroundInverse">@color/foreground_material_dark</item>
    <item name="android:colorBackground">@color/background_material_light</item>
    <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
    <item name="android:disabledAlpha">@dimen/abc_disabled_alpha_material_light</item>
    <item name="android:backgroundDimAmount">0.6</item>
    黑白屏问题根源
    <item name="android:windowBackground">@color/background_material_light</item>
</style>

反之如果 APP 主题没有继承 Theme.Holo.Light,则会显示黑屏,以上就是黑白屏的根源。

黑白屏的解决措施

系统进程在创建 Application 的过程中会产生一个 BackgroudWindow,等到 App 完成了第一次绘制,系统进程才会用 MainActivity 的界面替换掉原来的 BackgroudWindow,系统首先会读取当前 Activity 的 Theme,然后根据 Theme 中的配置来绘制,当 Activity 加载完毕后,才会替换为真正的界面。

1、设置 Windows 背景为透明

这种解决方案其实就相当于是欺骗用户,当用户点击 APP 时,系统会自动把 windowBacground 置为透明的,自然看不见黑白屏了,取而代之是看到系统当前的 Launcher 界面,容易造成点击了 APP 后没有响应的误解,以前 QQ 和今日头条采用过这样的方式,不过可能会和转场动画冲突,遇到的较多都是因为继承关系错误造成的,还有适配 android8.0 的时候在 values-v26 中,取消 android:windowIsTranslucent 属性。

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- 将背景设成透明的 -->
    <!-- 但是有 bug,在 Android 8.0 的设备上会出现打不开应用的情况,所以这个方法是个鸡肋,不推荐使用 -->
    <item name="android:windowIsTranslucent">true</item>
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

解决启动 Android 应用程序时出现白屏或者黑屏的问题
以上方式都不是很友好,推荐使用下面的方式

2、单独定义用于特定 Activity 的 style(用这个)(闪屏界面)

这是 Google 官方提供的解决方案:通过对属性 android:windowBackground 来进行加载前的配置,较常见的是使用一个 layer-list 资源来作为 android:windowBackground 要显示的图,然后再替换上 Activity 真正的主题,具体步骤如下:
1、定义替换的背景 Drawable 资源文件
这里是通过 layer-list 来实现图片的叠加,让开发者可以自由组合。其中属性 android:opacity=“opaque” 是为了防止在启动的时候出现背景的闪烁,你也可以使用其他 Drawable 资源。

layer_splash.xml

@drawable/begin_page 闪屏页中的图片

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:opacity="opaque">
    <item android:drawable="@color/white" />
    <item>
        <bitmap
            android:gravity="center"
            android:src="@drawable/begin_page" />
    </item>

</layer-list>

2、单独定义 Activity 特有的预加载的样式,这里定义的样式是将要配置到对应的 Activity 上的。

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <!--将用于配置到特定的 Activity 上的样式-->
    <style name="SplashStyle" parent="AppTheme">
        <item name="android:windowBackground">@drawable/layer_splash</item>
    </style>
</resources>

3、在清单 AndroidManifest 中给对应的 Activity 设置预加载的样式 style,需要注意一定是 Activity 的Theme,而不是 Application 的Theme

<activity android:name=".ui.main.activity.SplashActivity"
    android:theme="@style/SplashStyle">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

4、在 Activity 加载真正的界面之前,将 Theme 还原回正常的 Theme(可以不写)

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme); // 还原回正常的 Theme
        super.onCreate(savedInstanceState);
        SystemClock.sleep(2000);
        setContentView(R.layout.activity_main);
    }
}

闪屏页中的逻辑

public class SplashActivity extends AppCompatActivity {

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

        // 停留 2s
        SystemClock.sleep(2000);

        String token = (String) SpUtil.getParam(Constants.TOKEN, "");

        // 判断有没有保存过,如果保存过就直接进入登录页面
        Boolean b = (Boolean) SpUtil.getParam(Constants.INTRODUCTION, false);

        // 判断之前有没有登陆过,登陆过直接跳主页面,没有登陆过就再判断有没有进入过引导页,进入过就直接进入登录页面,否则进入引导页
        if (!token.equals("")) {
            MainActivity.starAct(SplashActivity.this);
            finish();
        } else if (b) {
            LoginActivity.startAct(SplashActivity.this,LoginActivity.TYPE_LOGIN);
            finish();
        } else {
            Intent intent = new Intent(SplashActivity.this, IntroductionActivity.class);
            startActivity(intent);
            finish();
        }
    }
}

引导页面

引导页面.gif
指示器(是一个自定义view)
/**
 * 用法:
 * 初始化的时候
 * mIndicator.initSize(80, 32, 6);
 * mIndicator.setNumbers(3);
 * 和 ViewPager 关联的时候
 * setSelectedState(int position)
 */
public class PreviewIndicator extends LinearLayout {

    // 指示器个数
    private int INDICATOR_COUNT = 3;
    private List<ImageView> mImageList = new ArrayList<>();
    // 选中时对应指示器点的宽度
    private int chooseSize = 80; // 80
    // 未选中时指示器点的宽度
    private int nomalSize = 32; // 32
    // 指示器高度
    private int height = 10; // 10

    public PreviewIndicator(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    // 设置指示器点的个数
    public void setNumbers(int number) {
        INDICATOR_COUNT = number;
        initView();
    }

    // 初始化所有的指示器点
    private void initView() {
        // initSize();
        removeAllViews();
        mImageList.clear();
        for (int i = 0; i < INDICATOR_COUNT; i++) {
            ImageView imageView = new ImageView(getContext());
            if (i == 0) {
                setSelectedState(imageView);
            } else {
                setNomalState(imageView);
            }
            addView(imageView);
            mImageList.add(imageView);
        }

    }

    /**
     * 初始化指示器点的宽高参数,单位 dp
     * @param chosedSize 选中指示器点的宽度
     * @param nomal 未选中时指示器点的宽度
     * @param hei 指示器点的高度
     */
    public void initSize(int chosedSize, int nomal, int hei) {
        chooseSize = SystemUtil.dp2px(getContext(), chosedSize);
        nomalSize = SystemUtil.dp2px(getContext(), nomal);
        height = SystemUtil.dp2px(getContext(), hei);
    }

    /**
     * 设置未选中指示器点的参数
     * @param imageView
     */
    private void setNomalState(ImageView imageView) {
        imageView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.bg_eaeaea_r6));
        LayoutParams params = new LayoutParams(nomalSize, height);
        params.setMargins(height, 0, 0, 0);
        imageView.setLayoutParams(params);
    }

    /**
     * 设置选中指示器点的宽度
     * @param imageView
     */
    private void setSelectedState(ImageView imageView) {
        imageView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.bg_fa6a13_r6));
        LayoutParams params = new LayoutParams(chooseSize, height);
        params.setMargins(height, 0, 0, 0);
        imageView.setLayoutParams(params);
    }

    /**
     * 设置哪个指示器的点为选中的点
     * @param position 该选中的点
     */
    public void setSelected(int position) {
        for (int i = 0; i < mImageList.size(); i++) {
            ImageView imageView = mImageList.get(i);
            int curPosition = position % mImageList.size();
            if (i == curPosition) {
               setSelectedState(imageView);
            } else {
                setNomalState(imageView);
            }
        }
    }

}
Activity 对应的 xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.IntroductionActivity"
    android:fitsSystemWindows="true">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v4.view.ViewPager>

    <com.example.lenovo.everywheretravel.widget.PreviewIndicator
        android:id="@+id/previewIndicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="@dimen/dp_61"
        android:layout_centerHorizontal="true">

    </com.example.lenovo.everywheretravel.widget.PreviewIndicator>

    <Button
        android:id="@+id/experience"
        android:layout_width="@dimen/dp_127"
        android:layout_height="@dimen/dp_40"
        android:text="@string/experience"
        android:textSize="@dimen/sp_16"
        android:textColor="@color/c_ffffff"
        android:background="@drawable/color_ff9d53_fa6a13"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="@dimen/dp_43"
        android:layout_centerHorizontal="true"
        android:visibility="gone" />

</RelativeLayout>
3 个 view(基本一样)

item_main_content_one.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/guide_01"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="@dimen/sp_30"
        android:layout_marginTop="@dimen/dp_100"
        android:layout_centerHorizontal="true"/>

</RelativeLayout>
Activity
public class IntroductionActivity extends BaseActivity<EmptyView, EmptyPresenter> implements EmptyView {

    @BindView(R.id.viewpager)
    ViewPager viewpager;
    @BindView(R.id.previewIndicator)
    PreviewIndicator previewIndicator;
    @BindView(R.id.experience)
    Button experience;
    private ArrayList<View> arrayList;
    private IntroductionViewPagerAdapter introductionViewPagerAdapter;

    @Override
    protected EmptyPresenter initPresenter() {
        return new EmptyPresenter();
    }

    @Override
    protected int initLayoutId() {
        return R.layout.activity_introduction;
    }

    @Override
    protected void initView() {
        super.initView();

        arrayList = new ArrayList<>();

        initViewOne();

        initViewTwo();

        initViewThree();

        previewIndicator.setNumbers(arrayList.size());

        introductionViewPagerAdapter = new IntroductionViewPagerAdapter(arrayList, IntroductionActivity.this);
        viewpager.setAdapter(introductionViewPagerAdapter);
    }

    @Override
    protected void initListener() {
        super.initListener();
        // viewpager 滑动监听
        viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int i, float v, int i1) {

            }

            @Override
            public void onPageSelected(int i) {
                previewIndicator.setSelected(i);
                switch (i) {
                    case 2:
                        // 隐藏指示器,显示“立即体验”按钮
                        previewIndicator.setVisibility(View.GONE);
                        experience.setVisibility(View.VISIBLE);
                        break;
                    case 0:
                    case 1:
                        // 隐藏“立即体验”按钮,显示指示器
                        previewIndicator.setVisibility(View.VISIBLE);
                        experience.setVisibility(View.GONE);
                        break;
                }
            }

            @Override
            public void onPageScrollStateChanged(int i) {

            }
        });
        
        // 点击立即体验保存一个状态,当应用下次启动时就不进入引导页面了
        experience.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SpUtil.setParam(Constants.INTRODUCTION,true);
                LoginActivity.startAct(IntroductionActivity.this,LoginActivity.TYPE_LOGIN);
                finish();
            }
        });
    }

    private void initViewOne() {
        View view1 = LayoutInflater.from(this).inflate(R.layout.item_introduction_one, null);
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getResources().getString(R.string.introduction_text1));
        // 前景色
        ForegroundColorSpan foregroundColorSpan1 = new ForegroundColorSpan(getResources().getColor(R.color.c_78d9ff));
        spannableStringBuilder.setSpan(foregroundColorSpan1, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        ForegroundColorSpan foregroundColorSpan2 = new ForegroundColorSpan(getResources().getColor(R.color.c_fa6a13));
        spannableStringBuilder.setSpan(foregroundColorSpan2, 6, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        TextView tv1 = view1.findViewById(R.id.tv1);
        tv1.setText(spannableStringBuilder);
        arrayList.add(view1);
    }

    private void initViewTwo() {
        View view2 = LayoutInflater.from(this).inflate(R.layout.item_introduction_two, null);
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getResources().getString(R.string.introduction_text2));
        // 前景色
        ForegroundColorSpan foregroundColorSpan1 = new ForegroundColorSpan(getResources().getColor(R.color.c_78d9ff));
        spannableStringBuilder.setSpan(foregroundColorSpan1, 4, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        ForegroundColorSpan foregroundColorSpan2 = new ForegroundColorSpan(getResources().getColor(R.color.c_fa6a13));
        spannableStringBuilder.setSpan(foregroundColorSpan2, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        TextView tv2 = view2.findViewById(R.id.tv2);
        tv2.setText(spannableStringBuilder);
        arrayList.add(view2);
    }

    private void initViewThree() {
        View view3 = LayoutInflater.from(this).inflate(R.layout.item_introduction_three, null);
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getResources().getString(R.string.introduction_text3));
        // 前景色
        ForegroundColorSpan foregroundColorSpan1 = new ForegroundColorSpan(getResources().getColor(R.color.c_78d9ff));
        spannableStringBuilder.setSpan(foregroundColorSpan1, 4, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        ForegroundColorSpan foregroundColorSpan2 = new ForegroundColorSpan(getResources().getColor(R.color.c_fa6a13));
        spannableStringBuilder.setSpan(foregroundColorSpan2, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        TextView tv3 = view3.findViewById(R.id.tv3);
        tv3.setText(spannableStringBuilder);
        arrayList.add(view3);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容