Android效能UI卡頓

yxc發表於2019-03-01

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;
  }
}
複製程式碼

相關文章