使用多种工具分析解决首页的性能问题

APP性能优化中应用到的几个工具

Posted by Tristan on July 30, 2019

做好性能优化需求,就要全面分析并优化性能不达标的低质量代码。

比如,你人工找到了一个觉得耗时的方法,但是通常可能这只是多个问题中的一个凸显问题,很巧被你发现了,做到全面分析就是为了避免片面解决问题。

还有,你可能在发现问题后对问题进行了处理,但是通常不知道剪短了多少耗时算是优化了这个低质量代码,那么就需要有个衡量或是对比尺子来评判你优化的内容是否已达标。

要对性能优化做到全面且达标,单靠个人经验和片面分析是完不成的。正所谓,工欲善其事必先利其器!

下面讲一下个人在做首页性能优化时用到的一些工具:

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文件的脚本,对比耗时方法处理前后的情况