android回收機制
㈠ Android App內存優化
內存優化就是對內存問題的一個預防和解決,做內存優化能讓應用掛得少、活得好和活得久。
掛的少:
「掛」指的是 Crash,內存問題導致 Crash 的具體表現就是內存溢出異常 OOM。
活得好:
活得好指的是使用流暢,Android 中造成界面卡頓的原因有很多種,其中一種就是由內存問題引起的。內存問題之所以會影響到界面流暢度,是因為垃圾回收(GC,Garbage Collection),在 GC 時,所有線程都要停止,包括主線程,當 GC 和繪制界面的操作同時觸發時,繪制的執行就會被擱置,導致掉幀,也就是界面卡頓。
活得久:
活得久指的是我們的應用在後台運行時不會被幹掉。Android 會按照特定的機制清理進程,清理進程時優先會考慮清理後台進程。清理進程的機制就是LowMemoryKiller。在 Android 中不同的進程有著不同的優先順序,當兩個進程的優先順序相同時,低殺會優先考慮幹掉消耗內存更多的進程。也就是如果我們應用佔用的內存比其他應用少,並且處於後台時,我們的應用能在後台活下來,這也是內存優化為我們應用帶來競爭力的一個直接體現。
內存佔用是否越少越好?
當系統 內存充足 的時候,我們可以多用 一些獲得更好的性能。當系統 內存不足 的時候,我們希望可以做到 」用時分配,及時釋放「。內存優化並不能一刀切。
我們都知道,應用程序的內存分配和垃圾回收都是由Android虛擬機完成的,在Android 5.0以下,使用的是Dalvik虛擬機,5.0及以上,則使用的是ART虛擬機。
Android虛擬機Dalvik和ART
1、內存區域劃分
詳細請看以下兩篇文章(建議全看):
java內存四大區_JVM內存區域劃分
Android 內存機制
2、內存回收
垃圾收集的標記演算法(找到垃圾):
垃圾收集演算法(回收垃圾):
引用類型:強引用、軟引用、弱引用、虛引用
對象的有效性=可達性+引用類型
JAVA垃圾回收機制-史上最容易理解看這一篇就夠了
Android:玩轉垃圾回收機制與分代回收策略
android中還存在低殺機制,這種情況屬於系統整機內存不足,直接把應用進程殺掉的情況。
Android後台殺死系列:LowMemoryKiller原理
1、內存溢出
系統會給每個App分配內存空間也就是heap size值,當app佔用的內存加上申請的內存超過這個系統分配的內存限額,最終導致OOM(OutOfMemory)使程序崩潰。
通過命令 getprop |grep dalvik.vm.heapsize 可以獲取系統允許的最大
注意:在設置了heapgrowthlimit的狀況下,單個進程可用最大內存為heapgrowthlimit值。在android開發中,若是要使用大堆,須要在manifest中指定android:largeHeap為true,這樣dvm heap最大可達heapsize。
關於heapsize & heapgrowthlimit
2、內存泄漏
Android系統虛擬機的垃圾回收是通過虛擬機GC機制來實現的。GC會選擇一些還存活的對象作為內存遍歷的根節點GC Roots,通過對GC Roots的可達性來判斷是否需要回收。內存泄漏就是 在當前應用周期內不再使用的對象被GC Roots引用,造成該對象無法被系統回收,以致該對象在堆中所佔用的內存單元無法被釋放而造成內存空間浪費,使實際可使用內存變小。簡言之,就是 對象被持有導致無法釋放或不能按照對象正常的生命周期進行釋放。
Android常見內存泄漏匯總
3、內存抖動
指的是在短時間內大量的新對象被實例化,運行時可能無法承載這樣的內存分配,在這種情況下就會導致垃圾回收事件被大量調用,影響到應用程序的UI和整體性能,最終可能導致卡頓和OOM。
常見情況:在一些被頻繁調用的方法內不斷地創建對象。例如在View 的onDraw方法內new 一些新的對象。
注意內存抖動也會導致 OOM,主要原因有如下兩點:
1、Android Studio Profiler
作用
優點
內存抖動問題處理實戰
理解內存抖動的概念的話,我們就能明白只要能找到抖動過程中所產生的對象及其調用棧,我們就能解決問題,剛好Android Studio 的Porfiler裡面的Memory工具就能幫我們記錄下我們操作過程中或靜止界面所產生的新對象,並且能清晰看到這些對象的調用棧。
選擇Profile 中 的Memory ,選擇 Record Java/Kotlin allocations,再點擊Record開始記錄, Record Java/Kotlin allocations 選項會記錄下新增的對象。
操作完成之後,點擊如圖所示的紅腦按鈕,停止記錄。
停止記錄後,我們就可以排序(點擊 Allocations可以排序)看看哪些對象或基本類型在短時間被頻繁創建多個,點擊這些新增的對象就可以看到它的完成的調用鏈了,進而就找找到導致內存抖動的地方在哪裡了。
2、利用DDMS 和 MAT(Memory Analyzer tool)來分析內存泄漏
我們利用工具進行內存泄漏分析主要是用對比法:
a.先打開正常界面,不做任何操作,先抓取一開始的堆文件。
b.一頓胡亂操作,回到原來操作前的界面。主動觸發一兩次GC,過10秒再抓取第二次堆文件。
c.通過工具對比,獲取胡亂操作後新增的對象,然後分析這些新增的對象。
DDMS作用:抓取堆文件,主動觸發GC。(其實也是可以用Android Studio 的Profile裡面的Memory工具來抓取堆文件的,但是我這邊在利用Profile 主動觸發gc 的時候會導致程序奔潰,也不知道是不是手機的問題,所以沒用Android Studio的Profiler)
MAT作用:對堆文件進行對比,找到多出的對象,找到對象的強引用調用鏈。
以下是詳細的過程:
步驟1.打開DDMS,選擇需要調試的應用,打開初始界面,點擊下圖的圖標(Dump Hprof File)先獲取一次堆文件。
步驟2.對應用隨便操作後,回到一開始的界面,先多觸發幾次GC ,點擊下圖的圖標(Cause Gc)來主動觸發GC,然後再次點擊 Dump Hprof File 圖標來獲取堆文件。
步驟3.通過Android Studio Profile 或者 DDMS mp 的堆文件無法在MAT 打開,需要藉助android sdk包下的一個工具hprof-conv.exe來轉換。
格式為 hprof-conv 舊文件路徑名 要轉換的名稱;
例如:hprof-conv 2022-04-13_17-54-40_827.hprof change.hprof
步驟4.把兩份堆文件導入MAT,然後選擇其中第二次獲取的堆文件,點擊 如圖所示的 Histogram查看。
步驟5.點擊下圖圖標,Compare To Another Heap Dump ,選擇另一份堆文件。
6.會得出下圖所示的 Hitogram 展示,我們主要看Objects 這一列。 如下圖所示 「+ 2」 則代表前面兩份堆文件對比,這個對象多了兩個,我們主要就是要分析這些多了出來,沒有被回收的對象。
7.加入我們從增加的對象中,看到了MainActivity ,則需要從一開始打開的Hitogram 展示裡面找到這個對象的調用棧。如下圖所示,搜索MainActivity
8.看到下圖所示解僱,然後滑鼠右鍵點擊下圖紅色圈圈著的MainActivity ,選擇 Merger Shortest Paths to Gc Roots ,再選擇 exclude all phantom/weak/soft etc.references ,就可以看到這個MainActivity 對象的強引用鏈,至此我們就可以找到MainActivity對象是被什麼引用導致無法回收了。
3、內存泄露檢測神器之LeakCanary(線下集成)
自行學習了解,接入簡單,使用簡單,基本可以解決大部分內存泄漏問題。
github地址 : https://github.com/square/leakcanary/
學習地址 : https://square.github.io/leakcanary/changelog/#version-22-2020-02-05
針對內存抖動的建議:
針對內存泄漏問題的建議:
針對內存溢出問題的建議(主要就是要減少內存佔用):
建議參考:
深入探索 Android 內存優化(煉獄級別)
對於 優化的大方向,我們應該優先去做見效快的地方,主要有以下三部分:內存泄漏、內存抖動、Bitmap。完善監控機制也是我們的重點,能幫助我們對內存問題快速分析和處理。
參考:
深入探索 Android 內存優化(煉獄級別)
㈡ Android 如何進行進程保活
每一個 Android 應用啟動後至少對應一個進程,有的是多個進程,而且主流應用中多個
進程的應用比例較大
對於任何一個進程,我們都可以通過 adb shell ps|grep <package_name>的方式來查看
它的基本信息
Android 中的進程跟封建社會一樣,分了三流九等,Android 系統把進程的劃為了如下
幾種(重要性從高到低),網上多位大神都詳細總結過(備註:嚴格來說是劃分了 6 種)。
場景:
1.某個進程持有一個正在與用戶交互的 Activity 並且該 Activity 正處於 resume 的
狀態。
2.某個進程持有一個 Service,並且該 Service 與用戶正在交互的 Activity 綁定。
3.某個進程持有一個 Service,並且該 Service 調用 startForeground()方法使之位於前台運行。
4.某個進程持有一個 Service,並且該 Service 正在執行它的某個生命周期回調方法,比如 onCreate()、 onStart()或 onDestroy()。
5.某個進程持有一個 BroadcastReceiver,並且該 BroadcastReceiver 正在執行其onReceive()方法。用戶正在使用的程序,一般系統是不會殺死前台進程的,除非用戶強制停止應用或者系統內存不足等極端情況會殺死。
場景:
1.擁有不在前台、但仍對用戶可見的 Activity(已調用 onPause())。
2.擁有綁定到可見(或前台)Activity 的 Service
用戶正在使用,看得到,但是摸不著,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程
不包含任何前台組件,一般系統也是不會殺死可見進程的,除非要在資源吃緊的情況下,
要保持某個或多個前台進程存活
場景
1.某個進程中運行著一個 Service 且該 Service 是通過 startService()啟動的,與用戶看見的界面沒有直接關聯。
在內存不足以維持所有前台進程和可見進程同時運行的情況下,服務進程會被殺死
場景:
在用戶按了"back"或者"home"後,程序本身看不到了,但是其實還在運行的程序,
比如 Activity 調用了 onPause 方法系統可能隨時終止它們,回收內存
場景:
某個進程不包含任何活躍的組件時該進程就會被置為空進程,完全沒用,殺了它只有好處沒壞處,第一個干它!
上面是進程的分類,進程是怎麼被殺的呢?系統出於體驗和性能上的考慮,app 在退到
後台時系統並不會真正的 kill 掉這個進程,而是將其緩存起來。打開的應用越多,後台緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制
來判斷要 kill 掉哪些進程,以騰出內存來供給需要的 app, 這套殺進程回收內存的機制
就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是內存閾值,我們可以使用
cat /sys/mole/lowmemorykiller/parameters/minfree 來查看某個手機的內存閾值。
其實系統在進程回收跟內存回收類似也是有一套嚴格的策略,可以
自己去了解,大概是這個樣子的,oom_adj 越大,佔用物理內存越多會被最先 kill 掉,OK,那麼現在對於進程如何保活這個問題就轉化成,如何降低 oom_adj 的值,以及如
何使得我們應用占的內存最少。
據說這個是手 Q 的進程保活方案,基本思想,系統一般是不會殺死前台進程的。所以要
使得進程常駐,我們只需要在鎖屏的時候在本進程開啟一個 Activity,為了欺騙用戶,
讓這個 Activity 的大小是 1 像素,並且透明無切換動畫,在開屏幕的時候,把這個 Activity
關閉掉,所以這個就需要監聽系統鎖屏廣播,我試過了,的確好使,如下。
如果直接啟動一個 Activity,當我們按下 back 鍵返回桌面的時候,oom_adj 的值是 8,
上面已經提到過,這個進程在資源不夠的情況下是容易被回收的。現在造一個一個像素
的 Activity。
為了做的更隱藏,最好設置一下這個 Activity 的主題,當然也無所謂了
在屏幕關閉的時候把 LiveActivity 啟動起來,在開屏的時候把 LiveActivity 關閉掉,所以
要監聽系統鎖屏廣播,以介面的形式通知 MainActivity 啟動或者關閉 LiveActivity。
現在 MainActivity 改成如下
按下 back 之後,進行鎖屏,現在測試一下 oom_adj 的值
果然將進程的優先順序提高了。
但是還有一個問題,內存也是一個考慮的因素,內存越多會被最先 kill 掉,所以把上面
的業務邏輯放到 Service 中,而 Service 是在另外一個 進程中,在 MainActivity 開啟這
個服務就行了,這樣這個進程就更加的輕量,
OK,通過上面的操作,我們的應用就始終和前台進程是一樣的優先順序了,為了省電,
系統檢測到鎖屏事件後一段時間內會殺死後台進程,如果採取這種方案,就可以避免了
這個問題。但是還是有被殺掉的可能,所以我們還需要做雙進程守護,關於雙進程守護,
比較適合的就是 aidl 的那種方式,但是這個不是完全的靠譜,原理是 A 進程死的時候,
B 還在活著,B 可以將 A 進程拉起來,反之,B 進程死的時候,A 還活著,A 可以將 B
拉起來。所以雙進程守護的前提是,系統殺進程只能一個個的去殺,如果一次性殺兩個,
這種方法也是不 OK 的。
事實上
那麼我們先來看看 Android5.0 以下的源碼,ActivityManagerService 是如何關閉在應用
退出後清理內存的
在應用退出後,ActivityManagerService 不僅把主進程給殺死,另外把主進程所屬的進
程組一並殺死,這樣一來,由於子進程和主進程在同一進程組,子進程在做的事情,也
就停止了。所以在 Android5.0 以後的手機應用在進程被殺死後,要採用其他方案。
這種大部分人都了解,據說這個微信也用過的進程保活方案,移步微信 Android 客戶端
後台保活經驗分享,這方案實際利用了 Android 前台 service 的漏洞。
原理如下
對於 API level < 18 :調用 startForeground(ID, new Notification()),發送空的
Notification ,圖標則不會顯示。
對於 API level >= 18:在需要提優先順序的 service A 啟動一個 InnerService,兩個服務
同時 startForeground,且綁定同樣的 ID。Stop 掉 InnerService ,這樣通知欄圖標即
被移除。
public class KeepLiveService extends Service{
public static final int NOTIFICATION_ID=0x11;
public KeepLiveService() {
}
@Override
public IBinder onBind(Intent intent){
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate(); //API 18 以下,直 接發 送 Notification 並 將 其 置 為 前 台
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN_MR2){
startForeground(NOTIFICATION_ID,new Notification());
} else { //API 18 以上,發送 Notification 並將其置為前台後,啟動 InnerService
Notification.Builder builder=new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}
public class InnerService extends Service{
@Override public IBinder onBind(Intent intent) {
return null;
}
@Override public void onCreate() {
super.onCreate(); //發送與 KeepLiveService中 ID 相同的 Notification,然後將其取消並取消自己的前台顯示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);startForeground(NOTIFICATION_ID,
builder.build());
new Handler().postDelayed(new Runnable() {
@Override public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},
100);
}
}
}
在沒有採取前台服務之前,啟動應用,oom_adj 值是 0,按下返回鍵之後,變成 9(不
同 ROM 可能不一樣)
相互喚醒的意思就是,假如你手機里裝了支付寶、淘寶、天貓、UC 等阿里系的 app,
那麼你打開任意一個阿里系的 app 後,有可能就順便把其他阿里系的 app 給喚醒了。
這個完全有可能的。此外,開機,網路切換、拍照、拍視頻時候,利用系統產生的廣播
也能喚醒 app,不過 Android N 已經將這三種廣播取消了。
如果應用想保活,要是 QQ,微信願意救你也行,有多少手機上沒有 QQ,微信呢?或
者像友盟,信鴿這種推送 SDK,也存在喚醒 app 的功能。
拉活方法
JobSheler是作為進程死後復活的一種手段,
native進程方式最大缺點是費電,Native
進程費電的原因是感知主進程是否存活有兩種實現方式,在 Native 進程中通過死循環
或定時器,輪訓判斷主進程是否存活,當主進程不存活時進行拉活。其次 5.0 以上系統
不支持。 但是 JobSheler 可以替代在 Android5.0 以上 native 進程方式,這種方式即
使用戶強制關閉,也能被拉起來,親測可行。
JobSheler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheler();
}
public void startJobSheler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5); builder.setPersisted(true); JobScheler jobScheler =(JobScheler)
this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheler.schele(builder.build());
}
catch
(Exception ex)
{ ex.printStackTrace(); } }
@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
} @Override public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
這個是系統自帶的,onStartCommand 方法必須具有一個整形的返回值,這個整形的返
回值用來告訴系統在服務啟動完畢後,如果被 Kill,系統將如何操作,這種方案雖然可
以,但是在某些情況 or 某些定製 ROM 上可能失效,我認為可以多做一種保保守方案。
1.START_STICKY
如果系統在 onStartCommand 返回後被銷毀,系統將會重新創建服務並依次調用
onCreate 和 onStartCommand(注意:根據測試 Android2.3.3 以下版本只會調用
onCreate 根本不會調用 onStartCommand,Android4.0 可以辦到),這種相當於服務
又重新啟動恢復到之前的狀態了)。
2.START_NOT_STICKY
如果系統在 onStartCommand 返回後被銷毀,如果返回該值,則在執行完
onStartCommand 方法後如果 Service 被殺掉系統將不會重啟該服務
3.START_REDELIVER_INTENT
START_STICKY 的兼容版本,不同的是其不保證服務被殺後一定能重啟。
4.相比與粘性服務與系統服務捆綁更厲害一點,這個來自愛哥的研究,這里說的系統服務
很好理解,比如 NotificationListenerService,NotificationListenerService 就是一個監聽
通知的服務,只要手機收到了通知,NotificationListenerService 都能監聽到,即時用戶
把進程殺死,也能重啟,所以說要是把這個服務放到我們的進程之中,那麼就可以呵呵
了
所以你的應用要是有消息推送的話,那麼可以用這種方式去欺騙用戶。
㈢ Android 虛擬機 | 垃圾回收機制
這篇文章的內容會涉及以下前置 / 相關知識,貼心的我都幫你准備好了,請享用~
並不是 Java 虛擬機管理的所有區域都需要垃圾回收,線程獨占的區域會隨著線程結束而銷毀,不需要垃圾回收。因此垃圾回收機制需要管理的區域是:
在實踐中,當代絕大多數垃圾收集器都採用了 「分代收集模型」 :
—— 圖片引用自網路
在標準的垃圾回收演算法中,在垃圾回收線程進行標記 - 清理 / 整理 / 復制的過程中需要 stop-the-world,這是為了保證能夠徹底清理所有垃圾對象。但是這種做法卻會導致虛擬機的吞吐量降低。
在追求響應速度的系統上,希望垃圾收集器暫停時間盡可能小,為此發展出了允許回收線程與用戶線程並發運行的垃圾收集器 —— CMS(並發標記清除)。主要工作過程分為 4 個步驟:
更多內容: Java 垃圾回收: Java 虛擬機 | 垃圾回收機制
Dalvik與ART虛擬機的GC調試日誌
JVM怎麼保證gc效率跟線程運行效率的 ?