每日一篇学习博客——Android冷启动
- 什么是冷启动
- 什么是热启动
- 冷启动的方式直接就造成的不好体验如下
- 先分析下冷启动的产生原因
- 先说说怎么处理白屏或者黑屏吧
- 优化启动的耗时操作,减少启动时间
什么是冷启动
简单来说就是APP的需要初始化启动,后台没有该应用的进程,直接点就是APP第一次打开、或者进程被杀死重新打开,这些启动都是需要重新创建Application实例——本人自己的理接(阔能很片面)
什么是热启动
当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,也就是直接从进程中启动,不需要重新创建Application,这个方式叫热启动。
冷启动的方式直接就造成的不好体验如下
- 响应慢 ,点击应用的icon不能瞬间打开应用,会出现短暂的白屏或者是黑屏;
- 用户体验很差,应用短暂的黑白屏现象直接让用户第一感觉就觉得不是很友好,卡顿的感觉,因为没有直接的提示或者过度,这一到两秒就显得很苍白;
先分析下冷启动的产生原因
首先冷启动是APP应用启动流程造成的,无可避免;只能尽可能的优化启动尽可能的缩短启动时间,其次就是做一些处理,优雅美观的实现过度.
白屏或者黑屏就是应用还没有加载道布局文件,显示得window窗口背景
先说说怎么处理白屏或者黑屏吧
- 瞒天过海之设置透明窗口背景:
style.xml设置(注意这里的AppTheme需要在``AndroidManifest.xml里引用):
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
- 设置一张背景图: 例如splash.png
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">>@mipmap/splash</item>
<item name="android:windowIsTranslucent">true</item>
</style>
对比两者:
1). 第一种: 给人很慢的感觉,大一点的APP甚至让用户觉得是不是手机卡了,反应慢.优点的话就是直接一次性当前页面全部加载出来.
2). 第二种:让白屏/黑屏能够通过展示启动的页面来过度,简单来说就是=====>优雅 ,现在很多应用都是这么处理的,但是这种方式会使你的App一直有这个图片背景在显示,不是我们想要的结果,所以我们在Activity中重写一个生命周期中界面加载完成后的回调方法,将背景颜色改为白色
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//修改背景为白色
getWindow().setBackgroundDrawable(new ColorDrawable(Color.WHITE));
}
还阔以设置的style不全局引用,只在主要页面引用就行,比如MainActivity.
代码:
<style name="AppTheme.Launcher" parent="Theme.AppCompat.Light.DarkActionBar">
<!--设置背景图-->
<item name="android:windowBackground">>@mipmap/splash</item>
<!--设置透明-->
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
然后继续操作上面的背景图修改为白色的操作就行!
3. 建议还是做一个欢迎页比较好,能有效的避免白屏问题还能做广告,这个页面的操作很少,还阔以设置跳转时间,提供充足的时间满足耗时操作,具体操作代码我写个了个小demo可以参考下.。很多大一点的应用都是有自己的欢迎页,以及自己定义的背景图来过度
SplashActivity——启动页
public class SplshActivity extends AppCompatActivity implements View.OnClickListener {
private TimeCount time;
private Button button;
private ImageView iv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
iv = findViewById(R.id.iv);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
button = findViewById(R.id.btn_into);
button.setOnClickListener(this);
time = new TimeCount(5000, 1000);
time.start();
}
//计时器
class TimeCount extends CountDownTimer {
public TimeCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) {
button.setText("跳过 " + (millisUntilFinished / 1000) + "");
}
@Override
public void onFinish() {
SharedPreferences sf = getSharedPreferences("data", MODE_PRIVATE);//判断是否是第一次进入
boolean isFirstIn = sf.getBoolean("isFirstIn", true);
SharedPreferences.Editor editor = sf.edit();
if (isFirstIn) { //若为true,则是第一次进入,进入引导页
Intent intent = new Intent(SplshActivity.this, MainActivity.class);
startActivity(intent);
} else {
editor.putBoolean("isFirstIn", false);
Intent intent = new Intent(SplshActivity.this, MainActivity.class);
startActivity(intent);
// finish();
}
editor.commit();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_into:
time.cancel();//关闭倒计时
time.onFinish();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (time != null) {
time.cancel();
time = null;
}
}
}
对应的xml代码:activity_splash.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/launch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/splash"/>
<Button
android:id="@+id/btn_into"
android:layout_width="60dp"
android:layout_height="30dp"
android:text="跳过"
android:layout_marginTop="20dp"
android:textSize="15dp"
android:background="@drawable/splash_btn_shape_gray1"
android:layout_alignParentRight="true"
android:layout_marginRight="19dp"
android:textColor="#ffffff"
/>
</RelativeLayout>
drawble文件:splash_btn_shape_gray1.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#AAB2BD" />
<corners android:radius="30dp" />
</shape>
style.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowIsTranslucent">true</item>
<!--设置背景图-->
<item name="android:windowBackground">@mipmap/splash</item>
<!--设置透明-->
<!-- <item name="android:windowBackground">@android:color/transparent</item>-->
</style>
</resources>
MainActivity.class
public class MainActivity extends AppCompatActivity {
/**
* 退出时的时间
*/
private long mExitTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//修改背景为白色
getWindow().setBackgroundDrawable(new ColorDrawable(Color.WHITE));
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
exit();
return true;
}
return super.onKeyDown(keyCode, event);
}
public void exit() {
if ((System.currentTimeMillis() - mExitTime) > 2000) {
Toast.makeText(MainActivity.this, "再点一次退出", Toast.LENGTH_SHORT).show();
mExitTime = System.currentTimeMillis();
} else {
//关闭活动退出
// this.finish();
//进入后台
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(homeIntent);
}
}
}
Manifests:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.coldstart" >
<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" >
<activity android:name=".SplshActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"
android:screenOrientation="portrait"
android:configChanges="orientation|keyboardHidden|screenSize"
/>
</application>
</manifest>
效果图
欢迎页的操纵我是设置这个页面为优先启动,然后每次跳转到MainActivity中,且这个欢迎页不能关了,然后在MainActivity的物理键返回的方法重写直接退出APP或者是应用进程进入后台.
优化启动的耗时操作,减少启动时间
- 在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化;
- 一些数据预取放在异步线程中,可以采取Callable实现。比如——对于sp的初始化,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度,对于这个还是需要放在异步线程中处理。
- 对于MainActivity活动,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的嵌套层次,考虑StubView的延迟加载策略;
- 当然在Activity中的onCreate、onStart、onResume方法中避免做耗时操作,尽量采用异步操作。