Android 性能优化-卡顿优化

卡顿问题错综复杂:代码 内存 CPU 绘制 IO 不易复现

线上卡顿不易复现,

1. CPU profiler

默认profiler里可以实时监控APP的CPU 情况。
也可以通过代码形式,最后生成一个文件
Android/data/packagename/files

Debug.startMethodTracing("filepath");
Debug.stopMethodTracing()

优缺点:
以图形的形式展示执行时间,调用栈
信息全面,包含所有线程
运行时开销严重,整体会变慢

2 systrace

监控跟踪API调用,生成一个html文件。
轻量级,开销小。
直观反映CPU利用率,API 18 以上,推荐使用TraceCompat
会给出建议

3 strictMode

严苛模式,Android提供的一种运行时检测机制。帮助开发人员不规范的问题。
方便强大
主要检测:
线程策略: 自定义耗时、网络 和
虚拟机策略: activity泄漏 service泄漏 实例数量 数据库泄漏

  private void initStrictMode() {
        if (DEV_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()// or .detectAll() for all detectable problems
                    .penaltyLog() //在Logcat 中打印违规异常信息
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .setClassInstanceLimit(NewsItem.class, 1) //限制newsItem对象为一个对象,如果出现多个对象会显示到logcat里
                    .detectLeakedClosableObjects() //API等级11
                    .penaltyLog()
                    .build());
        }
    }

logcat 里使用过滤字段为 StrictMode

4.自动检测卡顿

系统工具适合线下分析
线上及测试环节

4.1 原理:

消息处理机制,一个线程只有一个looper,在loop方法for循环里,会判断logging对象是否为null,不为null 打印当前的堆栈信息。
logging是一个Printer对象,我们设置自己的printer对象,通过Looper.getLooper().setMessageLogging(printer), mLogging对象在每个message处理前后被调用。主线程发生卡顿,是在dispatchMessage执行耗时操作。

  public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

	  	for (;;) {
           Message msg = queue.next(); // might block
 
  			// This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
             msg.target.dispatchMessage(msg);

	 		if (logging != null) {
	                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
	        }
	
		}
}	

//给mlogging对象赋值
public void setMessageLogging(@Nullable Printer printer) {
     mLogging = printer;
 }

4.2 BlockCanary

https://github.com/markzhai/AndroidPerformanceMonitor
非侵入式的性能监控组件,通知形式弹出卡顿信息。

  1. 依赖
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
  1. Maximum log count is set to 500, you can rewrite it in your app int.xml.可以设置最大日志数量,重写int.xml文件
<integer name="block_canary_max_stored_count">1000</integer>
  1. Monitor app’s label and icon can be configured by placing a block_canary_icon drawable in your xhdpi drawable directory and in strings.xml:
    在xhdpi 的strings.xml文件里添加label标签
<string name="block_canary_display_activity_label">Blocks</string>
  1. 在application里注册BloackCanary功能
public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        // ...
        // Do it on main process
        BlockCanary.install(this, new AppBlockCanaryContext()).start();
    }
}

  1. 定义子类AppBlockCanaryContext实现 BlockCanaryContext类
    Implement your application BlockCanaryContext context (strongly recommend you to check all these configs):

问题:
这种方式卡顿堆栈可能不准确,不能真正表达卡顿的具体位置。
获取监控周期内的多个堆栈,而不仅是最后一个。
startMonitor --> 高频采集堆栈–>endMonitor -->记录多个堆栈–>上报

高频卡顿上报量大,对服务器造成压力。一个卡顿下多个堆栈大概率有重复,可以对一个卡顿下堆栈进行hash排重,找出重复的堆栈。

5. ANR 分析

keyDispatchTimeOut 5s
BroadcastTimeOut 前台10s 后台60s
ServiceTimeOut 前台20s 后台200s

发生ANR ,进程会接收到异常终止信号,开始写入进程ANR信息写入到文件内(cpu IO 锁),弹出ANR提示框。

解决方案
adb pull data/anr/trances.txt 导入ANR的错误日志

5.1 线上ANR - WatchDog监控

通过FileObserver 监控文件变化,高版本权限问题
我们可以使用ANR-watchDog 一种非侵入式
A simple watchdog that detects Android ANRs (Application Not Responding).
https://github.com/SalomonBrys/ANR-WatchDog

  1. In the app/build.gradle file, add:
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
  1. In your application class, in onCreate, add:
new ANRWatchDog().start();

WatchDog的原理

watchDog 和 BlockCanary
BlockCanary 是监控handler内部处理的msg
ANR-WatchDog:通过改变变量值,最终根据变量的变化,来推导最终结果。

6. 卡顿单点问题方案

自动化监测方案的阈值可能没有达到直接崩溃的阈值,但是客户直观感觉卡顿。这就需要单项监测和解决方案。
IPC调用类型
调用次数 耗时

ARTHook 可以Hook 系统方法
Aspectj:非系统方法,自己的方法,或者jar

7. 界面秒开

异步+延迟初始化
异步Inflate 、x2c、绘制优化

onCreate 到 onWindowFocusChanged 从界面打开到显示的过程

Lancet

轻量级 aop框架 编译速度快,支持增量编译,使用简单,没有任何多余代码插入apk
https://github.com/eleme/lancet

 classpath 'me.ele:lancet-plugin:1.0.6'
apply plugin: 'me.ele.lancet'

dependencies {
    provided 'me.ele:lancet-base:1.0.6'
}

创建类ActivityHooker,添加方法

@Proxy(“i”) 对应i的方法
@TargetClass(“android.util.Log”) 对应监听Log类

@Proxy("i")
@TargetClass("android.util.Log")
public static int i(String tag, String msg) {
    msg = msg + " some msg";
   return (int) Origin.call();//调用原有的i方法
}

如果想监听界面打开需要的时间。需要写两个方法onCreate 和onWindowFocusChanged

    @Insert(value = "onCreate",mayCreateSuper = true)
    @TargetClass(value = "android.support.v7.app.AppCompatActivity",scope = Scope.ALL)
    protected void onCreate(Bundle savedInstanceState) {
        sActivityRecord.mOnCreateTime = System.currentTimeMillis();
        Origin.callVoid();
    }

    @Insert(value = "onWindowFocusChanged",mayCreateSuper = true)
    @TargetClass(value = "android.support.v7.app.AppCompatActivity",scope = Scope.ALL)
    public void onWindowFocusChanged(boolean hasFocus) {
        sActivityRecord.mOnWindowsFocusChangedTime = System.currentTimeMillis();
        LogUtils.i("onWindowFocusChanged cost "+(sActivityRecord.mOnWindowsFocusChangedTime - sActivityRecord.mOnCreateTime));
        Origin.callVoid();
    }

@Proxy and @Insert
Value in @Proxy and @Insert is the target method name.
@Proxy means to hook every invoke point of the target method.
@Insert means to hook the code inside the method.

8. 定制Handler

重写sendMessage()和dispatchMessage方法

使用自己的handler ,重写两个方法,这样发送的消息和接收的消息,都可以跟踪,时间。堆栈信息,包名

public class GetDetailHandlerHelper {

    private static ConcurrentHashMap<Message, String> sMsgDetail = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<Message, String> getMsgDetail() {
        return sMsgDetail;
    }

}

public class SuperHandler extends Handler {

    private long mStartTime = System.currentTimeMillis();

    public SuperHandler() {
        super(Looper.myLooper(), null);
    }

    public SuperHandler(Callback callback) {
        super(Looper.myLooper(), callback);
    }

    public SuperHandler(Looper looper, Callback callback) {
        super(looper, callback);
    }

    public SuperHandler(Looper looper) {
        super(looper);
    }

    @Override
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        boolean send = super.sendMessageAtTime(msg, uptimeMillis);
        if (send) {
            GetDetailHandlerHelper.getMsgDetail().put(msg, Log.getStackTraceString(new Throwable()).replace("java.lang.Throwable", ""));
        }
        return send;
    }

    @Override
    public void dispatchMessage(Message msg) {
        mStartTime = System.currentTimeMillis();
        super.dispatchMessage(msg);

        if (GetDetailHandlerHelper.getMsgDetail().containsKey(msg)
                && Looper.myLooper() == Looper.getMainLooper()) {
            JSONObject jsonObject = new JSONObject();
            try {
                jsonObject.put("Msg_Cost", System.currentTimeMillis() - mStartTime);
                jsonObject.put("MsgTrace", msg.getTarget() + " " + GetDetailHandlerHelper.getMsgDetail().get(msg));

                LogUtils.i("MsgDetail " + jsonObject.toString());
                GetDetailHandlerHelper.getMsgDetail().remove(msg);
            } catch (Exception e) {
            }
        }
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值