介绍
冷启动指用户点APP图标至启动页调onCreate()方法阶段。
打开一Activity,若该Activity所属Application没启动,则系统为该Activity创建一进程(即zygote进程首先创建一新进程运行APP)。每创建一进程调一次Application,故Application之onCreate()方法可能被调多次。进程创建和初始化势必消耗时间,该阶段内WindowManager先加载APP主题样式中窗口背景windowBackground
作为预览元素,即Preview Window(预览窗口),然后加载真正布局。该时间过长且默认背景黑或白色便给用户造成错觉,APP卡顿不流畅,影响用户体验。
思路
点APP图标后所见黑或白屏是界面渲染前第一帧,故对Theme中windowBackground
设置即可。
方案一
窗口背景Logo
<!--窗口背景-->
<item name="android:windowBackground">@drawable/splash_page</item>
方案二
窗口背景透明,用户点APP图标后不立即进入APP而桌面停留片刻。其实此时APP已启动,只是Theme中windowBackground
透明。
<!--窗口背景-->
<item name="android:windowBackground">@color/transparent</item>
<!--窗口透明-->
<item name="android:windowIsTranslucent">true</item>
方案三
禁止加载Preview Window
<item name="android:windowDisablePreview">true</item>
方案四
窗口背景设能解析出图片资源的XML文件
drawable
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<item>
<!--android:gravity="center"仅显图中部分-->
<bitmap
android:gravity="fill"
android:src="@drawable/splash" />
</item>
</layer-list>
styles
<resources>
<!-- Base Application Theme -->
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<!--去除ActionBar-->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<!--标题栏颜色-->
<item name="colorPrimary">@color/colorPrimary</item>
<!--状态栏颜色 5.0+有效-->
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<!--控件选中颜色-->
<item name="colorAccent">@color/colorPrimaryDark</item>
<!--窗口背景-->
<!--<item name="android:windowBackground">@drawable/preview_window</item>-->
<!--窗口透明-->
<!--<item name="android:windowIsTranslucent">true</item>-->
<!--页面切换动画-->
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<!--取消字体默认大写-->
<item name="android:textAllCaps">false</item>
<!--更改系统状态栏字体颜色 6.0+-->
<!--<item name="android:windowLightStatusBar">true</item>-->
<item name="android:windowDisablePreview">true</item>
</style>
<style name="AppTheme" parent="AppBaseTheme"></style>
<style name="AnimationActivity" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/slide_in_left</item>
<item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_in_right</item>
<item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
</style>
<style name="Theme.Splash" parent="AppTheme">
<item name="android:windowBackground">@drawable/preview_window</item>
<item name="android:windowFullscreen">true</item>
</style>
清单文件
<activity
android:name=".SplashActivity"
android:theme="@style/Theme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
布局
<?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"
android:background="@color/background"
android:orientation="vertical">
<ImageView
android:id="@+id/ivBack"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/ivTarget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/splash_icon" />
</RelativeLayout>
主代码
package com.self.zsp.rd;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.ViewPropertyAnimation;
import base.BaseActivity;
/**
* @decs: 闪屏
* @date: 2017/11/6 17:16
* @version: v 1.0
*/
public class SplashActivity extends BaseActivity {
/**
* 控件
*/
private ImageView imgBack;
private ImageView ivTarget;
/**
* 属性动画
*/
ViewPropertyAnimation.Animator animator = new ViewPropertyAnimation.Animator() {
@Override
public void animate(View view) {
view.setAlpha(0f);
ObjectAnimator objAnimator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
objAnimator.setDuration(2000);
objAnimator.start();
}
};
@Override
protected void initContentView() {
// 处理PreviewWindow背景图,避免图片一直占用内存,过渡绘制
getWindow().setBackgroundDrawable(null);
// 初始化状态栏
initStatus();
setContentView(R.layout.activity_splash);
}
@Override
protected void stepUI() {
imgBack = findViewById(R.id.ivBack);
ivTarget = findViewById(R.id.ivTarget);
}
@Override
protected void initConfiguration() {
}
@Override
protected void initData() {
}
@Override
protected void startLogic() {
ivTarget.post(new Runnable() {
@Override
public void run() {
Glide.with(SplashActivity.this).load(R.drawable.splash).animate(animator).into(imgBack);
startAnimation();
}
});
}
@Override
protected void setListener() {
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
}
/**
* 初始化状态栏
*/
private void initStatus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
View decoderView = getWindow().getDecorView();
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decoderView.setSystemUiVisibility(option);
getWindow().setStatusBarColor(Color.TRANSPARENT);
} else {
WindowManager.LayoutParams localLayoutParams = getWindow().getAttributes();
localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);
}
}
/**
* 动画执行
*/
private void startAnimation() {
int imgHeight = ivTarget.getHeight() / 5;
int height = getWindowManager().getDefaultDisplay().getHeight();
int dy = (height - imgHeight) / 2;
AnimatorSet set = new AnimatorSet();
ObjectAnimator animatorTranslate = ObjectAnimator.ofFloat(ivTarget, "translationY", 0, dy);
ObjectAnimator animatorScaleX = ObjectAnimator.ofFloat(ivTarget, "ScaleX", 1f, 0.2f);
ObjectAnimator animatorScaleY = ObjectAnimator.ofFloat(ivTarget, "ScaleY", 1f, 0.2f);
ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(ivTarget, "alpha", 1f, 0.5f);
set.play(animatorTranslate).with(animatorScaleX).with(animatorScaleY).with(animatorAlpha);
set.setDuration(1200);
set.setInterpolator(new AccelerateInterpolator());
set.start();
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
ivTarget.postDelayed(new Runnable() {
@Override
public void run() {
startActivity(new Intent(SplashActivity.this, LoginActivity.class));
SplashActivity.this.finish();
}
}, 3000);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
}
优化
如上方法可实现APP秒开效果但非真实速度。Activity创建过程会经一系列Framework层操作,日常开发会重写Application类,然后在Application进行一些初始化操作,比如存放用户标识的静态化TOKEN、第三方SDK初始化等。
- 尽量不让Application参与业务逻辑操作
- 尽量不在Application中进行耗时操作。比如查询一系列文件夹或文件,这些I/O操作应在必要使用时创建或利用数据库操作。
- 尽量不以静态变量方式在Application保存数据等。
- 尽量减少布局复杂性、布局深度,最好不超三层嵌套。因在View绘制过程中测量相当耗费性能。
注意
预览窗口全屏则
android:windowBackground
与android:windowFullscreen
结合使用,即剥离并设给SplashActivity。如下:<style name="Theme.Splash" parent="AppTheme"> <item name="android:windowBackground">@drawable/preview_window</item> <item name="android:windowFullscreen">true</item> </style>
<activity android:name=".SplashActivity" android:theme="@style/Theme.Splash"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
Preview Window背景图不做处理会致图一直存于内存并过渡绘制,故进入欢迎页时需把背景图置空或将主题重置默认。如下:
getWindow().setBackgroundDrawable(null);
setTheme(R.style.AppTheme);