update time 2019年11月28日 15:27:20 / 添加 假目錄。哪位大佬有比較好的可以供他人查看的好目錄
View相關(guān)
View的繪制流程
自定義控件:
1、組合控件。這種自定義控件不需要我們自己繪制,而是使用原生控件組合成的新控件。如標(biāo)題欄。
2、繼承原有的控件。這種自定義控件在原生控件提供的方法外,可以自己添加一些方法。如制作圓角,圓形圖片。
3、完全自定義控件:這個View上所展現(xiàn)的內(nèi)容全部都是我們自己繪制出來的。比如說制作水波紋進(jìn)度條。
View的繪制流程:OnMeasure()——>OnLayout()——>OnDraw()
第一步:OnMeasure():測量視圖大小。從頂層父View到子View遞歸調(diào)用measure方法,measure方法又回調(diào)OnMeasure。
第二步:OnLayout():確定View位置,進(jìn)行頁面布局。從頂層父View向子View的遞歸調(diào)用view.layout方法的過程,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù),將子View放在合適的位置上。
第三步:OnDraw():繪制視圖。ViewRoot創(chuàng)建一個Canvas對象,然后調(diào)用OnDraw()。
View,ViewGroup事件分發(fā)
Touch事件分發(fā)中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關(guān)事件。View包含dispatchTouchEvent、onTouchEvent兩個相關(guān)事件。其中ViewGroup又繼承于View。
2.ViewGroup和View組成了一個樹狀結(jié)構(gòu),根節(jié)點(diǎn)為Activity內(nèi)部包含的一個ViwGroup。
3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,可以為0個。
4.當(dāng)Acitivty接收到Touch事件時,將遍歷子View進(jìn)行Down事件的分發(fā)。ViewGroup的遍歷可以看成是遞歸的。分發(fā)的目的是為了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結(jié)果返回true。
5.當(dāng)某個子View返回true時,會中止Down事件的分發(fā),同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進(jìn)行處理。由于子View是保存在ViewGroup中的,多層ViewGroup的節(jié)點(diǎn)結(jié)構(gòu)時,上級ViewGroup保存的會是真實(shí)處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結(jié)構(gòu)中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會返回true,被保存在ViewGroup0中。當(dāng)Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
6.當(dāng)ViewGroup中所有子View都不捕獲Down事件時,將觸發(fā)ViewGroup自身的onTouch事件。觸發(fā)的方式是調(diào)用super.dispatchTouchEvent函數(shù),即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發(fā)Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發(fā)。2.中止Up和Move事件向目標(biāo)View傳遞,使得目標(biāo)View所在的ViewGroup捕獲Up和Move事件。
view 事件分發(fā)流程
ViewGroup 時間分發(fā)流程
整體Activity - ViewGroup - view 分發(fā)流程
MeasureSpec 相關(guān)知識
MeasureSpec 是一個32位int值,高2位代表SpecMode(測量模式),低30位代表SpecSize( 某種測量模式下的規(guī)格大小)。通過寬測量值widthMeasureSpec和高測量值heightMeasureSpec決定View的大小
SpecMode 代表的三種測量模式分別為:
UNSPECIFIED:父容器不對View有任何限制,要多大有多大。常用于系統(tǒng)內(nèi)部。
EXACTLY(精確模式):父視圖為子視圖指定一個確切的尺寸SpecSize。對應(yīng)LyaoutParams中的match_parent或具體數(shù)值。
AT_MOST(最大模式):父容器為子視圖指定一個最大尺寸SpecSize,View的大小不能大于這個值。對應(yīng)LayoutParams中的wrap_content。
決定因素:值由子View的布局參數(shù)LayoutParams和父容器的MeasureSpec值共同決定。見下圖:
參考圖片 及講解地址
SurfaceView和View的區(qū)別
SurfaceView是從View基類中派生出來的顯示類,他和View的區(qū)別有:
- View需要在UI線程對畫面進(jìn)行刷新,而SurfaceView可在子線程進(jìn)行頁面的刷新,View適用于主動更新的情況,View頻繁刷新會阻塞主線程,導(dǎo)致界面卡頓
- SurfaceView在底層已實(shí)現(xiàn)雙緩沖機(jī)制,而View沒有,因此SurfaceView更適用于被動更新,需要頻繁刷新、刷新時數(shù)據(jù)處理量很大的頁面,而SurfaceView適用于
invalidate()和postInvalidate()的區(qū)別
invalidate()與postInvalidate()都用于刷新View,主要區(qū)別是invalidate()在主線程中調(diào)用,若在子線程中使用需要配合handler;而postInvalidate()可在子線程中直接調(diào)用。
我們通過 postInvalidate 如何在子線程中更新的
// 系統(tǒng)代碼
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
接下來我們看下
// 系統(tǒng)代碼
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
我們可以看到 postInvalidate它是向主線程發(fā)送個Message,然后handleMessage時,調(diào)用了invalidate()函數(shù)。(系統(tǒng)幫我們 寫好了 Handle部分)
Android 動畫
Android中的幾種動畫
幀動畫:指通過指定每一幀的圖片和播放時間,有序的進(jìn)行播放而形成動畫效果,比如想聽的律動條。
補(bǔ)間動畫:指通過指定View的初始狀態(tài)、變化時間、方式,通過一系列的算法去進(jìn)行圖形變換,從而形成動畫效果,主要有Alpha、Scale、Translate、Rotate四種效果。注意:只是在視圖層實(shí)現(xiàn)了動畫效果,并沒有真正改變View的屬性,比如滑動列表,改變標(biāo)題欄的透明度。
屬性動畫:在Android3.0的時候才支持,通過不斷的改變View的屬性,不斷的重繪而形成動畫效果。相比于視圖動畫,View的屬性是真正改變了。比如view的旋轉(zhuǎn),放大,縮小。
屬性動畫和補(bǔ)間動畫區(qū)別
- 補(bǔ)間動畫僅僅是 Parents View 對子View里面的畫布進(jìn)行操作,新位置并不響應(yīng)點(diǎn)擊事件,原位置響應(yīng)。
- 屬性動畫是通過修改view屬性實(shí)現(xiàn)動畫,新位置響應(yīng)點(diǎn)擊事件
屬性動畫為何在新位置還能響應(yīng)事件
ViewGroup 在 getTransformedMotionEvent() 方法中會通過子 View 的 hasIdentityMatrix() 方法來判斷子 View 是否應(yīng)用過位移、縮放、旋轉(zhuǎn)之類的屬性動畫。如果應(yīng)用過的話,那還會調(diào)用子 View 的 getInverseMatrix() 做「反平移」操作,然后再去判斷處理后的觸摸點(diǎn)是否在子 View 的邊界范圍內(nèi)。
屬性動畫原理
屬性動畫要求 動畫作用的對象提供該屬性的set方法,屬性動畫根據(jù)你傳遞的該熟悉的初始值和最終值,以動畫的效果多次去調(diào)用set方法,每次傳遞給set方法的值都不一樣,確切來說是隨著時間的推移,所傳遞的值越來越接近最終值。如果動畫的時候沒有傳遞初始值,那么還要提供get方法,因?yàn)橄到y(tǒng)要去拿屬性的初始值。
// 系統(tǒng)代碼
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
Handler 詳解
Handler的原理
Android中主線程是不能進(jìn)行耗時操作的,子線程是不能進(jìn)行更新UI的。所以就有了handler,它的作用就是實(shí)現(xiàn)線程之間的通信。
handler整個流程中,主要有四個對象,handler,Message,MessageQueue,Looper。當(dāng)應(yīng)用創(chuàng)建的時候,就會在主線程中創(chuàng)建handler對象。
對于Message :
在線程之間傳遞的消息,它的內(nèi)部持有Handler和Runnable的引用以及消息類型??梢允褂脀hat、arg1、arg2字段攜帶一些整型數(shù)據(jù),使用obj字段攜帶Object對象;其中有一個obtain()方法,該方法的內(nèi)部是先通過消息池獲取消息,沒有再創(chuàng)建,實(shí)現(xiàn)了對message對象的復(fù)用。其內(nèi)部有一個target引用,就是對Handler對象的引用,在Looper.loop方法中的消息處理就是通過message的target引用來調(diào)用Handler的dispatchMessage()方法來實(shí)現(xiàn)消息的處理。
對于Message Queue:
指的是消息隊(duì)列,是通過一個 單鏈表 的數(shù)據(jù)結(jié)構(gòu)維護(hù)消息列表的,在插入和刪除有優(yōu)勢。其中主要包括兩個操作:插入和讀取,讀取操作本身伴隨著刪除操作。插入操作是enqueueMessage()方法,就是插入一條消息到MessageQueue中;讀取操作是next()方法,它是一個無限循環(huán),如果有消息就返回并從單鏈表中移除;沒有消息就一直阻塞(此時主線程會釋放CPU進(jìn)入休眠狀態(tài))。
對于Looper:
Looper在消息機(jī)制中進(jìn)行消息循環(huán),像一個泵,不斷地從MessageQueue中查看是否有新消息并提取,交給handler處理。Handler機(jī)制一定要Looper,在線程中通過Looper.prepare()為當(dāng)前線程創(chuàng)建一個Looper,并使用Looper.loop()來開啟消息的讀取。為什么在平常Activity主線程使用時沒有使用到Looper呢?因?yàn)閷τ谥骶€程(UI線程),會自動創(chuàng)建一個Looper 驅(qū)動消息隊(duì)列獲取消息,所以Looper可以通過getMainLooper獲取到主線程的Looper。
通過quit/quitSafely可以退出Looper,區(qū)別在于quit會直接退出,quitSafely會把消息隊(duì)列已有的消息處理完畢后才退出Looper。
對于Handler
Handler可以發(fā)送和接收消息。發(fā)送消息(就是往MessageQueue里面插入一條Message)通過post方法和send方法,而post方法最終也是通過send方法來發(fā)送的,最終就會調(diào)用sendMessageAtTime這個方法(內(nèi)部就是調(diào)用MessageQueue的enqueueMessage()方法,往MessageQueue里面插入一條消息),同時也會給msg的target賦值為handler本身,進(jìn)入MessageQueue中。處理消息就是Looper調(diào)用loop()方法進(jìn)入無限循環(huán),獲取到消息后就會調(diào)用msg.target(Handler本身)的dispatchMessage()方法,進(jìn)而調(diào)用handlerMessage()方法處理消息。
Handler導(dǎo)致內(nèi)存泄露問題
一般我們寫Handler:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
當(dāng)使用內(nèi)部類(包括匿名類)來創(chuàng)建Handler的時候,Handler對象會隱式地持有一個外部類對象(通常是一個Activity)的引用,而常常在Activity退出后,消息隊(duì)列還有未被處理完的消息,此時activity依然被handler引用,導(dǎo)致內(nèi)存無法回收而內(nèi)存泄露。
在Handler中增加一個對Activity的弱引用(WeakReference):
static class MyHandler extends Handler {
WeakReference mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
如果在非自定義 Handler 情況下,還可以通過 Activity 生命周期來及時清除消息,從而及時回收 Activity
override fun onDestroy() {
super.onDestroy()
if (mHandler != null){
mHandler.removeCallbacksAndMessages(null)
}
}
Handler的post方法原理
mHandler.post(new Runnable()
{
@Override
public void run()
{
Log.e(“TAG”, Thread.currentThread().getName());
mTxt.setText(“yoxi”);
}
});
然后run方法中可以寫更新UI的代碼,其實(shí)這個Runnable并沒有創(chuàng)建什么線程,而是發(fā)送了一條消息,下面看源碼:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
最終和handler.sendMessage一樣,調(diào)用了sendMessageAtTime,然后調(diào)用了enqueueMessage方法,給msg.target賦值為handler,最終加入MessagQueue.
Handler 其他問題
-
Looper.loop()和MessageQueue.next()取延時消息時,主線程中使用死循環(huán)為什么不會卡死?
答: 在MessageQueue在取消息時,如果是延時消息就會計(jì)算得到該延時消息還需要延時多久nextPollTimeoutMillis。然后再繼續(xù)循環(huán)的時候,發(fā)現(xiàn)nextPollTimeoutMillis不等于0,就會執(zhí)行nativePollOnce阻塞線程nextPollTimeoutMillis毫秒,而阻塞了之后被喚醒的時機(jī)就是阻塞的時間到了或者又有新的消息添加進(jìn)來執(zhí)行enqueueMessage方法調(diào)用nativeWake喚醒阻塞線程,再繼續(xù)執(zhí)行獲取消息的代碼,如果有消息就返回,如果還是需要延時就繼續(xù)和上邊一樣阻塞。而Android所有的事件要在主線程中改變的都會通過主線程的Handler發(fā)送消息處理,所以就完全保證了不會卡死。
其中nativePollOnce的位置也有考究,剛好在synchronized的外邊,所以在阻塞的時候也能保證添加消息是可以執(zhí)行的,而取消息 時添加消息就需要等待。
MessageQueue是隊(duì)列嗎?
答: MessageQueue不是隊(duì)列,它內(nèi)部使用一個Message鏈表實(shí)現(xiàn)消息的存和取。Handler的postDelay,時間準(zhǔn)嗎?它用的是system.currentTime嗎?
答: 不準(zhǔn),因?yàn)閘ooper里邊從MessageQueue里取出來的消息執(zhí)行也是串行的,如果前一個消息是比較耗時的,那么等到執(zhí)行之前延時的消息時時間難免可能會超過延時的時間。postDelay時用的是System.uptimeMillis,也就是開機(jī)時間。子線程run方法中如何使用Handler?
答 : 先要使用Lopper.prepare方法,然后使用該looper創(chuàng)建一個Handler,最后調(diào)用Looper.loop方法;Looper.loop方法之后就不要執(zhí)行寫代碼了,因?yàn)槭莑oop是死循環(huán)除非退出,所以Handler的創(chuàng)建也必須寫在loop之前。ThreadLocal是如何實(shí)現(xiàn)一個線程一個Looper的?
答: Looper的使用最終都需要執(zhí)行l(wèi)oop方法,而loop方法中去獲取的Looper是從sThreadLocal中獲取的,所以Looper就需要和sThreadLocal建立關(guān)系,在不考慮反射的情況下,就只能通過Looper的prepare方法進(jìn)行關(guān)聯(lián),這里邊就會引入一個threadLocalMap,該對象又是和thread一一對應(yīng),而threadLocal的get方法實(shí)際使用的就是threadLocalMap的get方法,而key就是Looper中的靜態(tài)變量sThreadLocal,value則就是當(dāng)前l(fā)ooper對象,而prepare方法只能被執(zhí)行一次,也就保證了一個線程只有一個looper。ThreadLocalMap對key和value的存取和hashMap類似。假設(shè)先 postDelay 10ms, 再postDelay 1ms,這兩個消息會有什么不同的經(jīng)歷。
答: 先傳入一個延時為10ms的消息進(jìn)入MessageQueue中,因?yàn)樵撓⒀訒r,假設(shè)當(dāng)前消息隊(duì)列中沒有消息,則會直接將消息放入隊(duì)列,因?yàn)閘oop一直在取消息,但是這里有延時就會阻塞10ms,當(dāng)然這不考慮代碼執(zhí)行的時間;然后延時1ms的消息進(jìn)入時,會和之前的10ms的消息進(jìn)行比較,根據(jù)延時的大小進(jìn)行排序插入,延時小的在前邊,所以這時候就把1ms的消息放在10ms的前邊,然后喚醒,不阻塞,繼續(xù)執(zhí)行取消息的操作,發(fā)現(xiàn)還是有延時1ms,所以也會繼續(xù)阻塞1ms,直到阻塞1ms之后或者又有新的消息進(jìn)入隊(duì)列喚醒,直到獲取到1ms延時消息,在loop中,通過調(diào)用handler的dispatchMessage方法,判斷消息的callback或者Handler的callback不為null就回調(diào)對應(yīng)的callback,否則就執(zhí)行handler的handleMessage方法,我們就可以根據(jù)情況處理消息了;10ms的延時消息的處理也是一致,延時的時間到了就交給返回給looper,然后給handler處理。HandlerThread ?
答: HandlerThread是Thread的一個子類,只是內(nèi)部創(chuàng)建了一個Handler,這個Handler是子線程的handler,其中子線程的looper的創(chuàng)建和管理也提供了方法方便使用。你對 Message.obtain() 了解嗎?
答: Message.obtain其實(shí)是從緩沖的消息池中取出第一個消息來使用,避免消息對象的頻繁創(chuàng)建和銷毀;消息池其實(shí)是使用Message鏈表結(jié)構(gòu)實(shí)現(xiàn),在消息在loop中被handler分發(fā)消費(fèi)之后會執(zhí)行回收的操作,將該消息內(nèi)部數(shù)據(jù)清空并添加到消息鏈表最前邊。
多線程相關(guān)問題
如何創(chuàng)建多線程
- 繼承Thread類,重寫run函數(shù)方法
- 實(shí)現(xiàn)Runnable接口,重寫run函數(shù)方法
- 實(shí)現(xiàn)Callable接口,重寫call函數(shù)方法
- HandlerThread
- AsyncTask很老的一種= =
多線程間同步問題
volatile關(guān)鍵字,在get和set的場景下是可以的,由于get和set的時候都加了讀寫內(nèi)存屏障,在數(shù)據(jù)可見性上保證數(shù)據(jù)同步。但是對于++這種非原子性操作,數(shù)據(jù)會出現(xiàn)不同步
synchronized對代碼塊或方法加鎖,結(jié)合wait,notify調(diào)度保證數(shù)據(jù)同步
reentrantLock加鎖結(jié)合Condition條件設(shè)置,在線程調(diào)度上保障數(shù)據(jù)同步
CountDownLatch簡化版的條件鎖,在線程調(diào)度上保障數(shù)據(jù)同步
cas=compare and swap(set), 在保證操作原子性上,確保數(shù)據(jù)同步
參照UI線程更新UI的思路,使用handler把多線程的數(shù)據(jù)更新都集中在一個線程上,避免多線程出現(xiàn)臟讀
當(dāng)然如果只是部分變量存在多線程修改的可能性 建議使用 原子類AtomicInteger AtomicBoolean等 這樣會更方便一點(diǎn)。
Android 優(yōu)化
OOM
根據(jù)java的內(nèi)存模型會出現(xiàn)內(nèi)存溢出的內(nèi)存有堆內(nèi)存、方法區(qū)內(nèi)存、虛擬機(jī)棧內(nèi)存、native方法區(qū)內(nèi)存;一般說的OOM基本都是針對堆內(nèi)存;
對于堆內(nèi)存溢出主的根本原因有兩種
(1)app進(jìn)程內(nèi)存達(dá)到上限
(2)手機(jī)可用內(nèi)存不足,這種情況并不是我們app消耗了很多內(nèi)存,而是整個手機(jī)內(nèi)存不足
而我們需要解決的主要是 app的內(nèi)存達(dá)到上限
對于app內(nèi)存達(dá)到上限只有兩種情況
(1)申請內(nèi)存的速度超出gc釋放內(nèi)存的速度
(2)內(nèi)存出現(xiàn)泄漏,gc無法回收泄漏的內(nèi)存,導(dǎo)致可用內(nèi)存越來越少對于申請內(nèi)存速度超出gc釋放內(nèi)存的速度主要有2種情況
(1)往內(nèi)存中加載超大文件
(2)循環(huán)創(chuàng)建大量對象一般申請內(nèi)存的速度超出gc釋放內(nèi)存基本不會出現(xiàn),內(nèi)存泄漏才是出現(xiàn)問題的關(guān)鍵所在
導(dǎo)致內(nèi)存泄漏情況
內(nèi)存泄漏的根本原因在于生命周期長的對象持有了生命周期短的對象的引用
資源對象沒關(guān)閉造成的內(nèi)存泄漏(如: Cursor、File等)
全局集合類強(qiáng)引用沒清理造成的內(nèi)存泄漏( static 修飾的集合)
接收器、監(jiān)聽器注冊沒取消造成的內(nèi)存泄漏,如廣播,eventsbus
Activity 的 Context 造成的泄漏,可以使用 ApplicationContext
Handler 造成的內(nèi)存泄漏問題(一般由于 Handler 生命周期比其外部類的生命周期長引起的)
注1:ListView 的 Adapter 中緩存用的 ConvertView ,主要緩存的是 移除屏幕外的View,就算沒有復(fù)用,暫時 只會 內(nèi)存溢出,和泄漏還是有區(qū)別的。
注2 :Bitmap 對象到底要不要調(diào)用 recycle() 釋放內(nèi)存。結(jié)論 Android 3.0 以前需要,因?yàn)橄袼財(cái)?shù)據(jù)與對象本身分開存儲,像素?cái)?shù)據(jù)存儲在native層;對象存儲在java層。 3.0之后 像素?cái)?shù)據(jù)與Bitmap對象數(shù)據(jù)一起關(guān)聯(lián)存儲在Dalvik堆中。所以,這個時候,就可以考慮用GC來自動回收。所以我們不用的時候直接 將Bitmap對象設(shè)置為Null 即可。參考博客地址
我們列舉了 大部分常見的 內(nèi)存泄漏出現(xiàn)的時機(jī),那么我也簡要的列舉下 常見的避免內(nèi)存泄漏的方法(僅供參考);
為應(yīng)用申請更大內(nèi)存,把manifest上的largdgeheap設(shè)置為true
減少內(nèi)存的使用
①使用優(yōu)化后的集合對象,比如SpaseArray;
②使用微信的mmkv替代sharedpreference;
③對于經(jīng)常打log的地方使用StringBuilder來組拼,替代String拼接
④統(tǒng)一帶有緩存的基礎(chǔ)庫,特別是圖片庫,如果用了兩套不一樣的圖片加載庫就會出現(xiàn)2個圖片各自維護(hù)一套圖片緩存
⑤給ImageView設(shè)置合適尺寸的圖片,列表頁顯示縮略圖,查看大圖顯示原圖
⑥優(yōu)化業(yè)務(wù)架構(gòu)設(shè)計(jì),比如省市區(qū)數(shù)據(jù)分批加載,需要加載省就加載省,需要加載市就加載失去,避免一下子加載所有數(shù)據(jù)-
避免內(nèi)存泄漏
編碼規(guī)范上:
①資源對象用完一定要關(guān)閉,最好加finally
②靜態(tài)集合對象用完要清理
③接收器、監(jiān)聽器使用時候注冊和取消成對出現(xiàn)
④context使用注意生命周期,如果是靜態(tài)類引用直接用ApplicationContext
⑤使用靜態(tài)內(nèi)部類
⑥結(jié)合業(yè)務(wù)場景,設(shè)置軟引用,弱引用,確保對象可以在合適的時機(jī)回收
建設(shè)內(nèi)存監(jiān)控體系:
線下監(jiān)控:
①使用ArtHook檢測圖片尺寸是否超出imageview自身寬高的2倍
②編碼階段Memery Profile看app的內(nèi)存使用情況,是否存在內(nèi)存抖動,內(nèi)存泄漏,結(jié)合Mat分析內(nèi)存泄漏
線上監(jiān)控:
①上報(bào)app使用期間待機(jī)內(nèi)存、重點(diǎn)模塊內(nèi)存、OOM率
②上報(bào)整體及重點(diǎn)模塊的GC次數(shù),GC時間
③使用LeakCannery自動化內(nèi)存泄漏分析
ANR
Android系統(tǒng)中,AMS和WMS會檢測App的響應(yīng)時間,如果App在主線程進(jìn)行耗時操作,導(dǎo)致用戶的操作沒得到及時的響應(yīng) 就會報(bào)出 Application Not Response 的問題 即ANR 。
- activity 、鍵盤輸入事件和觸摸事件超過五秒
- 前臺廣播10秒沒有完成 后臺60秒
- 服務(wù)前臺20秒 后臺200秒
主要的 規(guī)避方案
解決籠統(tǒng)一下盡量使用 子線程,避免死鎖 的出現(xiàn),使用子線程來處理耗時操作或阻塞任務(wù)。服務(wù)內(nèi)容提供者盡量不要執(zhí)行太長時間的任務(wù)。