Android效能UI卡頓
UI卡頓原理
Android當中保持60幀以上算是流暢:60fps ——>16ms/幀(數字量化)
準則:儘量保證每次在16ms內處理完所有的cpu與Gpu計算、繪製、渲染等操作,否則會造成丟幀卡頓等問題
原因:在主執行緒中執行耗時工作,把事件分發給合適的view或者widget的
- 在子執行緒中處理
handler
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
複製程式碼
-
佈局Layout過於複雜,無法在16ms內完成渲染
-
View的過度繪製
-
View頻繁的觸發measure、layout
-
記憶體頻繁的觸發GC過多(STW,建立太多的臨時變數)實現的核心原理
###Blockcanary工具 在主執行緒ActivityThread中的 dispatchMessge(msg)上下方列印時間,計算閥值,超過了就列印
-
postMessage(Handler)
-
Queue.next()獲取我們的訊息
-
是否超過我們的閥值 (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的分類
-
watchDog-anr是如何監控anr的?
- Service Timeout(5秒)
- BroadcastQueue Timeout(10秒)
- inputDispatch Timeout (5秒)
-
主執行緒耗時操作 ()
-
主執行緒被鎖住
-
cpu被其它的程序佔用
如何解決
- 主執行緒讀取資料
- sharepreference的 commit(), 子執行緒中 apply()替換
- Broadcast的reciever不能進行耗時操作, IntentService中操作
- Activity的生命週期中不應該有太耗時的操作
WatchDog監控
建立一個監控執行緒
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可以執行。
存在的問題:
- 多個耗時任務開啟多個執行緒,開銷是非常大的
- 如果線上程中執行迴圈任務,只能通過一個Flag開控制它的停止
- 沒有執行緒切換的介面
- 如果從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()中訪問相同的資源,執行緒池會造成執行緒的併發訪問造成執行緒安全問題,所以設計成序列的,就不會有執行緒安全問題。
將任務從主執行緒拋到工作執行緒
- Thread/Runnable
- HandlerThread
- 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
-
intentService是Service類的子類
-
單獨開啟一個執行緒來處理所有的Intent請求所對應的任務
-
當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); } 複製程式碼
多程序的好處
- 解決OOM問題(Android會限制單一程序的記憶體大小)
- 合理的利用記憶體
- 單一程序奔潰不會影響整體應用
- 專案解耦、模組化開發
問題
- Application會多次建立的問題(根據程序名進行不同的初始化,不要做過多的靜態物件初始化)
- 檔案讀寫潛在的問題(Java中檔案鎖基於Java 虛擬機器、程序存在的,特別Sharepreference)
- 靜態變數和單例模式完全失效。(多程序中不要過多的用靜態變數,失效了)
- 執行緒同步都會失效,這些都是在虛擬機器、程序的基礎上。
synchronized和volidate
- 阻塞執行緒與否
- 使用範圍
- 原子性(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;
}
}
複製程式碼