Android性能UI卡顿

1,680 阅读6分钟

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;
  }
}