当前位置: 首页 > news >正文

Android性能UI卡顿

UI卡顿原理

Android当中保持60帧以上算是流畅:60fps ——>16ms/帧(数字量化)

准则:尽量保证每次在16ms内处理完所有的cpu与Gpu计算、绘制、渲染等操作,否则会造成丢帧卡顿等问题

原因:在主线程中执行耗时工作,把事件分发给合适的view或者widget的

  1. 在子线程中处理
handler
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
复制代码
  1. 布局Layout过于复杂,无法在16ms内完成渲染

  2. View的过度绘制

  3. View频繁的触发measure、layout

  4. 内存频繁的触发GC过多(STW,创建太多的临时变量)实现的核心原理

    ​ ###Blockcanary工具 在主线程ActivityThread中的 dispatchMessge(msg)上下方打印时间,计算阀值,超过了就打印

  5. postMessage(Handler)

  6. Queue.next()获取我们的消息

  7. 是否超过我们的阀值 (Dump all Allocation)

DisplayActivity在 release 版本不会显示

核心逻辑在BlockCanaryInternals:

LooperMonitor:判断是否卡顿 isBlock

private boolean isBlock(long endTime) {
        return endTime - mStartTimestamp > mBlockThresholdMillis;
    }

    private void notifyBlockEvent(final long endTime) {
        final long startTime = mStartTimestamp;
        final long startThreadTime = mStartThreadTimestamp;
        final long endThreadTime = SystemClock.currentThreadTimeMillis();
        HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
            @Override
            public void run() {
                mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
            }
        });
    }

    private void startDump() {
        if (null != BlockCanaryInternals.getInstance().stackSampler) {
            BlockCanaryInternals.getInstance().stackSampler.start();
        }

        if (null != BlockCanaryInternals.getInstance().cpuSampler) {
            BlockCanaryInternals.getInstance().cpuSampler.start();
        }
    }
复制代码

stackSampler:

public ArrayList<String> getThreadStackEntries(long startTime, long endTime) {
        ArrayList<String> result = new ArrayList<>();
        synchronized (sStackMap) {
            for (Long entryTime : sStackMap.keySet()) {
                if (startTime < entryTime && entryTime < endTime) {
                    result.add(BlockInfo.TIME_FORMATTER.format(entryTime)
                            + BlockInfo.SEPARATOR
                            + BlockInfo.SEPARATOR
                            + sStackMap.get(entryTime));
                }
            }
        }
        return result;
    }

    @Override
    protected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();

        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append(BlockInfo.SEPARATOR);
        }

        synchronized (sStackMap) {
            if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
                sStackMap.remove(sStackMap.keySet().iterator().next());
            }
            sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
        }
    }
复制代码

CpuSampler:

@Override
    protected void doSample() {
        BufferedReader cpuReader = null;
        BufferedReader pidReader = null;

        try {
            cpuReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/stat")), BUFFER_SIZE);
            String cpuRate = cpuReader.readLine();
            if (cpuRate == null) {
                cpuRate = "";
            }

            if (mPid == 0) {
                mPid = android.os.Process.myPid();
            }
            pidReader = new BufferedReader(new InputStreamReader(
                    new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
            String pidCpuRate = pidReader.readLine();
            if (pidCpuRate == null) {
                pidCpuRate = "";
            }

            parse(cpuRate, pidCpuRate);
        } catch (Throwable throwable) {
            Log.e(TAG, "doSample: ", throwable);
        } finally {
            try {
                if (cpuReader != null) {
                    cpuReader.close();
                }
                if (pidReader != null) {
                    pidReader.close();
                }
            } catch (IOException exception) {
                Log.e(TAG, "doSample: ", exception);
            }
        }
    }
复制代码

ANR造成原因

  • ANR: Application Not responding
  • Activity Manager和 WindowManager(系统服务监控)
  • ANR的分类
  1. watchDog-anr是如何监控anr的?

    1. Service Timeout(5秒)
    2. BroadcastQueue Timeout(10秒)
    3. inputDispatch Timeout (5秒)
  2. 主线程耗时操作 ()

  3. 主线程被锁住

  4. cpu被其它的进程占用

如何解决

  1. 主线程读取数据
  2. sharepreference的 commit(), 子线程中 apply()替换
  3. Broadcast的reciever不能进行耗时操作, IntentService中操作
  4. Activity的生命周期中不应该有太耗时的操作

WatchDog监控

github.com/SalomonBrys…

创建一个监控线程

private final Handler _uiHandler = new Handler(Looper.getMainLooper());
复制代码

改线程不断往UI线程post一个任务

private final Runnable _ticker = new Runnable() {
        @Override public void run() {
            _tick = (_tick + 1) % Integer.MAX_VALUE;
        }
 };


@Override
public void run() {
  setName("|ANR-WatchDog|");

  int lastTick;
  int lastIgnored = -1;
  while (!isInterrupted()) {
    lastTick = _tick;
    _uiHandler.post(_ticker);
    try {
      //睡眠固定时间
      Thread.sleep(_timeoutInterval);
    }
    catch (InterruptedException e) {
      _interruptionListener.onInterrupted(e);
      return ;
    }

    // If the main thread has not handled _ticker, it is blocked. ANR.
    if (_tick == lastTick) {
      if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
        if (_tick != lastIgnored)
          Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
        lastIgnored = _tick;
        continue ;
      }

      ANRError error;
      if (_namePrefix != null)
        error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
      else
        error = ANRError.NewMainOnly();
      _anrListener.onAppNotResponding(error);
      return;
    }
  }
}
复制代码

new Thread

  • 问题:Thread 的 start() 启动线程, 处于就绪状态,并没有运行,告诉CPU可以运行。

存在的问题:

  1. 多个耗时任务开启多个线程,开销是非常大的
  2. 如果在线程中执行循环任务,只能通过一个Flag开控制它的停止
  3. 没有线程切换的接口
  4. 如果从UI线程启动,则该线程优先级默认为Default

Process.setThreadPriority(Process.THREAD_PRIO_RITY_BACKGROUD), 需要把线程优先级降低

线程间通信

将子线程中的消息抛到主线程中去:
Handler handler = new Handler(){
    void handlerMessage(){
		do UI things...
	}
}

New Thread(){
  void run(){
     handler. sendMessage();
  }
}.start();
  
handler.post(runnable);

Activity.runOnUiThread(Runnable)
复制代码
AsynTask

AsynTask的线程优先级是background不会阻塞UI。

AsyncTask 3.0之后改成顺序执行,当一个进程中有多个AsynTask同时并行执行,他们会公用线程池,主要原因在doInBackground()中访问相同的资源,线程池会造成线程的并发访问造成线程安全问题,所以设计成串行的,就不会有线程安全问题。

将任务从主线程抛到工作线程

  1. Thread/Runnable
  2. HandlerThread
  3. InterService
thread/runnable

Runnable作为匿名内部类的话会持有外部类的引用,容易内存泄漏,不建议采取这种方式

HandlerThread

它集成了Thread

它有自己的内部Looper对象,通过Looper.loop()进行looper循环

HandlerThread的looper对象传递给Handler对象,然后在handleMessge()方法中执行异步任务

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * @return The looper.
     */
  //这个是在UI线程中调用,需要解决同步问题,因为looper对象在 run方法里运行。
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    /**
     * @return a shared {@link Handler} associated with this thread
     * @hide
     */
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    /**
     * Quits the handler thread's looper.
     * <p>
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
          	//清空所有消息
            looper.quit();
            return true;
        }
        return false;
    }

    /**
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
          //清除所有的延迟消息
            looper.quitSafely();
            return true;
        }
        return false;
    }
    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

复制代码

HandlerThread适合单线程或者异步队列,I/O流读取文件,进行异步转化比较合适,只有一个线程。

网络的数据需要并发处理,不太适合。

IntentService

  1. intentService是Service类的子类

  2. 单独开启一个线程来处理所有的Intent请求所对应的任务

  3. 当IntentService处理完任务之后,会自己在合适的时候结束

    public abstract class IntentService extends Service {
        private volatile Looper mServiceLooper;
        private volatile ServiceHandler mServiceHandler;
        private String mName;
        private boolean mRedelivery;
    
        private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                onHandleIntent((Intent)msg.obj);
                stopSelf(msg.arg1);
            }
        }
        public IntentService(String name) {
            super();
            mName = name;
        }
        
        public void setIntentRedelivery(boolean enabled) {
            mRedelivery = enabled;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
    
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }
    
        @Override
        public void onStart(@Nullable Intent intent, int startId) {
            Message msg = mServiceHandler.obtainMessage();
            msg.arg1 = startId;
            msg.obj = intent;
            mServiceHandler.sendMessage(msg);
        }
    
        @Override
        public void onDestroy() {
            mServiceLooper.quit();
        }
    
        /**
         * Unless you provide binding for your service, you don't need to implement this
         * method, because the default implementation returns null.
         * @see android.app.Service#onBind
         */
        @Override
        @Nullable
        public IBinder onBind(Intent intent) {
            return null;
        }
      
        @WorkerThread
        protected abstract void onHandleIntent(@Nullable Intent intent);
    }
    
    复制代码

多进程的好处

  1. 解决OOM问题(Android会限制单一进程的内存大小)
  2. 合理的利用内存
  3. 单一进程奔溃不会影响整体应用
  4. 项目解耦、模块化开发

问题

  1. Application会多次创建的问题(根据进程名进行不同的初始化,不要做过多的静态对象初始化)
  2. 文件读写潜在的问题(Java中文件锁基于Java 虚拟机、进程存在的,特别Sharepreference)
  3. 静态变量和单例模式完全失效。(多进程中不要过多的用静态变量,失效了)
  4. 线程同步都会失效,这些都是在虚拟机、进程的基础上。

synchronized和volidate

  1. 阻塞线程与否
  2. 使用范围
  3. 原子性(synchronized保证原子性)
volidate与单例
//饿汉
public class Singleton{
  private static Singleton intance = new Singleton();
  private Singleton(){}
  
  public static Singleton getInstance(){
    return instance;
  }
}

//懒汉
public class SingletonLazy{
  private static SingletonLazy intance = null;
  private Singleton(){}
  
  public static SingletonLazy getInstance(){
    if(null == instance){
      instance = new SingletonLazy();
    }
    return instance;
  }
  //性能损耗较大
  public static synchronized SingletonLazy getInstance1(){
    if(null == instance){
      instance = new SingletonLazy();
    }
    return instance;
  }
}

//双重校验锁
public class SingletonDouble{
  private static volatile SingletonDouble intance = null;
  private SingletonDouble(){}
  
  public static SingletonDouble getInstance(){
    if(null == instance){
      synchronized(SingletonDouble.this){
        if(null == instance){
          instance = new SingletonDouble();
        }
      }
    }
    return instance;
  }
}
复制代码

相关文章:

  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【记一次pull request的惨痛教训】不可见的分隔符之Zero-with-space
  • 在SQL 2005中用T-SQL插入中文数据时出现的问号或乱码的解决方案[转]
  • (轉貼)《OOD启思录》:61条面向对象设计的经验原则 (OO)
  • 电子书下载:Programming Entity Framework DbContext
  • C++ int转string
  • 反射相关函数获取枚举描述函数
  • 关于Repeater如何获取控件对象
  • 5分钟快速建立项目版本控制
  • 设计模式学习之生成器模式
  • SSH项目的简单table及其分页框架
  • .NET 事件模型教程(二)
  • SUP (SAP Mobile SDK 2.2) 连接 Sybase SQL Anywhere sample 数据库
  • 流的压缩与解压缩函数
  • Javascript 严格模式详解(转)
  • js如何打印object对象
  • node 版本过低
  • Nodejs和JavaWeb协助开发
  • SpringBoot几种定时任务的实现方式
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • TypeScript迭代器
  • Vue 2.3、2.4 知识点小结
  • 关于 Cirru Editor 存储格式
  • 前端
  • 一个完整Java Web项目背后的密码
  • 终端用户监控:真实用户监控还是模拟监控?
  • ​马来语翻译中文去哪比较好?
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • (04)odoo视图操作
  • (145)光线追踪距离场柔和阴影
  • (HAL库版)freeRTOS移植STMF103
  • (LeetCode) T14. Longest Common Prefix
  • (第二周)效能测试
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (转)Sublime Text3配置Lua运行环境
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .net 后台导出excel ,word
  • .NET 指南:抽象化实现的基类
  • .Net小白的大学四年,内含面经
  • @RequestParam,@RequestBody和@PathVariable 区别
  • [100天算法】-二叉树剪枝(day 48)
  • [BUAA软工]第一次博客作业---阅读《构建之法》
  • [EWS]查找 文件夹
  • [github全教程]github版本控制最全教学------- 大厂找工作面试必备!
  • [HXPCTF 2021]includer‘s revenge
  • [LeetCode] Longest Common Prefix 字符串公有前序
  • [LeetCode]—Simplify Path 简化路径表达式
  • [linux][调度] 内核抢占入门 —— 高优先级线程被唤醒时会立即抢占当前线程吗 ?
  • [ListView.View=List]的垂直滚动条
  • [MySQL] 二进制文件
  • [MYSQL]mysql常用操作命令
  • [POJ 2888]Magic Bracelet[Polya Burnside 置换 矩阵]
  • [RISC-V]verilog