android性能优化

作者: 我已不是少年郎 | 来源:发表于2017-02-24 11:42 被阅读2238次

    Reason: Broadcast of Intent { act=android.intent.action.TIME_TICK

    ActivityManager: 
    ANR in com.***.***
    PID: 16227
    Reason: Broadcast of Intent { act=android.intent.action.TIME_TICK flg=0x50000014 (has extras) }
    

    有那么一段时间我被这个ANR折磨到每天吃不下饭睡不着觉日渐憔悴,Read the fucking source code,每天看android源码各种跟踪调试编译烧写之后,终于找出了问题所在,应用层APP的UI线程刷新太频繁!

    不要使用Html.fromHtml()

    Textview里的文字设置多种颜色时不要偷懒,分两个Textview来写,使用Html.fromHtml加载时会耗时500+毫秒,用多了引起卡顿。

    Handler传参尽量要使用基础类型

    msg.obj传递java类对象在1秒400次频率下会导致数据丢失卡顿,并且会莫名其妙出现数据错乱。

    UI线程一定要只做刷新UI动作

    UI线程最好只做界面相关的动作,不要为了偷懒少些几行代码就把整个数据抛给UI线程,并发大的时候会丢失数据。
    数据的各种格式和逻辑要在子线程中判断,把不需要刷新UI的数据拦截下来并抛掉,总之就是不要频繁刷新UI。

    EventBus不适合高并发数据的处理,老老实实写接口用回调吧

    在实现一些设计模式上EventBus更有用,但在高并发数据下即使加了eventBusIndex处理速度还是慢,并且会越来越慢。

    定时任务不要用timer,使用Handler的sendEmptyMessageDelayed()或sendMessageDelayed()

    timer耗费的资源多,并且一旦异常之后就会把整个任务终结掉出现各种难以预料的问题。

    jni回调java函数时,一定要去冗余数据

    jni层把有用数据过滤精简之后再回调java函数,1000毫秒回调400多次根本刷新不过来。

    JNI DETECTED ERROR IN APPLICATION: JNI SetByteArrayRegion called with pending exception 'android.view.ViewRootImpl$CalledFromWrongThreadException' thrown in unknown throw location

    刚开始遇到这个错误我以为jni层SetByteArrayRegion这个地方byte数组copy出错,单独调试jni的库也一点问题没有。最后感觉这个地方又有view又有Thread,那么会不会是在jni的子线程里调用了UI线程导致,十几个从jni回调上来的地方一个一个地方排查,果然是在callback线程直接更新UI所致!一个地方遗漏掉了,同事以为我改了,我以为他改了,结果谁都没改,因为板子没接收音机的模块,CAN总线没有发收音机的数据上来,这个模块一直没调试过,直到被测试发现之后叼了我们一顿。信息同步很重要啊!

    Skipped *** frames! The application may be doing too much work on its main thread.

    场景是不停的用handler定时发delay message,然后分发给UI来跑界面,模拟真实使用场景,跑了一夜,第二天又卡又慢,这个问题不用到处搜索了,写的清清楚楚明明白白

    The application may be doing too much work on its main thread
    

    只能看自己的代码调试看看耗时操作,逐个场景分析。我这里是因为真实数据是在子线程分发出去,然后个UI线程获取数据之后再用Handler来更新UI,但是因为模拟分发数据,为了图方便用了handler的sendMessageDelayed函数,所以相当于主线程分发数据之后,UI线程又开一个Handler又在主线程中刷新数据,造成不必要开销。解决方法就是,加一个开关,如果是模拟数据,直接更新UI不再发handler更新UI。

    android.view.InflateException: Binary XML file line # *: Error inflating class

    自定义view库在另外一个功能,通过compile project的方式引入,然后在项目工程的layout文件里直接按照包名类名来引入,总是报这个错误,包名类名路径都正确,很郁闷,一般也不会怀疑一个经过各种验证的自己写好的封装view库,但是原因就在这里。

    @RequiresApi(api = Build.VERSION_CODES.N)
    public MyView(Context context, AttributeSet attrs, int defStyleAttr)
    

    为了调用新接口构造函数加了的API版本,而运行的机器低于这个版本,所以在xml里配置这个view的时候就会报错,这个地方调用低版本的API,去掉版本限制就OK了,RequiresApi要慎用。

    自定义View抗锯齿、画大于180度的扇形问题

    画仪表盘的时候,刚开始是用切割画布的方式实现clipPath,但是锯齿太明显了,后来用图层的方式实现,效果非常平滑完美,主要用到PorterDuff.Mode.DST_OUT,但是要先设置setLayerType为LAYER_TYPE_SOFTWARE或者LAYER_TYPE_HARDWARE

    大于180度的扇形时,由于path的addArc函数只能添加一个弧形,如果想要一个扇形还要切割一个三角形,和这个弧形拼凑起来,如果大于180度的时候,下面会多出一条线,这时候要把大于180度的面积,分割成两个扇形,分别添加到path中,多说无益,上代码,示例代码是从150度开始绘制.

    补充:后面我再看这段代码发现还有优化的空间

    对于Dalvik虚拟机来说,要尽量避免频繁生成临时变量特别是onDraw等函数,也要避免产生很多长生命周期的对象,但是出于懒的关系,下面这段代码并没有修改

    private void drawProgressIndicator(Canvas canvas, int percent) {
            if (percent == 0) {
                return;
            }
            canvas.save();
            canvas.translate(0f, 0f);
            canvas.drawBitmap(bitmapIndicatorShadow, 0, 0, dPaint);
    
            float startAngle = START_ARC + percent;
    
            dPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
    
            float sX, sY;
            float eX, eY;
    
            sX = (float) (CENTER + (2 + CENTER) * Math.cos((startAngle) * Math.PI / 180));
            sY = (float) (CENTER + (2 + CENTER) * Math.sin((startAngle) * Math.PI / 180));
            eX = (float) (CENTER + (2 + CENTER) * Math.cos((endAngle) * Math.PI / 180));
            eY = (float) (CENTER + (2 + CENTER) * Math.sin((endAngle) * Math.PI / 180));
    
            RectF rectF = new RectF(0, 0, mWidth, mWidth);
            Path path = new Path();
            path.reset();
            path.moveTo(CENTER, CENTER);
            path.lineTo(sX, sY);
            if (startAngle <= 210) {
                float cX = (float) (CENTER + (2 + CENTER) * Math.cos((270) * Math.PI / 180));
                float cY = (float) (CENTER + (2 + CENTER) * Math.sin((270) * Math.PI / 180));
                path.lineTo(cX, cY);
                path.lineTo(eX, eY);
                path.close();
    
                path.addArc(rectF, startAngle, 270 - startAngle);
                path.addArc(rectF, 270, endAngle - 270);
    
            } else {
                path.lineTo(eX, eY);
                path.close();
    
                path.addArc(rectF, startAngle, endAngle - startAngle);
            }
    
            canvas.drawPath(path, dPaint);
            dPaint.setXfermode(null);
            canvas.restore();
        }
    

    优化内存

    1、所有的HashMap尽量改ArrayMap
    2、所有的enum尽量改成static变量
    3、所有xml布局层级嵌套超过3层精简一下
    4、所有的for或while循环里,尽量避免new对象除非逻辑如此
    5、所有for或while避免使用Iterator或者for(Object obj:list)形式便利,直接用最原始语句遍历
    6、onDraw()里不要new对象
    7、能用jpg的图就不要用png,jpg的图质量最低压缩比例最高,网上搜索一下jpg和png压缩工具,压缩图片大小
    8、所有fragment,特别是主界面里的,改成用时加载不用时销毁,不再一直加载,容易被回收
    9、编译时剪裁系统应用主要在./build/target/product下,利用find语句查找并注释删减应用,在注释时,# Launcher2 \要把连接符\直接删除掉,否则注释语句相当于/** Launcher2 \ 后面的一长串代码 **/

    find . -type f -name '*' | xargs grep 'Launcher2'
    find . -name '*' | xargs grep dalvik.vm.heapsize
    

    10、内存相关

    adb remount
    adb shell
    mount -o remount rw /system
    
    dumpsys meminfo
    adb shell dumpsys meminfo com.***.***
    adb shell dmesg
    adb shell cat /proc/kmsg
    adb shell procrank
    //查看应用启动时间
    adb shell am start -W com.***.***/com.***.***.MainActivity
    
     VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
     RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
     PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
     USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
    
    

    修改framework层应用内存阈值大小在./frameworks/native/build
    下,比如android:largeHeap="true"时,调整内存到512m

    PRODUCT_PROPERTY_OVERRIDES += \
        dalvik.vm.heapstartsize=8m \
        dalvik.vm.heapgrowthlimit=64m \
        dalvik.vm.heapsize=512m \
        dalvik.vm.heaptargetutilization=0.75 \
        dalvik.vm.heapminfree=512k \
        dalvik.vm.heapmaxfree=8m
    

    11、固定应用方向,使android:screenOrientation="reverseLandscape"无效,固定在某个方向,修改frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java
    源文件

    @Override
        public int rotationForOrientationLw(int orientation, int lastRotation) {
            if (false) {
                Slog.v(TAG, "rotationForOrientationLw(orient="
                            + orientation + ", last=" + lastRotation
                            + "); user=" + mUserRotation + " "
                            + ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED)
                                ? "USER_ROTATION_LOCKED" : "")
                            );
            }
    
            if (mForceDefaultOrientation) {
                return Surface.ROTATION_0;
            }
    

    修改为固定旋转180度

    @Override
        public int rotationForOrientationLw(int orientation, int lastRotation) {
            if (false) {
                Slog.v(TAG, "rotationForOrientationLw(orient="
                            + orientation + ", last=" + lastRotation
                            + "); user=" + mUserRotation + " "
                            + ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED)
                                ? "USER_ROTATION_LOCKED" : "")
                            );
            }
    
            if (mForceDefaultOrientation || true) {
                return Surface.ROTATION_180;
            }
    

    12、删减系统服务
    在frameworks/base/services/java/com/android/server/SystemServer.java中删减服务, 这里也是启动SystemUI的地方

    相关文章

      网友评论

        本文标题:android性能优化

        本文链接:https://www.haomeiwen.com/subject/cifwwttx.html