做好性能优化需求,就要全面分析并优化性能不达标的低质量代码。
比如,你人工找到了一个觉得耗时的方法,但是通常可能这只是多个问题中的一个凸显问题,很巧被你发现了,做到全面分析就是为了避免片面解决问题。
还有,你可能在发现问题后对问题进行了处理,但是通常不知道剪短了多少耗时算是优化了这个低质量代码,那么就需要有个衡量或是对比尺子来评判你优化的内容是否已达标。
要对性能优化做到全面且达标,单靠个人经验和片面分析是完不成的。正所谓,工欲善其事必先利其器!
下面讲一下个人在做首页性能优化时用到的一些工具:
1. 严格模式
如下是集成严格模式StrictMode的代码块:
public class XXXApplication extends Application {
@Override
public void onCreate() {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectResourceMismatches()//最低版本为API23 发现资源不匹配
.detectCustomSlowCalls()//发现UI线程调用的哪些方法执行得比较慢
.detectDiskReads()//检测在UI线程读磁盘操作
.detectDiskWrites()//检测UI线程写磁盘操作
.detectNetwork() //检测在UI线程执行网络操作
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectActivityLeaks()//最低版本API11 用户检查 Activity 的内存泄露情况
.detectCleartextNetwork()//最低版本为API23 检测明文的网络
.detectFileUriExposure()//最低版本为API18 检测file://或者是content://
.detectLeakedClosableObjects()//最低版本API11 资源没有正确关闭时触发
.detectLeakedRegistrationObjects()//最低版本API16 BroadcastReceiver、ServiceConnection是否被释放
.detectLeakedSqlLiteObjects()//最低版本API9 资源没有正确关闭时回触发
.setClassInstanceLimit(HomeActivity.class, 2)//设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露
.penaltyLog()
.build());
}
super.onCreate();
}
}
通过Android的“严格模式”在日志中排查主线程中是否有io操作,其中发现三个异常问题,这也是QA很难发现但确实存在的问题。
问题一,切换到后台toast提示加载字体出现主线程io耗时操作。
2019-07-30 16:29:16.832 22351-22351/? D/StrictMode: StrictMode policy violation; ~duration=75 ms: android.os.strictmode.DiskReadViolation
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1504)
at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:251)
at java.io.File.exists(File.java:815)
at android.graphics.Typeface.isAndroidFont(Typeface.java:215)
at android.widget.Toast.change2VivoFont(Toast.java:399)
at android.widget.Toast.makeText(Toast.java:313)
at android.widget.Toast.makeText(Toast.java:290)
at com.wuba.commons.toast.ToastCompat.makeText(ToastCompat.java:51)
at com.wuba.commons.utils.WubaToast.makeText(WubaToast.java:21)
at com.wuba.commons.utils.ToastUtils.show(ToastUtils.java:50)
at com.wuba.commons.utils.ToastUtils.showToast(ToastUtils.java:46)
at com.wuba.application.GroundMonitor.onBackground(GroundMonitor.java:33)
at com.wuba.application.GroundDetectLifecycle.onActivityStopped(GroundDetectLifecycle.java:50)
at android.app.Application.dispatchActivityStopped(Application.java:276)
at android.app.Activity.onStop(Activity.java:1930)
at android.support.v4.app.FragmentActivity.onStop(FragmentActivity.java:636)
at android.support.v7.app.AppCompatActivity.onStop(AppCompatActivity.java:184)
at com.wuba.activity.BaseAppCompatActivity.onStop(BaseAppCompatActivity.java:79)
at com.wuba.home.activity.HomeActivity.onStop(HomeActivity.java:881)
at android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1436)
at android.app.Activity.performStop(Activity.java:7478)
at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:4443)
at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:4421)
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4496)
at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:41)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2051)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7104)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
public class GroundMonitor implements GroundDetectLifecycle.GroundDetectListener {
@Override
public void onBackground() {
ToastUtils.showToast(mContext, "已进入后台运行");
...
}
}
问题二,在主线程读取缓存的背景图片文件。
2019-07-30 17:18:41.383 2348-2348/com.wuba D/StrictMode: StrictMode policy violation; ~duration=37 ms: android.os.strictmode.DiskReadViolation
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1504)
at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:251)
at java.io.File.exists(File.java:815)
at com.wuba.commons.picture.ImageLoaderUtils.exists(ImageLoaderUtils.java:381)
at com.wuba.homepage.HomePageMVPPresenter.loadBackgroundImage(HomePageMVPPresenter.java:99)
at com.wuba.homepage.HomePageMVPFragment.onResume(HomePageMVPFragment.java:253)
at android.support.v4.app.Fragment.performResume(Fragment.java:2498)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1501)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3269)
at android.support.v4.app.FragmentManagerImpl.dispatchResume(FragmentManager.java:3241)
at android.support.v4.app.FragmentController.dispatchResume(FragmentController.java:223)
at android.support.v4.app.FragmentActivity.onResumeFragments(FragmentActivity.java:538)
at android.support.v4.app.FragmentActivity.onPostResume(FragmentActivity.java:527)
at android.support.v7.app.AppCompatActivity.onPostResume(AppCompatActivity.java:172)
at android.app.Activity.performResume(Activity.java:7428)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4052)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4092)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2051)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7104)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
@Override
public void loadBackgroundImage() {
String homeBgUrl = WubaPersistentUtils.getHomeCityBuildingUrl(mContext);
Uri homeBgUri = Uri.parse(homeBgUrl);
if (ImageLoaderUtils.getInstance().exists(homeBgUri)) {
String realPath = ImageLoaderUtils.getInstance().getRealPath(homeBgUri);
Bitmap bitmap = PicUtils.makeNormalBitmap(realPath, -1, -1, Bitmap.Config.ARGB_8888, false);
mIView.setBackgroundImage(bitmap);
}
}
问题三,之前QA反馈大类icon偶现卡顿且有加载偏错的现象报出,通过此处的耗时输出初步判断可能跟此处的实现机制和io操作有关。
2019-07-30 17:16:32.300 2348-2348/com.wuba D/StrictMode: StrictMode policy violation; ~duration=161 ms: android.os.strictmode.DiskReadViolation
at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1504)
at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:251)
at java.io.File.exists(File.java:815)
at vivo.content.res.VivoThemeZipFile.isFileExist(VivoThemeZipFile.java:172)
at vivo.content.res.VivoThemeZipFile.getThemeFile(VivoThemeZipFile.java:89)
at vivo.content.res.VivoThemeZipFile.getThemeFile(VivoThemeZipFile.java:86)
at vivo.content.res.VivoThemeResources.getThemeFile(VivoThemeResources.java:83)
at vivo.content.res.VivoThemeResourcesPackage.getThemeFile(VivoThemeResourcesPackage.java:73)
at android.content.res.VivoBaseResourcesImpl.createFromResourceStream(VivoBaseResourcesImpl.java:58)
at android.content.res.VivoBaseResourcesImpl.createFromResourceStream(VivoBaseResourcesImpl.java:160)
at android.content.res.ResourcesImpl.loadDrawableForCookie(ResourcesImpl.java:971)
at android.content.res.ResourcesImpl.loadDrawable(ResourcesImpl.java:744)
at android.content.res.Resources.getDrawableForDensity(Resources.java:946)
at android.content.res.Resources.getDrawable(Resources.java:885)
at android.content.res.Resources.getDrawable(Resources.java:860)
at com.facebook.drawee.generic.GenericDraweeHierarchy.setPlaceholderImage(GenericDraweeHierarchy.java:440)
at com.wuba.commons.picture.fresco.widget.WubaDraweeView.setImageDefautId(WubaDraweeView.java:296)
at com.wuba.commons.picture.fresco.widget.WubaDraweeView.setNoFrequentImageWithDefaultId(WubaDraweeView.java:88)
at com.wuba.homepage.section.bigicon.BigIconAdapter.getView(BigIconAdapter.java:69)
at android.widget.AbsListView.obtainView(AbsListView.java:2516)
at android.widget.GridView.makeAndAddView(GridView.java:1444)
at android.widget.GridView.makeRow(GridView.java:371)
at android.widget.GridView.fillDown(GridView.java:312)
at android.widget.GridView.fillFromTop(GridView.java:447)
at android.widget.GridView.layoutChildren(GridView.java:1288)
at android.widget.AbsListView.onLayout(AbsListView.java:2304)
at android.view.View.layout(View.java:20793)
at android.view.ViewGroup.layout(ViewGroup.java:6264)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1801)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1567)
at android.view.View.layout(View.java:20793)
at android.view.ViewGroup.layout(ViewGroup.java:6264)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
at com.wuba.homepage.view.flingappbarlayout.HomePageAppBarLayout.onLayout(HomePageAppBarLayout.java:239)
at android.view.View.layout(View.java:20793)
at android.view.ViewGroup.layout(ViewGroup.java:6264)
at android.support.design.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1183)
at android.support.design.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:870)
at com.wuba.homepage.view.flingappbarlayout.ViewOffsetBehavior.layoutChild(ViewOffsetBehavior.java:64)
at com.wuba.homepage.view.flingappbarlayout.ViewOffsetBehavior.onLayoutChild(ViewOffsetBehavior.java:43)
at com.wuba.homepage.view.flingappbarlayout.HomePageAppBarLayout$Behavior.onLayoutChild(HomePageAppBarLayout.java:1026)
at com.wuba.homepage.view.flingappbarlayout.HomePageAppBarLayout$Behavior.onLayoutChild(HomePageAppBarLayout.java:782)
at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:888)
at android.view.View.layout(View.java:20793)
at android.view.ViewGroup.layout(ViewGroup.java:6264)
at com.wuba.homepage.view.HomePageSmartRefreshLayout.onLayout(HomePageSmartRefreshLayout.java:658)
at android.view.View.layout(View.java:20793)
at android.view.ViewGroup.layout(ViewGroup.java:6264)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:331)
at android.widget.FrameLayout.onLayout(FrameLay
通过“严格模式”也可以看出模块布局资源和图片加载耗时的具体数据,加载布局和图片的过程其实都有在主线程中进行io操作。
UIComponent | LINE | 文件操作 | TIME |
---|---|---|---|
TribeEnterLayout.java | 175 | 布局 | 360ms |
BigIconComponent.java | 59 | 背景图 | 118ms |
BigIconAdapter.java | 60 | 布局 | 331ms |
BigIconAdapter.java | 69 | 图片 | 161ms |
SmallIconComponent.java | 59 | 背景图 | 96ms |
NewsLayout.java | 121 | 布局 | 81ms |
NewsAdapter.java | 225 | 布局 | 77ms |
RecommendComponent.java | 144 | 图片 | 44ms |
RecommendComponent.java | 148 | 默认图 | 42ms |
2. 插桩计时
冷启动
冷启动时间是从点击App桌面icon开始到HomeActivity加载完成的历时计算所得。
public class XXXApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
StartTimeUtils.beginTimeCalculate(StartTimeUtils.COLD_START);
}
}
public class HomeActivity extends BaseAppCompatActivity {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
long coldStartTime = StartTimeUtils.getTimeCalculate(StartTimeUtils.COLD_START);
LOGGER.i("displayed", "coldStartTime: "+coldStartTime);
}
}
}
冷启动耗时
NO. | 冷启动 |
---|---|
1 | 7s, 890ms |
2 | 7s, 832ms |
3 | 7s, 820ms |
3. Displayed
通过Android日志输出各页面耗时 下面输出了三次APP冷启动过程中Launch和Home以及整个过程的所耗时长。
NO. | LaunchActivity | HomeActivity |
---|---|---|
1 | 2s, 696ms | 1s, 431ms |
2 | 2s, 718ms | 1s, 410ms |
3 | 2s, 711ms | 1s, 398ms |
4. ASProfiler
分析方法耗时
5. 解析Trace文件
编写解析trace文件的脚本,对比耗时方法处理前后的情况