打开app,往往会先白屏停顿一下后再进入启动页面(Splash)
在启动Acitivty的onCreate()方法里面,系统先绘制窗体,再执行setContentView(R.layout.activity_splash),窗体绘制后布局资源还没加载,于是就使用默认背景色。
如果主题使用Theme.AppCompat.Light(亮色系)则显示白色闪屏,若使用ThemeOverlay.AppCompat.Dark(暗色系)则显示黑色闪屏
步骤1:把启动图bg_splash设置为窗体背景,避免刚刚启动App的时候出现,黑/白屏
步骤2 :设置为背景bg_splash显示的时候,后台负责加载资源,同时去下载广告图,广告图下载成功或者超时的时候显示SplashActivity的真实样子
步骤3:随后进入MainAcitivity
每个Android App都在一个独立空间里, 意味着其运行在一个单独的进程中, 拥有自己的VM, 被系统分配一个唯一的user ID.
Android App由很多不同组件组成, 这些组件还可以启动其他App的组件. 因此, Android App并没有一个类似程序入口的main()方法.
Android进程与Linux进程一样. 默认情况下, 每个apk运行在自己的Linux进程中. 另外, 默认一个进程里面只有一个线程—主线程. 这个主线程中有一个Looper实例, 通过调用Looper.loop()从Message队列里面取出Message来做相应的处理.
进程在其需要的时候被启动. 任意时候, 当用户或者其他组件调取你的apk中的任意组件时, 如果你的apk没有运行, 系统会为其创建一个新的进程并启动. 通常, 这个进程会持续运行直到被系统杀死.
用户点击Home上的一个App图标, 启动一个应用时:
Click事件会调用startActivity(Intent), Launcer会通过Binder IPC机制, 最终通知ActivityManagerService(AMS是Android系统的一个进程,用于管理系统四大组件运行状态)去启动Activity。
该Service会执行如下操作:
第一步:通过PackageManager的resolveIntent()收集这个intent对象的指向信息.指向信息被存储在一个intent对象中
第二步:通过grantUriPermissionLocked()方法来验证用户是否有足够的权限去调用该intent对象指向的Activity.
如果有权限, ActivityManagerService会检查并在新的task中启动目标activity
第三步:检查这个进程的ProcessRecord是否存在了.若存在,直接启动activity,如果ProcessRecord是null, ActivityManagerService会创建新的进程来实例化目标activity
第四步:ActivityManagerService调用startProcessLocked()方法来创建新的进程, 该方法会通过前面讲到的socket通道传递参数给Zygote进程. Zygote孵化自身, 并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid
ActivityThread随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环
第五步:将进程和指定的Application绑定起来
第六步:在该存在的进程中调用realStartActivity()来启动Activity
App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动.
冷启动的流程即为第2节所描述的App启动流程的全过程, 需要创建App进程, 加载相关资源, 启动Main Thread, 初始化首屏Activity等.
在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至首屏Activity完全启动.
冷启动时间线:
热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户.
类同与冷启动, 在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至activity渲染完毕.
介于冷启动和热启动之间, 一般来说在以下两种情况下发生:
a.用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建.
b.用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复.
通过三种启动状态的相关描述, 可以看出我们要做的启动优化其实就是针对冷启动. 热启动和温启动都相对较快.
根据冷启动的时间图, 可以看出, 对于App来说, 我们可以控制的启动时间线的点无外乎:
(3.1)Application的onCreate
(3.2)首屏Activity的渲染
而我们现在的App动不动集成了很多第三方服务, 启动时需要检查广告, 注册状态等等一系列接口都是在Application的onCreate或是首屏的onCreate中做的.
因为这个App集成了Bugly, Push, Feedback等服务, 所以Application的onCreate有很多第三方平台的初始化工作:
public class GithubApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
// init logger.
AppLog.init();
// init crash helper
CrashHelper.init(this);
// init Push
PushPlatform.init(this);
// init Feedback
FeedbackPlatform.init(this);
// init Share
SharePlatform.init(this);
// init Drawer image loader
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
public void set(ImageView imageView, Uri uri, Drawable placeholder) {
ImageLoader.loadWithCircle(GithubApplication.this, uri, imageView);
}
});
}
}
接下来我们结合我们上文的理论知识, 和介绍的Traceview工具, 来分析下Application的onCreate耗时.
在onCreate开始和结尾打上trace.
Debug.startMethodTracing("GithubApp");
...
Debug.stopMethodTracing();
运行程序, 会在sdcard上生成一个"GithubApp.trace"的文件.
注意: 需要给程序加上写存储的权限:
通过adb pull将其导出到本地
adb pull /sdcard/GithubApp.trace ~/temp
打开DDMS分析trace文件
分析trace文件
在下方的方法区点击"Real Time/Call", 按照方法每次调用耗时降序排.
耗时超过500ms都是值得注意的.
看左边的方法名, 可以看到耗时大户就是我们用的几大平台的初始化方法, 特别是Bugly, 还加载native的lib, 用ZipFile操作等.
点击每个方法, 可以看到其父方法(调用它的)和它的所有子方法(它调用的).
点击方法时, 上方的该方法执行时间轴会闪动, 可以看该方法的执行线程及相对时长.
将第三方SDK初始化放在一个单独的线程中处理。这里用了一个InitializeService的IntentService来做初始化工作.(IntentService不同于Service, 它是工作在后台线程的.)
InitializeService.java代码如下:
public class InitializeService extends IntentService {
private static final String ACTION_INIT_WHEN_APP_CREATE = "com.anly.githubapp.service.action.INIT";
public InitializeService() {
super("InitializeService");
}
public static void start(Context context) {
Intent intent = new Intent(context, InitializeService.class);
intent.setAction(ACTION_INIT_WHEN_APP_CREATE);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {
performInit();
}
}
}
private void performInit() {
AppLog.d("performInit begin:" + System.currentTimeMillis());
// init Drawer image loader
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
public void set(ImageView imageView, Uri uri, Drawable placeholder) {
ImageLoader.loadWithCircle(getApplicationContext(), uri, imageView);
}
});
// init crash helper
CrashHelper.init(this.getApplicationContext());
// init Push
PushPlatform.init(this.getApplicationContext());
// init Feedback
FeedbackPlatform.init(this.getApplication());
// init Share
SharePlatform.init(this.getApplicationContext());
AppLog.d("performInit end:" + System.currentTimeMillis());
}
}
GithubApplication的onCreate改成:
public class GithubApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
// init logger.
AppLog.init();
InitializeService.start(this);
}
}
步骤1:制作一个主题,带上背景
步骤2:将一个不渲染布局的Activity作为启动屏,并加上主题
public class LogoSplashActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注意, 这里并没有setContentView, 单纯只是用来跳转到相应的Activity.
// 目的是减少首屏渲染
if (AppPref.isFirstRunning(this)) {
IntroduceActivity.launch(this);
}
else {
MainActivity.launch(this);
}
finish();
}
}
(1)Application的onCreate中不要做太多事情.
(2)首屏Activity尽量简化.
(3)善用性能分析工具分析.