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”)
反之如果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属性。
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 闪屏页中的图片
-
2、单独定义Activity 特有的预加载的样式这里定义的样式是将要配置到对应的Activity上的。
3、在清单AndroidManifest中给对应的Activity设置预加载的样式style需要注意一定是Activity的Theme,而不是Application的Theme
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();
}
}
}
引导页面
指示器(是一个自定义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 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
3个view(基本一样)
item_main_content_one.xml
Activity
public class IntroductionActivity extends BaseActivity implements EmptyView {
@BindView(R.id.viewpager)
ViewPager viewpager;
@BindView(R.id.previewIndicator)
PreviewIndicator previewIndicator;
@BindView(R.id.experience)
Button experience;
private ArrayList 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);
}
}