您的位置:首页 > 移动开发

Android性能优化系列之App启动优化

2017-02-22 00:23 561 查看

Android性能优化系列之App启动优化

标签:
性能优化安卓性能优化app启动优化

2017-02-22 00:23
2924人阅读 评论(2)
收藏
举报

本文章已收录于:


分类:
性能优化(5)




作者同类文章X

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]
应用的启动方式
App的启动过程
启动时间统计
利用TraceView分析启动时间
启动页优化
启动优化一些思路

Android性能优化系列之布局优化

Android性能优化系列之内存优化

Android性能优化系列之apk瘦身

应用的启动速度缓慢是我们在开发过程中经常会遇到的问题,比如启动缓慢导致的黑屏,白屏问题,本篇博客就将介绍App启动优化的相关知识。

应用的启动方式

通常来说,启动方式分为两种:冷启动和热启动。

1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。

2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。

App的启动过程

本文所指的优化针对冷启动。简单解释一下App的启动过程:

1.点击Launcher,启动程序,通知ActivityManagerService

2.ActivityManagerService通知zygote进程孵化出应用进程,分配内存空间等

3.执行该应用ActivityThread的main()方法

4.应用程序通知ActivityManagerService它已经启动,ActivityManagerService保存一个该应用的代理对象,ActivityManagerService通过它可以控制应用进程

5.ActivityManagerService通知应用进程创建入口的Activity实例,执行它的生命周期

启动过程中Application和入口Activity的生命周期方法按如下顺序调用:

1.Application 构造方法

2.attachBaseContext()

3.onCreate()

4.入口Activity的对象构造

5.setTheme() 设置主题等信息

6.入口Activity的onCreate()

7.入口Activity的onStart()

8.入口Activity的onResume()

9.入口Activity的onAttachToWindow()

10.入口Activity的onWindowFocusChanged()

启动时间统计

理论上来说当回调到入口Activity的onResume()方法时,App就算正式启动了,但是从这种意义上其实是不严谨的,因为在这个时间点

实际只完成了主题信息以及view树建立等。而view绘制的真正过程measure layout draw过程并没有,所以不是真正意义上的“可见”。 在onWindowFocusChanged()回调处更接近事实。所以我们确认启动时间可以从Application的构造方法记录开始时间,然后到入口Activity的onWindowFocusChanged()再记录一个时间,两者之差就是App的启动时间。当然这是比较笨的方法,adb命令里有相应的记录命令 :adb shell am start -W 包名/入口类全路径名

adb shell am start -W [PackageName]/[PackageName.MainActivity]
1


1
执行成功后将返回三个测量到的时间:



这里面涉及到三个时间,ThisTime、TotalTime 和 WaitTime。WaitTime 是 startActivityAndWait 这个方法的调用耗时,ThisTime 是指调用过程中最后一个 Activity 启动时间到这个 Activity 的 startActivityAndWait 调用结束。TotalTime 是指调用过程中第一个 Activity 的启动时间到最后一个 Activity 的 startActivityAndWait 结束。如果过程中只有一个 Activity ,则
TotalTime 等于 ThisTime。

利用TraceView分析启动时间

在onCreate开始和结尾打上trace.

Debug.startMethodTracing("TestApp");
...
Debug.stopMethodTracing();
1
2
3


1
2
3
运行程序, 会在sdcard上生成一个”TestApp.trace”的文件.

注意: 需要给程序加上写存储的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
1


1
通过adb pull将其导出到本地

adb pull /sdcard/TestApp.trace ~/testSpeed.trace
1


1
打开DDMS分析trace文件,会出现以下的界面





展开后,大多数有以下两个类别:

Parents:调用该方法的父类方法

Children:该方法调用的子类方法

如果该方法含有递归调用,可能还会多出两个类别:

Parents while recursive:递归调用时所涉及的父类方法

Children while recursive:递归调用时所涉及的子类方法

开发者最关心的数据有:

很重要的指标:Calls + Recur Calls / Total , 最重要的指标: Cpu Time / Call

因为我们最关心的有两点,一是调用次数不多,但每次调用却需要花费很长时间的函数。这个可以从Cpu Time / Call反映出来。另外一个是那些自身占用时间不长,但调用却非常频繁的函数。这个可以从Calls + Recur Calls / Total 反映出来。

然后我们可以通过这样的分析,来查看启动的时候,哪些进行了耗时操作,然后进行相应的处理,比如能不在主线程中做的,放入子线程中,还有一些懒加载处理,比如有些Application中做了支付SDK的初始化,用户又不会一打开App就要支付,对其进行懒加载。

启动页优化

平时我们在开发App时,都会设置一个启动页SplashActivity,然后2或3秒后,并且SplashActivity里面可以去做一些MainActivity的数据的预加载,然后需要通过意图传到MainActivity。

优点:启动速度有所加快

缺点:最终还是要进入首页,在进入首页的时候,首页复杂的View渲染以及必须在UI线程执行的业务逻辑,仍然拖慢了启动速度。启动页简单执行快,首页复杂执行慢,前轻后重。

思路:能否在启动页的展示的同时,首页的View就能够被加载,首页的业务逻辑就能够被执行?

优化方向:

把SplashActivity改成SplashFragment,应用程序的入口仍然是MainActivity,在MainActivity中先展示SplashFragment,当SplashFragment显示完毕后再将它remove,同时在SplashFragment的2S的友好时间内进行网络数据缓存,在窗口加载完毕后,我们加载activity_main的布局,考虑到这个布局有可能比较复杂,耽误View的解析时间,采用ViewStub的形式进行懒加载。这样一开始只要加载SplashFragment所展示的布局就Ok了。

代码:

MainActivity .Java

public class MainActivity extends FragmentActivity {

private  MyHandler mHandler=new MyHandler(this);

public static final String TAG="MainActivity";

private ProgressBar mNetLoadingBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate");
setContentView(R.layout.activity_main);
final SplashFragment splashFragment = new SplashFragment();
final ViewStub mainLayout = (ViewStub) findViewById(R.id.content_viewstub);
//1、一上来首先显示启动页面
FragmentManager supportFragmentManager = getSupportFragmentManager();
if (supportFragmentManager != null) {
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
if (fragmentTransaction != null) {
fragmentTransaction.replace(R.id.container, splashFragment);
fragmentTransaction.commit();
}
}

//2、如果主页有网络等耗时操作,可以现在就开始
new Thread(new Runnable() {
@Override
public void run() {
//耗时3500
SystemClock.sleep(3000);
mHandler.sendEmptyMessage(0);
}
}).start();

//3、渲染完毕后,立刻加载主页布局
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
Log.d(TAG," getWindow().getDecorView().post");
View mainView = mainLayout.inflate();
initView(mainView);
}
});

//4、 启动页有动画,延迟一下,播放完动画,执行remove
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
mHandler.postDelayed(new DelayRunnableImpl(MainActivity.this, splashFragment), 2000);
}
});
}

@Override
protected void onResume() {
super.onResume();
Log.d(TAG,"onResume");
}

/**
* 初始化主页View
*/
private void initView(View pMainView) {
if (pMainView != null) {
mNetLoadingBar = (ProgressBar) pMainView.findViewById(R.id.progressbar);
mNetLoadingBar.setVisibility(View.VISIBLE);
}
}

private static class MyHandler extends Handler {

private WeakReference wRef;

private MyHandler(MainActivity pActivity) {
this.wRef = new WeakReference(pActivity);
}

@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = wRef.get();
if (mainActivity != null) {
mainActivity.mNetLoadingBar.setVisibility(View.GONE);
}
}
}

private class DelayRunnableImpl implements Runnable {
WeakReference contextWref;
WeakReference fragmentWref;

private DelayRunnableImpl(Context pContext, Fragment pFragment) {
this.contextWref = new WeakReference<>(pContext);
this.fragmentWref = new WeakReference<>(pFragment);
}

@Override
public void run() {
FragmentActivity context = (FragmentActivity) contextWref.get();
if (context != null) {
FragmentManager supportFragmentManager = context.getSupportFragmentManager();
if (supportFragmentManager != null) {
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
SplashFragment fragment = (SplashFragment) fragmentWref.get();
if (fragment != null) {
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();
}
}
}
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
SplashFragment .java

public class SplashFragment extends Fragment {

public SplashFragment() {

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View ret = inflater.inflate(R.layout.fragment_splash, container, false);
initView(ret);
return ret;
}

private void initView(View ret) {
if (ret != null) {
ImageView imageView = (ImageView) ret.findViewById(R.id.laucher_logo);
playAnimator(imageView);
}
}

private void playAnimator(ImageView pView) {
if (pView != null) {
PropertyValuesHolder pvhA = PropertyValuesHolder.ofFloat("alpha", 1f, 0.7f, 0.1f);
//  PropertyValuesHolder pvhR= PropertyValuesHolder.ofFloat("rotationX", 0.0F, 360.0F);
ObjectAnimator.ofPropertyValuesHolder(pView, pvhA).setDuration(2000).start();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
activity_mian.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zhangwan.wj.com.myshare.activity.MainActivity">

<ViewStub
android:id="@+id/content_viewstub"
android:layout="@layout/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fragment.xml

<FrameLayout 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="zhangwan.wj.com.myshare.fragment.SplashFragment">

<ImageView
android:id="@+id/laucher_logo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/launch" />

</FrameLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13


1
2
3
4
5
6
7
8
9
10
11
12
13
采用以上方式后,可以发现启动速度得到很大的改观。

启动优化一些思路

1、避免启动页UI的过度绘制,减少UI重复绘制时间,打开设置中的GPU过度绘制开关,界面整体呈现浅色,特别复杂的界面,红色区域也不应该超过全屏幕的四分之一;

2、主线程中的所有SharedPreference能否在非UI线程中进行,SharedPreferences的apply函数需要注意,因为Commit函数会阻塞IO,这个函数虽然执行很快,但是系统会有另外一个线程来负责写操作,当apply频率高的时候,该线程就会比较占用CPU资源。类似的还有统计埋点等,在主线程埋点但异步线程提交,频率高的情况也会出现这样的问题。

3、对于首次启动的黑屏问题,对于“黑屏”是否可以设计一个.9图片替换掉,间接减少用户等待时间。

4、对于网络错误界面,友好提示界面,使用ViewStub的方式,减少UI一次性绘制的压力。

5、通过下面这种方式进行懒加载

getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
1
2
3
4
5
6


1
2
3
4
5
6
6、Multidex的使用,也是拖慢启动速度的元凶,必须要做优化



顶 4 踩 0

上一篇Android设计模式学习之观察者模式

下一篇CoordinatorLayout使用全解析

我的同类文章

性能优化(5)

http://blog.csdn.net
Android性能优化系列之apk瘦身2017-02-09阅读2565

Android性能优化系列之布局优化2017-01-15阅读3797

Android中常见的内存泄露2016-08-16阅读847

Android性能优化系列之内存优化2017-01-21阅读2284

手把手带你实现Android增量更新2016-11-13阅读1166
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: