1 App的启动
1.1 启动方式
通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动。
(1)冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
(2)热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
1.2 特点
(1)冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
(2)热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。
上面说的启动是点击app的启动图标来启动的,而另外一种方式是进入最近使用的列表界面来启动应用,这种不应该叫启动,应该叫恢复。
1.3 应用启动的流程
由图分析可总结Activity大概启动流程:启动虚拟机—>启动AMS —>通过Zygote创建ApplicationProcess进程–>Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
2 测量一个应用的启动时间
2.1 测量方法1
adb shell am start -W [PackageName]/[PackageName.MainActivity]
执行成功后将返回三个测量到的时间:
(1)ThisTime:指当前指定的MainActivity的启动时间
(2)TotalTime:整个应用的启动时间,Application+Activity的使用的时间。
(3)WaitTime:一般比TotalTime大点,包括系统影响的耗时
2.2 测量方法2
我的另一篇文章:性能优化之卡顿分析-计算并优化内存抖动和耗时操作–第4部分 如何使用Traceview计算某个类的耗时+第5部分 利用loop()中打印的日志检测应用中的UI卡顿
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Debug.startMethodTracing("textracing");//开始
sleep();
login();
Debug.stopMethodTracing();//结束
}
2.3 测量方法3
log会打印一串log如下(Displayed过滤):
ActivityManager﹕ Displayed xxx.xxx.xxx/TestActivity: +1s272ms (total +3s843ms)
Android Activity页面加载时间性能分析,以及改进要点
2.3 测量结果
adb shell am start -W com.guesslive.caixiangji/com.guesslive.caixiangji.activity.MainActivity
(1)优化前
Activity: com.guesslive.caixiangji/.activity.MainActivity
ThisTime: 1363
TotalTime: 1363
WaitTime: 1391
Complete
(2)Application优化后
Activity: com.guesslive.caixiangji/.activity.BootActivity
ThisTime: 521
TotalTime: 521
WaitTime: 547
Complete
(3)MainActivity&SplashFragment 优化后
Activity: com.guesslive.caixiangji/.activity.MainActivity
ThisTime: 340
TotalTime: 340
WaitTime: 366
Complete
3 减少应用启动时的耗时
主要是在Application初始化 + MainActivity的界面加载绘制时间。
(1)不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操作。使用IntentService代替初始化工作。
(2)MainActivity,由于用户只关心最后的显示的这一帧,对我们的布局的层次要求要减少,自定义控件的话测量、布局、绘制的时间。不要在onCreate、onStart、onResume当中做耗时操作。
(3)对于SharedPreference的初始化。因为他初始化的时候是需要将数据全部读取出来放到内存当中。
(4)SplashActivity和MainActivity合为一个优化。
(5)合理使用延迟加载DelayLoad。达到效果:应用已经启动并加载完成,界面已经显示出来了,然后我们再去做其他的事情
4 综合方案
4.1 Application
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
InitializeService.start(this);//启动服务执行耗时操作
}
}
/**
* 启动初始化Service
*/
public class InitializeService extends IntentService {
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
public static final String ACTION_INIT_WHEN_APP_CREATE = "com.guesslive.caixiangji.service.action.app.create";
public static final String EXTRA_PARAM = "com.guesslive.caixiangji.service.extra.PARAM";
public InitializeService() {
super("InitializeService");
}
/**
* 启动调用
* @param context
*/
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(EXTRA_PARAM);
}
}
}
/**
* 启动初始化操作
*/
private void performInit(String param) {
initImageLoader();//初始化图片加载控件
initRealm();//初始化Realm数据库
initUser();//初始化用户(Realm数据库)
initPush();//初始化推送
initTuSdk();//初始化图sdk
initQiNiu();//初始化七牛
initQiyu();//网易七鱼
initLogger();//注释启动,打开屏蔽打印
}
}
4.2 MainActivity&SplashFragment
(1)MainActivity.java
public class MainActivity extends FragmentActivity {
private Handler mHandler = new Handler();
private SplashFragment splashFragment;
private ViewStub viewStub;
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewStub = (ViewStub)findViewById(R.id.content_viewstub);
//首先加载并显示splash页面
splashFragment = new SplashFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame, splashFragment);
transaction.commit();
//1.判断当窗体加载完毕的时候,立马再加载真正的布局进来
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
// 开启延迟加载
mHandler.post(new Runnable() {
@Override
public void run() {
viewStub.inflate();//将viewstub加载进来,记载完毕后控件使用正常
// iv = (ImageView) findViewById(R.id.iv);
}
} );
}
});
//2.判断当窗体加载完毕的时候执行,延迟一段时间做动画。
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
// 开启延迟加载,实现fragment里面的动画效果
mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);
// mHandler.post(new DelayRunnable());//不开启延迟加载
}
});
//3.同时进行异步加载数据
}
static class DelayRunnable implements Runnable{
private WeakReference<Context> contextRef;
private WeakReference<SplashFragment> fragmentRef;
public DelayRunnable(Context context, SplashFragment f) {
contextRef = new WeakReference<Context>(context);
fragmentRef = new WeakReference<SplashFragment>(f);
}
@Override
public void run() {
// 移除splash页面
if(contextRef!=null){
SplashFragment splashFragment = fragmentRef.get();
if(splashFragment==null){
return;
}
FragmentActivity activity = (FragmentActivity) contextRef.get();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.remove(splashFragment);
transaction.commit();
}
}
}
@Override
protected void onResume() {
super.onResume();
}
}
(2)activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.applicationstartoptimizedemo.MainActivity" >
<ViewStub
android:id="@+id/content_viewstub"
android:layout="@layout/activity_main_viewstub"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- android:background="@mipmap/bg_boot" 设置背景图-->
</FrameLayout>
</RelativeLayout>
(3)activity_main_viewstub.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ProgressBar
android:id="@+id/progressBar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<ImageView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitStart"
android:src="@drawable/content" />
</FrameLayout>
(4)SplashFragment.java
public class SplashFragment extends Fragment {
@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_splash, container,false);
}
}
4.3 给Window加上背景避免白屏
(1)bg_splash.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 底层白色 -->
<item android:drawable="@color/white" />
<!-- 顶层Logo居中 -->
<item>
<bitmap
android:gravity="center"
android:src="@drawable/ic_github" />
</item>
</layer-list>
(2)SplashTheme
<style name="SplashTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/bg_splash</item>
</style>
(3)ActivityManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guesslive.caixiangji">
<application
android:name=".application.MainApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".activity.MainActivity"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.InitializeService"
android:exported="false"/>
</application>
</manifest>
5 结语
实际场景可能远比这个复杂,这只是其中一种分析思路,还有其他思路欢迎私信。