APP 啓動優化html
UI 繪製優化java
內存優化android
圖片壓縮git
長圖優化github
電量優化shell
Dex 加解密性能優化
如今只要是社交 APP 沒有哪一個開發者不想讓本身的 APP 永久常駐的,想要永久常駐除非大家家的實力很是雄厚,APP 用戶量很是大,那麼廠商都會主動來找你,把大家家的 APP 加入白名單。不然永久常駐是不可能甚至都不給你權限後臺運行。既然不能永久常駐,那麼咱們有沒有一個辦法可使咱們的 APP 不那麼容易被系統殺死勒?或者說是殺死後能主動喚醒,顯然是能夠的,下面咱們進入主題吧。
down 代碼 github.com/yangkun1992… ,將 live_library 放入本身工程
在 KeepAliveRuning onRuning 中實現須要保活的代碼
public class KeepAliveRuning implements IKeepAliveRuning {
/**這裏實現 Socket / 推送 等一些保活組件*/
@Override
public void onRuning() {
//TODO--------------------------------------------
Log.e("runing?KeepAliveRuning", "true");
}
@Override
public void onStop() {
Log.e("runing?KeepAliveRuning", "false");
}
}
複製代碼
開啓保活
public void start() {
//啓動保活服務
KeepAliveManager.toKeepAlive(
getApplication()
, HIGH_POWER_CONSUMPTION,
"進程保活",
"Process: System(哥們兒) 我不想被殺死",
R.mipmap.ic_launcher,
new ForegroundNotification(
//定義前臺服務的通知點擊事件
new ForegroundNotificationClickListener() {
@Override
public void foregroundNotificationClick(Context context, Intent intent) {
Log.d("JOB-->", " foregroundNotificationClick");
}
})
);
}
複製代碼
中止保活
KeepAliveManager.stopWork(getApplication());
複製代碼
長時間運行,不被殺死,若是被殺死雙進程會啓動死掉的進程
主動殺掉某一獨立運行的進程
若是內存不足,須要爲其餘用戶提供更緊急服務的進程又須要內存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷燬。 當這些組件須要再次運行時,系統將爲它們重啓進程。
決定終止哪一個進程時,Android 系統將權衡它們對用戶的相對重要程度。例如,相對於託管可見 Activity 的進程而言,它更有可能關閉託管屏幕上再也不可見的 Activity 的進程。 所以,是否終止某個進程的決定取決於該進程中所運行組件的狀態。 下面,咱們介紹決定終止進程所用的規則。
Android 系統將盡可能長時間地保持應用進程,但爲了新建進程或運行更重要的進程,最終須要移除舊進程來回收內存。 爲了肯定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每一個進程放入「重要性層次結構」中。 必要時,系統會首先消除重要性最低的進程,而後是重要性略遜的進程,依此類推,以回收系統資源。
重要性層次結構一共有 5 級。如下列表按照重要程度列出了各種進程(第一個進程最重要,將是最後一個被終止的進程):
名稱 | 歸納 | 回收狀態 |
---|---|---|
前臺進程 | 正在交互 | 只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們 |
可見進程 | 沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程 | 可見進程被視爲是極其重要的進程,除非爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。 |
服務進程 | 正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的進程。 |
除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會讓服務進程保持運行狀態。 |
後臺進程 | 對用戶不可見的 Activity 的進程 | 系統可能隨時終止它們 |
空進程 | 不含任何活動應用組件的進程 | 最容易爲殺死 |
進程的啓動分冷啓動和熱啓動,當用戶退出某一個進程的時候,並不會真正的將進程退出,而是將這個進程放到後臺,以便下次啓動的時候能夠立刻啓動起來,這個過程名爲熱啓動,這也是Android 的設計理念之一。這個機制會帶來一個問題,每一個進程都有本身獨立的內存地址空間,隨着應用打開數量的增多, 系統已使用的內存愈來愈大,就頗有可能致使系統內存不足。爲了解決這個問題,系統引入 LowmemoryKiller (簡稱 lmk ) 管理全部進程,根據必定策略來 kill 某個進程並釋放佔用的內存,保證系統的正常運行。
全部應用進程都是從 zygote 孵化出來的,記錄在 AMS 中mLruProcesses 列表中,由 AMS 進行統一管理,AMS 中會根據進程的狀態更新進程對應的 oom_adj 值,這個值會經過文件傳遞到 kernel 中去,kernel 有個低內存回收機制,在內存達到必定閥值時會觸發清理 oom_adj 值高的進程騰出更多的內存空間
minfree : 存放6個數值,單位內存頁面數 ( 一個頁面 4kb )
內存閾值 | 內存回收閾值 | 對應進程 |
---|---|---|
18432 | 72 M | .前臺進程(foreground) |
23040 | 90 M | 可見進程(visible) |
27648 | 108 M | 次要服務(secondary server) |
32256 | 126 M | 後臺進程(hidden) |
36864 | 144 M | 內容供應節點(content provider) |
46080 | 180 M | 空進程(empty) |
當內存到 180 M的時候會將空進程進行回收,當內存到 144 M 的時候把空進程回收完之後開始對內容供應節點進行回收,並非全部的內容供應節點都回收,而是經過判斷它的優先級進行回收,優先級是用 oom_adj 的值來表示,值越大回收的概率越高
adj 查看:
cat /sys/module/lowmemorykiller/parameters/adj
複製代碼
查看進程 adj 值:
adb shell ps
複製代碼
值越低越不易被回收,0 表明就不會被回收。
內存閾值在不一樣的手機上不同,一旦低於該值, Android 便開始按順序關閉進程. 所以 Android 開始結束優先級最低的空進程,即當可用內存小於 180MB (46080)
這裏可見 oom_adj 爲 0 是不會被回收的
後臺 oom_adj 爲 6 內存不足會被回收
鎖屏 oom_adj 開啓一像素 Activity 爲 0 至關於可見進程,不易被回收
實現原理:
監控手機鎖屏解鎖事件,在屏幕鎖屏時啓動 1 個像素透明的 Activity ,在用戶解鎖時將 Activity 銷燬掉,從而達到提升進程優先級的做用。
代碼實現
建立 onePxActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//設定一像素的activity
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.height = 1;
params.width = 1;
window.setAttributes(params);
//在一像素activity裏註冊廣播接受者 接受到廣播結束掉一像素
br = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
registerReceiver(br, new IntentFilter("finish activity"));
checkScreenOn("onCreate");
}
複製代碼
建立鎖屏開屏廣播接收
@Override
public void onReceive(final Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { //屏幕關閉的時候接受到廣播
appIsForeground = IsForeground(context);
try {
Intent it = new Intent(context, OnePixelActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(it);
} catch (Exception e) {
e.printStackTrace();
}
//通知屏幕已關閉,開始播放無聲音樂
context.sendBroadcast(new Intent("_ACTION_SCREEN_OFF"));
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { //屏幕打開的時候發送廣播 結束一像素
context.sendBroadcast(new Intent("finish activity"));
if (!appIsForeground) {
appIsForeground = false;
try {
Intent home = new Intent(Intent.ACTION_MAIN);
home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
home.addCategory(Intent.CATEGORY_HOME);
context.getApplicationContext().startActivity(home);
} catch (Exception e) {
e.printStackTrace();
}
}
//通知屏幕已點亮,中止播放無聲音樂
context.sendBroadcast(new Intent("_ACTION_SCREEN_ON"));
}
}
複製代碼
建立一個前臺服務用於提升 app 在按下 home 鍵以後的進程優先級
private void startService(Context context) {
try {
Log.i(TAG, "---》啓動雙進程保活服務");
//啓動本地服務
Intent localIntent = new Intent(context, LocalService.class);
//啓動守護進程
Intent guardIntent = new Intent(context, RemoteService.class);
if (Build.VERSION.SDK_INT >= 26) {
startForegroundService(localIntent);
startForegroundService(guardIntent);
} else {
startService(localIntent);
startService(guardIntent);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
複製代碼
注意若是開啓 startForegroundService 前臺服務,那麼必須在 5 s內開啓一個前臺進程的服務通知欄,否則會報 ANR
startForeground(KeepAliveConfig.FOREGROUD_NOTIFICATION_ID, notification);
複製代碼
在發生特定系統事件時,系統會發出廣播,經過在 AndroidManifest 中靜態註冊對應的廣播監聽器,便可在發生響應事件時拉活。可是從android 7.0 開始,對廣播進行了限制,並且在 8.0 更加嚴格。
以靜態廣播的形式註冊
<receiver android:name=".receive.NotificationClickReceiver">
<intent-filter>
<action android:name="CLICK_NOTIFICATION"></action>
</intent-filter>
</receiver>
複製代碼
有多個 app 在用戶設備上安裝,只要開啓其中一個就能夠將其餘的app 也拉活。好比手機裏裝了手 Q、QQ 空間、興趣部落等等,那麼打開任意一個 app 後,其餘的 app 也都會被喚醒。
將 Service 設置爲 START_STICKY,利用系統機制在 Service 掛掉後自動拉活
只要 targetSdkVersion 不小於5,就默認是 START_STICKY。 可是某些 ROM 系統不會拉活。而且通過測試,Service 第一次被異常殺死後很快被重啓,第二次會比第一次慢,第三次又會比前一次慢,一旦在短期內 Service 被殺死 4-5 次,則系統再也不拉起。
手機系統設置裏會有 「賬戶」 一項功能,任何第三方 APP 均可以經過此功能將數據在必定時間內同步到服務器中去。系統在將 APP 賬戶同步時,會將未啓動的 APP 進程拉活
JobScheduler 容許在特定狀態與特定時間間隔週期執行任務。能夠利用它的這個特色完成保活的功能,效果即開啓一個定時器,與普通定時器不一樣的是其調度由系統完成。
注意 setPeriodic 方法 在 7.0 以上若是設置小於 15 min 不起做用,可使用setMinimumLatency 設置延時啓動,而且輪詢
public static void startJob(Context context) {
try {
mJobScheduler = (JobScheduler) context.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(10,
new ComponentName(context.getPackageName(),
JobHandlerService.class.getName())).setPersisted(true);
/** * I was having this problem and after review some blogs and the official documentation, * I realised that JobScheduler is having difference behavior on Android N(24 and 25). * JobScheduler works with a minimum periodic of 15 mins. * */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//7.0以上延遲1s執行
builder.setMinimumLatency(KeepAliveConfig.JOB_TIME);
} else {
//每隔1s執行一次job
builder.setPeriodic(KeepAliveConfig.JOB_TIME);
}
mJobScheduler.schedule(builder.build());
} catch (Exception e) {
Log.e("startJob->", e.getMessage());
}
}
複製代碼
根據終端不一樣,在小米手機(包括 MIUI)接入小米推送、華爲手機接入華爲推送。
Native fork 子進程用於觀察當前 app 主進程的存亡狀態。對於 5.0以上成功率極低。
//若是選擇流氓模式,就默認接收了耗電的缺點,可是保活效果很好。
if (mediaPlayer == null && KeepAliveConfig.runMode == RunMode.HIGH_POWER_CONSUMPTION) {
mediaPlayer = MediaPlayer.create(this, R.raw.novioce);
mediaPlayer.setVolume(0f, 0f);
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
Log.i(TAG, "循環播放音樂");
play();
}
});
play();
}
複製代碼
兩個進程相互綁定 (bindService),若是有其中一個進程被殺,那麼另一個進程就會將被殺的進程從新拉起
進程保活就講到這裏了,最後我本身是結合裏面最靠譜的(Activity + Service 提權 + Service 機制拉活 + JobScheduler 定時檢測進程是否運行 + 後臺播放無聲文件 + 雙進程守護),而後組成了一個進程保活終極方案。 文章中只是部分代碼,感興趣的能夠下載 demo 試下保活效果。