做好性能优化需求,就要全面分析并优化性能不达标的低质量代码。
比如,你人工找到了一个觉得耗时的方法,但是通常可能这只是多个问题中的一个凸显问题,很巧被你发现了,做到全面分析就是为了避免片面解决问题。
还有,你可能在发现问题后对问题进行了处理,但是通常不知道剪短了多少耗时算是优化了这个低质量代码,那么就需要有个衡量或是对比尺子来评判你优化的内容是否已达标。
要对性能优化做到全面且达标,单靠个人经验和片面分析是完不成的。正所谓,工欲善其事必先利其器!
下面讲一下个人在做首页性能优化时用到的一些工具:
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文件的脚本,对比耗时方法处理前后的情况
