一、背景
- 復雜的項目:代碼復雜度的增加,第三方庫的引入,某個Activity or Fragment與其他相關聯的類或是方法 或是子模塊 。這時候針對某一個Activity進行查找Ui卡頓的問題,然后進行操作是十分困難的!
- 卡頓積累到一定程度造成Activity Not Response,只有在ANR現象下,才能獲取到當前堆棧信息
BlockCanary
————Android 平台-非侵入式的性能監控組件
————針對輕微的UI卡頓及不流暢現象的檢查工具
1.1、UI卡頓原理——性能優化的大問題之一
最優策略:
60fps——>16ms/幀(一幅圖像)
16ms內是否能完成一次操作
准則:盡量保證每次在16ms內處理完單次的所有的CPU與GPU計算、繪制、渲染等操作,否則會造成丟幀卡頓問題
1.2、UI卡頓常見原因:
1、Ui線程中做輕微耗時操作
系統為App創建的ActivityThread的作用,將事件分發給合適的view 或是widget;同時是應用和Ui交互的主線程
子線程通知主線程Ui完成可以顯示:
- handler
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable,long)-延時post
2、布局Layout過於復雜,無法再16ms內完成渲染
3、View過度繪制-負載加劇cpu or gpu
4、View頻繁的觸發measure、layout事件,導致在繪制Ui的時候時間加巨,損耗加巨,造成View頻繁渲染
5、內存頻繁觸發GC過多 ——在同一幀頻繁的創建臨時變量加劇內存的浪費和渲染的增加
ps:虛擬機在執行Gc垃圾回收的時候,會暫停所有的線程(包括ui線程)。只有當Gc垃圾回收器完成工作才能繼續開啟線程繼續執行工作
- 盡量減少臨時變量的創建———代碼優化的小點之一
二、BlockCanary 使用
2.1、引入依賴:
implementation'com.github.markzhai:blockcanary-android:1.5.0'
2.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;
}
…...
}
三、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.
核心處理消息方法
*/
//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 流程圖

1、通過handler.postMessage() 發送消息給主線程
2、sMainLooper.looper() 通過輪詢器不斷的輪詢MessageQueue中的消息隊列
3、通過Queue.next() 獲取需要的消息
4、計算出調用dispatchMessage()方法中前后的時間差值
5、通過T2-T1的時間差來判斷是否超過設定好的時間差的閥值
6、如果T2-T1 時間差 > 閥值 ,就dump 出information來定位Ui卡頓

7、如果執行完dispatchMessage()后延遲了閥值的0.8倍的話,進行了延遲發送 也會dump出需要的信息(堆棧信息,cpu使用率、內存信息)
三、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);
}
});
}
四、BlockCanary補充知識
4.1 ANR 定義
ANR:Application Not responding 程序未響應
超過預定時間仍然未響應就會造成ANR
監控工具:
Activity Manager 和WindowManager 進行監控的
4.2 ANR 分類 ***
1、Service Timeout
服務如果在20秒內沒有完成執行的話,就會造成ANR
2、BroadcastQueue Timeout
廣播如果在10秒內沒有完成執行的話,就會造成ANR
3、InputDispatching Timeout
輸入事件如果超過了5秒鍾就會造成ANR
廣播如果在10秒內沒有完成執行的話,就會造成ANR
3、InputDispatching Timeout
輸入事件(觸摸屏、點擊事件)如果超過了5秒鍾就會造成ANR
4.3 ANR造成原因 ***
1、主線程做了一些耗時操作,比如網絡、數據庫獲取操作等
2、主線程被其他線程鎖住
主線程所需要的資源在被其他線程所使用中,導致主線程無法獲取到該資源而造成主線程的阻塞,進而造成ANR現象
3、cpu被其他進程占用
這個進程沒有被分配到足夠的cpu資源
4.4 ANR 解決措施 ***
1、主線程讀取數據
主線程禁止從網絡獲取數據,但是可以從數據庫獲取數據,雖然未被禁止該操作,但是執行這類操作會造成掉幀現象
Tips : sharePreference 的commit () /apply()
commit() :同步方法
apply(): 異步方法
所以主線程中盡量不要調用 commit()方法,調用同步方法會阻塞主線程,盡量通過apply()方法執行操作
2、不要在BroadCastReceiver 的onReceive()方法中執行耗時操作
onReceive():也是運行在主線程中的,后台操作。
通過IntentService()方法執行相關操作
3、Activity的生命周期函數中都不應該有太耗時的操作
該生命周期函數大多數執行於主線程中,及時是Service 服務或是 內容提供者ContentProvider也不要在onCreate()中執行耗時操作
4.5 ANR 第三方監控工具 watchdog 檢測anr工具
watchdog :
https://github.com/SalomonBrys/ANR-WatchDog
在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也是一個線程類
// 簡單原理:1、創建一個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 {
//2、執行完上面的操作會讓線程睡眠固定的時間,給線程執行操作留出時間
Thread.sleep(_timeoutInterval);
}
catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
//3、線程重新開始運行 檢測之前的post的任務是在執行了 兩個臨時變量是否相等,不相等代表Ui線程沒有阻塞
相等表示 Ui線程沒有接收到Post runnable這個消息
//4、剛才保存的_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 開啟線程的弊端
1、多個耗時任務時就會開多個新線程,開銷是非常大的 ,造成很大的性能浪費
2、如果在線程中執行循環任務,只能通過一個人為的標識位Flag來控制它的停止
3、沒有線程切換的接口
4、如果從UI線程啟動一個子線程,則該線程優先級默認為Default,這樣就和Ui線程級別相同了 體現不出開子線程的目的
通過方法設定線程的優先級,將子線程的優先級將低為后台線程: Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUD);
4.7 線程間通信
Android 多線程編程時的兩大原則
1、不要阻塞Ui線程
2、不要在ui線程之外訪問Ui控件
線程間通信的兩類:
1、將任務從工作線程交還到主線程 --更新Ui組件,將結果交回給主線程
通過handlerMessage()方法將工作線程的結果拋出給主線程
//2、handler機制的looper 輪詢 MessageQueue,獲取到消息交給handleMessage()
private Handler handle = new Handler() {
@Override
public void handleMessage(Message msg) {
//3、主線程handleMessage(接收的消息)並執行
super.handleMessage(msg);
}
};
//1、開啟一個線程,在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、將任務從主線程分配到工作線程 —耗時操作 分配給工作線程
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();
//1、創建looper對象
Looper.prepare();
synchronized(this) {
//2、將looper對象賦值給了handleThread的內部變量mLooper獲得當前線程的looper
mLooper = Looper.myLooper();
//3、喚醒等待線程,通知線程競爭鎖(wait :釋放鎖)
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();//線程初始化操作
//4、開啟線程隊列
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 完成異步執行的
1、首先在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.1、多進程的優點:
1、解決OOM問題——將耗時的工作放在輔助進程中避免主進程出現OOM
2、合理利用內存,在適當的時候生成新的進程,在不需要的時候殺掉這個進程
3、單一進程崩潰不會影響整個app的使用
4、項目解耦、模塊化開發有好處
4.8.2、多進程的缺陷:
1、每次新進程的創建都會創建一個Application,造成多次創建Application
解析:根據進程名區分不同的進程,然后進行不同的初始化。不要在Application中進行過度靜態變量初始化
2、文件讀寫潛在的問題
解析:需要並發訪問的文件、本地文件、數據庫文件等;多利用接口而避免直接訪問文件
文件鎖是基於進程和虛擬機的級別,如果從不同的進程訪問一個文件鎖,這個鎖是失效的!(sharePreference)
3、靜態變量和單例模式完全失效
解析:在進程中間,我們的內存空間是相互獨立的。虛擬方法區內的靜態變量也是相互獨立的,由於靜態變量是基於進程的,所以單例模式會失效。在不同進程間訪問同一個相同類的靜態變量,他的值也不一定相同
盡量避免在多進程中頻繁的使用靜態變量
4、線程同步機制完全失效
解析:Java的同步機制也是由虛擬機進行調度的。兩個進程會有兩個不同的虛擬機。同步關鍵字都將沒用意義
synchronized 和 voliate 的三大區別 ?
1、阻塞線程與否
解析:
voliate關鍵字本質上是告訴JVM虛擬機當前的變量在寄存器中的值是不確定的,需要從主存中去獲取不會造成線程的阻塞
synchronized關鍵字指明的代碼塊只有當前線程可以訪問它的臨界區的資源,其他的線程就會被阻塞住
2、使用范圍
voliate關鍵字 只是修飾變量的
synchronized關鍵字不僅可以修飾變量還可以修飾方法
3、原子性-操作不會再分(不會因為多線程造成操作順序的改變)
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(){
//1、只有在instance是空的情況才會創建SingletonLazy對象
if(null == instance){
instance = new SingletonLazy();
}
return instance;
}
//2、加鎖只有一個線程可以進入方法體進行對象的創建,實現了延遲加載不會每次加載類的時候都創建對象
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;
}
}