Android 經常使用開源框架源碼解析 系列 (七)BlockCanary 性能優化框架

1、背景
 
  • 複雜的項目:代碼複雜度的增長,第三方庫的引入,某個Activity or Fragment與其餘相關聯的類或是方法 或是子模塊 。這時候針對某一個Activity進行查找Ui卡頓的問題,而後進行操做是十分困難的!
 
  • 卡頓積累到必定程度形成Activity Not Response,只有在ANR現象下,才能獲取到當前堆棧信息
 
BlockCanary
————Android 平臺-非侵入式的性能監控組件
————針對輕微的UI卡頓及不流暢現象的檢查工具
 
 
1.一、UI卡頓原理——性能優化的大問題之一
 
最優策略:
60fps——>16ms/幀(一幅圖像) 
16ms內是否能完成一次操做
 
準則:儘可能保證每次在16ms內處理完單次的全部的CPU與GPU計算、繪製、渲染等操做,不然會形成丟幀卡頓問題
 
1.二、UI卡頓常見緣由:
    一、Ui線程中作輕微耗時操做
            系統爲App建立的ActivityThread的做用,將事件分發給合適的view 或是widget;同時是應用和Ui交互的主線程
  
  子線程通知主線程Ui完成能夠顯示:
  • handler
  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)-延時post
    
 二、佈局Layout過於複雜,沒法再16ms內完成渲染
 三、View過分繪製-負載加重cpu or gpu
 四、View頻繁的觸發measure、layout事件,致使在繪製Ui的時候時間加巨,損耗加巨,形成View頻繁渲染
 五、內存頻繁觸發GC過多 ——在同一幀頻繁的建立臨時變量加重內存的浪費和渲染的增長
 
ps:虛擬機在執行Gc垃圾回收的時候,會暫停全部的線程(包括ui線程)。只有當Gc垃圾回收器完成工做才能繼續開啓線程繼續執行工做
  • 儘可能減小臨時變量的建立———代碼優化的小點之一
 
2、BlockCanary 使用
2.一、引入依賴:
    implementation'com.github.markzhai:blockcanary-android:1.5.0'
 
2.二、在代碼中註冊blockCanary:
 
 (1)、application代碼中初始化:
@Override
public void onCreate() {
    super.onCreate();
    //第一個參數上下文,第二個參數是blockcanary建立的本身的上下文
   (2)、BlockCanary.install(this, new AppBlockContenxt() ).start();
}
//   (3)、blockContext 特有的上下文的建立
public class AppBlockContext extends BlockCanaryContext {
    //實現各類上下文,包括應用標識符,用戶uid ,網絡類型,卡慢判斷閥值,Log保存位置等內容
        …...
    //1000ms,事件處理時間的閥值,能夠經過修改這個閥值來修改超時閥值
    public int provideBlockThreshold() {
        return 1000;
    }
        …...
}
 
3、BlockCanary 核心實現原理                                         
 
BlockCanary 核心實現原理 :離不開主線程 ActivityThread +handler+looper輪詢器
 
androidxref.com 在線查看源碼網站
 
每一個App只有一個主線程就是ActivityThread線程。
 
3.1 ActivityThread源碼:
在官方的ActivityThread的源碼中,能夠看到:
publicstaticvoidmain(String[] args) {
    …
    Looper.prepareMainLooper(); 
  • //主線程下建立好MainLooper後,關聯一個消息隊列MessageQueue;
  • MainLooper就會在生命週期內不斷的進行輪詢操做,經過Looper獲取到MessageQueue中的message,而後通知主線程去更新Ui
    ...
}
在prepareMainLooper()中:
/**
*經過MyLooper()函數建立一個主線程的looper
  • 不論一共有多少個子線程,主線程只會有這一個looper,同理不論建立多少個handler最後都會關聯到這個looper上
*/
publicstaticvoidprepareMainLooper() {
     prepare(false);
     synchronized(Looper.class) {
         if(sMainLooper!= null) {
             thrownewIllegalStateException("The main Looper has already been prepared.");
           }
             sMainLooper= myLooper();
           }
        }
在Looper中是如何實現消息的分發的呢?
 
在Looper.class() 中
    msg.target.dispatchMessage(msg);//分發message ;msg.target 實際上就是handler
         ...
}
    /**
        * Handle system messages here.
        核心處理消息方法
     */
 public  void   dispatchMessage( Message msg) {
        //if :在這裏這個callback 實際上就是runnable,因此handleCallback實際上就是執行了runnbale中的run()函數來執行子線程
        if(msg.callback!= null) {
             handleCallback(msg);
        } else{
             if(mCallback!= null) {
        //else :handler經過sendMessage()方式來投遞message 到messageQueue中
                 if(mCallback.handleMessage(msg)) {
                        return;
                   }
               }
     //調用該方法來處理消息 ,不論如何最後的回調必定是發生在Ui線程上
             handleMessage(msg);
        }
   }
ps:若是Ui卡頓頗有多是由於在dispatchMessage()這個函數裏執行了卡頓的耗時操做
 
思考:blockCanary 是如何經過android中dispatchMessage()原理實現打印的呢?
 
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);
          }
解析:blockCanary 利用了handler原理在dispatchMessage()的上下方分別打印方法執行的時間,而後根據上下兩個時間差,來判斷dispatchMessage()中是否產生了耗時的操做,也就是這個dispatchMessage():是否有Ui卡頓;若是有Ui卡頓上下兩個值計算的閥值就是配置的閥值,若是超過這個閥值,就能夠dump出Ui卡頓的信息。經過堆棧信息來定位卡頓問題
 
3.2 blockCanary 流程圖
 
 
    一、經過handler.postMessage() 發送消息給主線程
    二、sMainLooper.looper() 經過輪詢器不斷的輪詢MessageQueue中的消息隊列
    三、經過Queue.next() 獲取須要的消息
    四、計算出調用dispatchMessage()方法中先後的時間差值
    五、經過T2-T1的時間差來判斷是否超過設定好的時間差的閥值
    六、若是T2-T1 時間差 > 閥值 ,就dump 出information來定位Ui卡頓
 
    
    七、若是執行完dispatchMessage()後延遲了閥值的0.8倍的話,進行了延遲發送 也會dump出須要的信息(堆棧信息,cpu使用率、內存信息)
 
3、BlockCanary 源碼
BlockCanary.install(this, new AppBlockContenxt()).start();
 
install():
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
   //將context,blockCanaryContext 賦值給BlockCanaryContext
    BlockCanaryContext.init(context, blockCanaryContext);
   //是否開啓或是關閉展現通知欄的界面
    setEnabled(context, DisplayActivity.class,         BlockCanaryContext.get().displayNotification());
    return get();
}
//決定 通知欄 開啓or 關閉 的策略:BlockCanaryContext.get().displayNotification()
ps:displayNotification在debug 和test版本都是返回true,只有在release版本才返回fasle,也就是說 displayNotification 是不會展現的
 
get():經過get()單例模式生成BlockCanary的實例
public static BlockCanary get() {
    if (sInstance == null) {
        synchronized (BlockCanary.class) {
            if (sInstance == null) {
                sInstance = new BlockCanary();
            }
        }
    }
    return sInstance;
}
 
BlockCanary() 構造函數的實現:   //BlockCanary()內部類就是blockCanary核心的實現
private BlockCanary() {
    BlockCanaryInternals.setContext(BlockCanaryContext.get());
    mBlockCanaryCore = BlockCanaryInternals.getInstance();
   //傳入BlockCanaryContext.get()的上下文,只有開啓通知欄的時候纔會展開下面的BlockInterceptor攔截器
    mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
    if (!BlockCanaryContext.get().displayNotification()) {
        return;
    }
   //傳入BlockCanary內部實現的,來展現DisplayService()
    mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
 
BlockCanaryInternals():
public BlockCanaryInternals() {
    //dump出線程的dump信息,傳入參數主線程,looper.getMainLooper().getThread()
    stackSampler= new StackSampler(
            Looper.getMainLooper().getThread(),
            sContext.provideDumpInterval());
   //dump出cpu有關信息
    cpuSampler= new CpuSampler(sContext.provideDumpInterval());
    //內部建立一個LooperMonitor,在該方法中控制時間差
    setMonitor(new  LooperMonitor(new LooperMonitor.BlockListener() {
       //在該方法內打印:主線程調用棧、cpu使用狀況、內存狀況
        @Override
        public void onBlockEvent(long realTimeStart, long realTimeEnd,
                                 long threadTimeStart, long threadTimeEnd) {
            // Get recent thread-stack entries and cpu usage
            ArrayList<String> threadStackEntries = stackSampler
                    .getThreadStackEntries(realTimeStart, realTimeEnd);
            if (!threadStackEntries.isEmpty()) {
                BlockInfo blockInfo = BlockInfo.newInstance()
                        .setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
                        .setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
                        .setRecentCpuRate(cpuSampler.getCpuRateInfo())
                        .setThreadStackEntries(threadStackEntries)
                        .flushString();
                LogWriter.save(blockInfo.toString());
 
                if (mInterceptorChain.size() != 0) {
                    for (BlockInterceptor interceptor : mInterceptorChain) {
                        interceptor.onBlock(getContext().provideContext(), blockInfo);
                    }
                }
            }
        }
    }, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
            //刪除日誌,默認狀況下日誌保存2天
    LogWriter.cleanObsolete();
}
 
start():
public void start() {
    if (!mMonitorStarted) {
        mMonitorStarted = true;
       //獲取主線程looper;再調用主線程的setMessageLogging()進行時間打點
        ps:mBlockCanaryCore.monitor 在BlockCanaryInternals()中建立
        Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }
}
monitor():
class LooperMonitor implements Printer {
    
    //時間打點方法
@Override
public void println(String x) {
    if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
        return;
    }
 
    if (!mPrintingStarted) {
       //獲取系統時間的時間戳 mStartTimestamp
        mStartTimestamp = System.currentTimeMillis();
       //獲取當前線程運行的時間mStartThreadTimestamp,當前線程處於運行狀態的總時間
    ps:線程中的sleep、wait時間不會記錄在這個總時間內
        mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
        mPrintingStarted = true;
        startDump();//打印開始時間的堆棧信息
    } else {        //在dispatchMessage()以後進行以下操做
        final long endTime = System.currentTimeMillis();
        mPrintingStarted = false;
        if (isBlock(endTime)) { //產生Ui卡頓現象
            notifyBlockEvent(endTime);
        }
        stopDump();//打印結束時間的堆棧信息
    }
}
//經過BlockCanaryInternals()方法分別打印stackSampler和cpuSampler
startDump():
private void startDump() {
    if (null != BlockCanaryInternals.getInstance().stackSampler) {
        BlockCanaryInternals.getInstance().stackSampler.start();
    }
    if (null != BlockCanaryInternals.getInstance().cpuSampler) {
        BlockCanaryInternals.getInstance().cpuSampler.start();
    }
}
.stackSampler.start():
public void start() {
    if (mShouldSample.get()) {
        return;
    }
    mShouldSample.set(true);
 
    HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
   //調用handler的postDelayed方法傳遞一個runnable
    HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
            BlockCanaryInternals.getInstance().getSampleDelay());
}
該runnable 定義在AbstractSampler 抽象類中(cpuSampler、stackSampler)
private Runnable mRunnable= new Runnable() {
    @Override
    public void run() {
        doSample();
        if (mShouldSample.get()) {
            HandlerThreadFactory.getTimerThreadHandler()
                    .postDelayed(mRunnable, mSampleInterval);
        }
    }
};
     doSample(); 抽象方法,意味着stackSampler和 cpuSampler會有不一樣的實現
abstract void doSample();
 
stackSampler():
@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());
        }
        //在同步代碼塊中,以當前時間戳爲key,put放入到StackMap這個HashMap中
       sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
    }
}
private static final LinkedHashMap<Long, String> sStackMap = new LinkedHashMap<>();
思考:sStackMap爲何被定義成LinkedHashMap?
 
LinkedHashMap 和HashMap最大的區別?
  • LinkedHashMap可以記錄entry的插入順序!,插入的順序是已知的
  • HashMap不能記錄順序,未知的
 
cpuSampler():
@Override
protected void doSample() {
    BufferedReader cpuReader = null;
    BufferedReader pidReader = null;
 
    try {
        //經過bufferReader讀取 /proc 下的cpu文件
        cpuReader = new BufferedReader(new InputStreamReader(
                new FileInputStream("/proc/stat")), BUFFER_SIZE);
          ...
        if (mPid == 0) {
            mPid = android.os.Process.myPid();
        }
            //經過bufferReader讀取 /proc 下的內存文件
        pidReader = new BufferedReader(new InputStreamReader(
                new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
        String pidCpuRate = pidReader.readLine();
        if (pidCpuRate == null) {
            pidCpuRate = "";
        }
        parse(cpuRate, pidCpuRate);
           ...
    }
    //T2-T1 時間 是否 > 設定好的blockCanary 閥值時間的最大值
    
isBlock():
private boolean isBlock(long endTime) {
    //若是大於該mBlockThresholdMillis 返回 true 說明存在卡頓狀況,有可能產生阻塞現象
    return endTime - mStartTimestamp > mBlockThresholdMillis;
}
notifyBlockEvent():
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() {
            //調用監聽事件,它的回調方法在BlockCanaryInternals().setMonitor()回調方法中
           mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
        }
    });
}
                                             
4、BlockCanary補充知識
    4.1 ANR 定義
 ANR:Application Not responding 程序未響應
    超過預約時間仍然未響應就會形成ANR
    
監控工具:
    Activity Manager 和WindowManager 進行監控的
 
   4.2 ANR 分類 ***
    一、Service Timeout 
        服務若是在20秒內沒有完成執行的話,就會形成ANR
    二、BroadcastQueue Timeout 
        廣播若是在10秒內沒有完成執行的話,就會形成ANR
    三、InputDispatching Timeout
        輸入事件若是超過了5秒鐘就會形成ANR
        廣播若是在10秒內沒有完成執行的話,就會形成ANR
三、InputDispatching Timeout
    輸入事件(觸摸屏、點擊事件)若是超過了5秒鐘就會形成ANR
 
  4.3 ANR形成緣由 ***
    一、主線程作了一些耗時操做,好比網絡、數據庫獲取操做等
    二、主線程被其餘線程鎖住
        主線程所須要的資源在被其餘線程所使用中,致使主線程沒法獲取到該資源而形成主線程的阻塞,進而形成ANR現象
    三、cpu被其餘進程佔用
        這個進程沒有被分配到足夠的cpu資源
 
  4.4 ANR 解決措施 ***
   一、主線程讀取數據
        主線程禁止從網絡獲取數據,可是能夠從數據庫獲取數據,雖然未被禁止該操做,可是執行這類操做會形成掉幀現象
 
Tips : sharePreference 的commit () /apply()
   commit() :同步方法
    apply(): 異步方法
    因此主線程中儘可能不要調用 commit()方法,調用同步方法會阻塞主線程,儘可能經過apply()方法執行操做
 
        二、不要在BroadCastReceiver 的onReceive()方法中執行耗時操做
        onReceive():也是運行在主線程中的,後臺操做。
    經過IntentService()方法執行相關操做
 
   三、Activity的生命週期函數中都不該該有太耗時的操做
        該生命週期函數大多數執行於主線程中,及時是Service 服務或是 內容提供者ContentProvider也不要在onCreate()中執行耗時操做
 
   4.5 ANR 第三方監控工具 watchdog 檢測anr工具
    在linux 內核下,當Watchdog 啓動後,便設定了一個定時器,當出現故障時候,經過會讓Android系統重啓。因爲這種機制的存在,常常會出現一些system_server 進程被watchdog殺掉而發生手機重啓的問題。
    
    4.5.1 watchdog 初始化
Android 的watchdog 是一個單例線程 ,在System server時候就會初始化watchdog 。在watchdog初始化化時候會構建不少 HandlerChecker 
    大體能夠分爲兩類:
Monitor Checker,用於檢查是Monitor對象可能發生的死鎖, AMS, PKMS, WMS等核心的系統服務都是Monitor對象。
Looper Checker,用於檢查線程的消息隊列是否長時間處於工做狀態。Watchdog自身的消息隊列,Ui, Io, Display這些全局的消息隊列都是被檢查的對象。此外,一些重要的線程的消息隊列,也會加入到Looper Checker中,譬如AMS, PKMS,這些是在對應的對象初始化時加入的。
 private Watchdog(){
    …
    mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
                                        「foreground thread」,DEFAULT_TIMEOUT);
    mHandlerCheckers.add(mMonitorChecker);
    mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
                                        「main thread」,DEFAULT_TIMEOUT));
 
    mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
                                        「ui thread」,DEFAULT_TIMEOUT));
    mHandlerCheckers.add(new HandlerChecker(IOThread.getHandler(),
                                        「i/o thread」,DEFAULT_TIMEOUT)):
    mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
                                        「display thread」,DEFAULT_TIMEOUT));
    …
}
    兩類 HandlerChecker的側重點不一樣,Monitor Checker預警咱們不能長時間持有核心繫統服務的對象鎖,不然會阻塞不少函數的運行;Looper Checker 預警我媽不能長時間的霸佔消息隊列,不然其餘消息將得不處處理。
    這兩類都會致使系統卡住 ANR
 
public class ANRWatchDog extends Thread 繼承自Thread類 代表氣勢ANRWatchDog也是一個線程類
    // 簡單原理:一、建立一個ANR線程,不斷的向Ui線程經過handler post一個runnable任務
@Override
public void run() {
    setName("|ANR-WatchDog|");
 
    int lastTick;
    int lastIgnored = -1;
    while (!isInterrupted()) {
        lastTick =_tick; //保存_tick成員變量
        _uiHandler.post(_ticker);
            ps1:handler在構造方法中傳入了一個Looper.getMainLooper()主線程looper
        this._uiHandler = new Handler(Looper.getMainLooper()); 因此這個Ui looper 很顯然
關聯的主線程 ,因此能夠看出經過這個_uiHandler會將任務發送給主線程!
            ps2:_tick在構造方法中的runnable()函數中 的run方法裏每次會給_tick 計數 +1 是
                ANRWatchDog.this._tick = (ANRWatchDog.this._tick +1) %2147483647 ;
        try {
           //二、執行完上面的操做會讓線程睡眠固定的時間,給線程執行操做留出時間
            Thread.sleep(_timeoutInterval);
        }
        catch (InterruptedException e) {
            _interruptionListener.onInterrupted(e);
            return ;
        }
        //三、線程從新開始運行 檢測以前的post的任務是在執行了 兩個臨時變量是否相等,不相等表明Ui線程沒有阻塞
        相等表示 Ui線程沒有接收到Post runnable這個消息
       //四、剛纔保存的_tick變量是否等於 剛纔開啓子線程當中進行run()+1 是否不等於保存的變量。經過對比來查看post runnable 是否已經發送到了主線程,主線程是否已經執行了該消息,執行後_tick != lastTick
        // 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;
        }
    }
}
 
 4.6 newThread 
Android 系統中最簡單開啓一個線程操做的機制
    public void test (){
      new Thread(){
        @Override
        public void run(){
            super.run():
        }
    }.start()://start()線程處於就緒狀態,一旦調用start(),run方法裏就會執行到底沒法中途取消
區別:
start() :啓動線程,真正實現了多線程的運行,無需等待其中的run()方法執行完,能夠直接執行下面的代碼。就緒了一個線程並無直接就開始運行,告訴cpu能夠運行狀態
run() :代表將該方法做爲普同方法使用,想要執行run()方法後面的方法必定要等待裏面的方法體執行完才能進行。因此也就是代表調用run()方法還不是多線程。
 
new Thread 在Android 開啓線程的弊端
    一、多個耗時任務時就會開多個新線程,開銷是很是大的 ,形成很大的性能浪費
    二、若是在線程中執行循環任務,只能經過一我的爲的標識位Flag來控制它的中止
    三、沒有線程切換的接口
    四、若是從UI線程啓動一個子線程,則該線程優先級默認爲Default,這樣就和Ui線程級別相同了 體現不出開子線程的目的
   經過方法設定線程的優先級,將子線程的優先級將低爲後臺線程:  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUD);
 
 4.7 線程間通訊
 
Android 多線程編程時的兩大原則
    一、不要阻塞Ui線程
 
    二、不要在ui線程以外訪問Ui控件
 
線程間通訊的兩類:
   一、將任務從工做線程交還到主線程 --更新Ui組件,將結果交回給主線程
經過handlerMessage()方法將工做線程的結果拋出給主線程
 
//二、handler機制的looper 輪詢 MessageQueue,獲取到消息交給handleMessage()
    private Handler handle = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //三、主線程handleMessage(接收的消息)並執行
            super.handleMessage(msg);
        }
    };
//一、開啓一個線程,在run方法中sendMessage 發送消息。
public void handlerTest() {
    new Thread() {
        @Override
        public void run() {
            super.run();
            handle.sendEmptyMessage(0);
        }
    }.start();
 
在子線程中建立一個handler
new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        //在子線程中建立一個handler 必須要先調用Looper.prepare(),建立一個給這個Handler使用的looper。讓這個handler和建立好的looper關聯起來。這個handler就是處理子線程消息的。
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}.start();
 
(1)、final Runnable runnable = new Runnable() {
            @Override
            public void run() {
 
            }
        };
   (2)、new Thread() {
    @Override
    public void run() {
        super.run();
    //在子線程中使用handle.post(runnable)並無開啓了一個新的線程!
    ps:只是讓run()方法中的代碼拋到與handle相關的線程中去執行
        handle.post(runnable);
    }
}.start();
    //更新Ui的代碼建立在runnable當中,只有是若當前線程是Ui線程時纔會被當即執行
   (3)、 Activity.this.runOnUiThread(runnable);
    (4)、AsyncTask 內部經過線程池管理線程,經過handler切換Ui 線程和子線程
缺陷:AsyncTask 會持有當前Activity的引用,在使用的時候要把AsyncTask聲明爲靜態static,在AsyncTask內部持有外部Activity的弱引用,預防內存泄漏
    class MyAsyncTask extends AsyncTask<Void,Void,Void>{
        @Override 
            protected void doInBackground(Void ..params){
            //進行耗時操做,由於優先級時background因此不會阻塞Ui線程
        3.0之後默認api設置爲串行的緣由:
            當一個進程中開啓多個AsyncTask的時候,它會使用同一個線程池執行任務,因此又多個AsyncTask一塊兒並行執行的話,並且要在DIBG中訪問相同的資源,這時候就有可能出現數據不安全的狀況。設計成串行就不會有多線程數據不安全的問題。
            }
        @Override
            protected void onPostExecute(Void voida){
            }
        }
 
     二、將任務從主線程分配到工做線程 —耗時操做 分配給工做線程
 
        2.1 Thread /Runnable
缺陷: Runnable 做爲匿名內部類會持有外部類的引用 ,線程執行完前這個引用就會一直持有着,致使該Activity沒法被正常回收 ,進而形成內存泄漏。因此不建議使用這類方法開啓子線程
 
        2.2 HandlerThread
   繼承自Thread 類,適用於單線程或是異步隊列場景,耗時很少不會產生較大阻塞的狀況好比io流讀寫操做,並不適合於進行網絡數據的獲取!!!
優勢:
  • 有本身內部的Looper對象,
  • 經過Looper().prepare()能夠初始化looper對象
  • 經過Looper().loop()開啓looper循環
  • HandlerThread的looper對象傳遞給Handler對象,而後在handleMessage()方法中執行異步任務
 
ps:不管是Thread 仍是handlerThread只有開啓了start()才能表示這個線程是啓動的
 
handlerThread源碼:
根據需求設置線程的優先級:
    public HandlerThread(String name(線程名稱), int priority(線程優先級)){
        super(name);
    mPriority = priority;
    }
 
線程優先級:-20~19 優先級高的cpn資源得到更多,最高值是-20 ,正19是優先級最低的
 
//在run()中建立一個Looper對象;經過  Looper.prepare()和Looper.loop()構造一個循環的線程
@Override
public void run() {
    mTid = Process.myTid();
    //一、建立looper對象
    Looper.prepare();
    synchronized(this) {
       //二、將looper對象賦值給了handleThread的內部變量mLooper得到當前線程的looper
        mLooper = Looper.myLooper();
       //三、喚醒等待線程,通知線程競爭鎖(wait :釋放鎖)
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();//線程初始化操做
   //四、開啓線程隊列
    Looper.loop();
    mTid = -1;
}
//在Ui線程中調用getLooper()
public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
       //同步代碼塊:獲取當前子線程HandleThread所關聯的這個looper對象的實例
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
       //該線程是否存活,沒存活返回null
        while (isAlive() && mLooper == null) {
            try {
             //進入到阻塞階段,直到前面的同步代碼塊中looper對象建立成功,並調用notifyAll()喚醒該等待線程,而後纔會返回mLooper這個對象
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
ps:若是不使用wait() 和notifyAll()這套等待喚醒機制,就沒法保證在Ui線程中調用的getLooper()方法,調用的時候這個looper有可能已經被建立了。有可能面臨同步的問題。
 
思考:如何退出HandleThread的循環線程呢?
a、quit()
    public boolean quit(){
        Looper looper = getLooper();
        if(looper !=null){
            //清空全部MessageQueue的消息
           looper.quit():
            return true;
        }
        return false;
    }
 
b、quitSafely()
    public boolean quitSafely(){
        Looper looper = getLooper();
        //只會清空MessageQueue中全部的延遲消息,將全部的非延遲消息分發出去
        if(looper!=null){
            looper.quitSafely();
            return true;
        }
            return false;
    }
 
        2.3 IntentService
  • IntentService 是Service類的子類,擁有service全部的生命週期的方法
  • 會單獨開啓一個線程HandlerThread 來處理全部的Intent請求所對應的任務
  • 當IntentService處理完全部的任務後,它會在適當的時候自動結束服務
 
IntentService源碼
 內部實現是經過HandleThread 完成異步執行的
 
一、首先在onCreate()方法中:
//創建一個循環的工做線程
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        //mServiceLooper、mServiceHandler進行數據的讀取  
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
 
思考:爲何要用volatile 修飾 mServiceLooper 和mServiceHandler?
 
解析:保證每一次looper 和ServiceHandler它的讀取都是從主存中讀取,而不是從各個線程中的緩存去讀取。保證了兩個數據的安全性。
 
//handler接收到消息要回調的,onHandleIntent這個是在工做線程執行的
protected abstract void onHandleIntent(@Nullable Intent intent);
 
 
 4.8 多進程的優勢 與缺陷
 
    4.8.一、多進程的優勢:
        一、解決OOM問題——將耗時的工做放在輔助進程中避免主進程出現OOM
        二、合理利用內存,在適當的時候生成新的進程,在不須要的時候殺掉這個進程
        三、單一進程崩潰不會影響整個app的使用
        四、項目解耦、模塊化開發有好處
 
    4.8.二、多進程的缺陷:
     一、每次新進程的建立都會建立一個Application,形成屢次建立Application
        解析:根據進程名區分不一樣的進程,而後進行不一樣的初始化。不要在Application中進行過分靜態變量初始化
       
     二、文件讀寫潛在的問題
        解析:須要併發訪問的文件、本地文件、數據庫文件等;多利用接口而避免直接訪問文件
    文件鎖是基於進程和虛擬機的級別,若是從不一樣的進程訪問一個文件鎖,這個鎖是失效的!(sharePreference)
        
     三、靜態變量和單例模式徹底失效
        解析:在進程中間,咱們的內存空間是相互獨立的。虛擬方法區內的靜態變量也是相互獨立的,因爲靜態變量是基於進程的,因此單例模式會失效。在不一樣進程間訪問同一個相同類的靜態變量,他的值也不必定相同
        儘可能避免在多進程中頻繁的使用靜態變量
    
    四、線程同步機制徹底失效
        解析:Java的同步機制也是由虛擬機進行調度的。兩個進程會有兩個不一樣的虛擬機。同步關鍵字都將沒用意義
 
synchronized 和 voliate 的三大區別 ?
   一、阻塞線程與否
        解析:
voliate關鍵字本質上是告訴JVM虛擬機當前的變量在寄存器中的值是不肯定的,須要從主存中去獲取不會形成線程的阻塞
synchronized關鍵字指明的代碼塊只有當前線程能夠訪問它的臨界區的資源,其餘的線程就會被阻塞住
 
    二、使用範圍
voliate關鍵字 只是修飾變量的
synchronized關鍵字不只能夠修飾變量還能夠修飾方法
    
    三、原子性-操做不會再分(不會由於多線程形成操做順序的改變)
voliate關鍵字不具有原子性
synchronized關鍵字能夠保證變量的原子性
 
 4.9  voliate關鍵字和單例寫法
    單例模式:餓漢、懶漢、線程安全的 分析其中的問題
 
餓漢 單例模式:構造函數是私有的   
缺陷:消耗不少內存,無論是否已經實例化過instance;適合佔用內存比較小的單例
 
public class Singleton{
    private static Singleton instance = new Singleton();
    //其餘類不能經過構造函數實例化Singleton類
   private Singleton(){}
    //提供一個靜態的公共的獲取類的方法
    public static Singleton getInstance(){
        return instance;
    }
}
ps:無論instance是否建立完成,在加載類的時候都回去建立這個對象。
 
佔用內存大的時候就衍生出了,相比餓漢單例模式消耗的資源更少
缺陷:並無考慮多線程安全的問題,屢次調用帶有同步鎖的代碼塊累積的性能損害就愈來愈大
懶漢單例模式
public class SingletonLazy {
    private static SingletonLazy instance;
    private SingletonLazy(){}
    public static SingletonLazy getInstance(){
        //一、只有在instance是空的狀況纔會建立SingletonLazy對象
        if(null == instance){
            instance = new SingletonLazy();
    }
    return instance;
  }
    //二、加鎖只有一個線程能夠進入方法體進行對象的建立,實現了延遲加載不會每次加載類的時候都建立對象
    public static synchronized SingletonLazy getInstance1(){
        if(null == instance) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}
ps:經過synchronized關鍵字修飾的對象雖然會線程安全,可是會消耗更多的資源。
 
雙重效驗鎖單例模式:
優勢:解決了併發問題
缺陷:指令衝排序優化問題,致使初始化的時候它instance獲取的地址是不肯定的
也就是說這個instance有可能從單個緩存中獲取,每個線程的緩存是不同的,就會形成靜態的這個
instance不一致 形成單例的失靈
 
//雙重instance 判斷
 public class SingletonDouble{
    private static SingletonDouble instance = null;
    private SingletonDouble{}
   
    public static SingletonDouble getInstance(){
        if(instance == null){
        //大部分狀況下不須要進入同步代碼塊中
            synchronized (SingletonDouble.class){
                if(instance ==null){
                    instance = new SingletonDouble():
                }
            }
        }
        return instance;
      }
}
ps:若是同時兩個線程都走到  if(instance == null){的判斷,那麼它會認爲單例對象沒有被建立,而後兩個線程會同時進入同步代碼塊中。若是這時候判斷對象爲空的話,兩個線程會同時建立單例對象
 
voliate關鍵字的單例模式
爲了解決第三個的問題 出現了voliate關鍵字的使用
public class SingletonVoliate{
    //當單例對象被修飾成voliate後,每一次instance內存中的讀取都會從主內存中獲取,而不會從緩存中獲取,這樣就解決了雙重效驗鎖單例模式的缺陷
    private static volatile SingletonVoliate instance =null;
    private SingletonVoliate(){}
    public static SingletonVoliate getInstance(){
      if(instance ==null){
        synchronized(SingletonVoliate.class){
            if(instance ==null){
                instance = new SingletonVoliate():
            }
         }
      }
        return instance;
    }
}
相關文章
相關標籤/搜索