Android性能优化系列之App启动优化
来源:互联网 发布:php变量引用 编辑:程序博客网 时间:2024/05/01 05:06
Android性能优化系列之App启动优化
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
- 应用的启动方式
- 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使用全解析
我的同类文章
- •Android性能优化系列之apk瘦身2017-02-09
- •Android性能优化系列之布局优化2017-01-15
- •Android中常见的内存泄露2016-08-16
- •Android性能优化系列之内存优化2017-01-21
- •手把手带你实现Android增量更新2016-11-13
- Android性能优化系列之App启动优化
- Android性能优化系列之App启动优化
- Android 性能优化系列之 App 启动优化
- Android性能优化系列之App启动优化
- Android性能优化系列之App启动优化
- Android性能优化之App应用启动分析与优化
- Android性能优化之App应用启动分析与优化
- Android性能优化(二):APP启动优化
- Android App 性能优化之图片优化
- Android app 性能优化之视图优化
- Android性能优化之app启动时间测量
- Android性能优化之APP启动白屏
- Android性能优化系列之布局优化
- Android性能优化系列之布局优化
- Android性能优化系列之渲染优化
- Android性能优化系列之电量优化
- Android性能优化第(九)篇---App启动速度优化之启动页设计
- Android App 性能优化系列结语篇
- 机器学习->监督学习->logistic回归,softMax回归
- 适合设计电话号码的一款字体
- 设计模式——简单工厂模式
- 算法笔记_062-蓝桥杯练习 最小乘积(基本型)(Java)
- ajax获取json数据,ajax发生异常抛出错误
- Android性能优化系列之App启动优化
- 欢迎使用CSDN-markdown编辑器
- [POJ3678] Katu Puzzle
- C#设计模式总结
- java枚举类型enum的使用
- 【Android】简单实用的图片裁剪库
- xxx cannot be resolved to a type 错误解决方法
- ExecutorService和BlockingQueue的小例子
- 二叉树的几种遍历(递归,非递归)JAVA实现