由於一些需求的原因需要讓自己App長時間在后台。雖然這樣的做法是很Orz的,但是由於項目需求有時候不得不這樣做。QQ、微信之所以沒被第三方應用直接給kill掉,從市場原因騰訊的軟件已經深入人心,很多廠家或是軟件早就把這些軟件加入白名單中!
但是作為App的開發人員來說肯定是強烈建議不要這么做,不僅僅從用戶角度考慮,作為Android開發者也有責任去維護Android的生態環境。就是因為越來越多的這樣的占用系統內存的進程越來越多才使得android手機越來越卡,android碎片化的原因越來越多的第三方App可以通過清理內存的方式將后台進程給直接Kill掉來增加run可用內存。
但是通過技術層面如何解決這個不被kill掉進程的問題呢?
- Service設置成START_STICKY,kill 后會被重啟(等待5秒左右),重傳Intent,保持與重啟前一樣。(PM1中提供參考)
- 通過 startForeground將進程設置為前台進程,做前台服務,優先級和前台應用一個級別,除非在系統內存非常缺,否則此進程不會被 kill
- 雙進程Service:讓2個進程互相保護,其中一個Service被清理后,另外沒被清理的進程可以立即重啟進程
- QQ黑科技:在應用退到后台后,另起一個只有 1 像素的頁面停留在桌面上,讓自己保持前台狀態,保護自己不被后台清理工具殺死
- 在已經root的設備下,修改相應的權限文件,將App偽裝成系統級的應用(Android4.0系列的一個漏洞,已經確認可行)
- Android系統中當前進程(Process)fork出來的子進程,被系統認為是兩個不同的進程。當父進程被殺死的時候,子進程仍然可以存活,並不受影響。鑒於目前提到的在Android-Service層做雙守護都會失敗,我們可以fork出c進程,多進程守護。死循環在那檢查是否還存在,具體的思路如下(Android5.0以下可行)
- 用C編寫守護進程(即子進程),守護進程做的事情就是循環檢查目標進程是否存在,不存在則啟動它。
- 在NDK環境中將1中編寫的C代碼編譯打包成可執行文件(BUILD_EXECUTABLE)。
- 主進程啟動時將守護進程放入私有目錄下,賦予可執行權限,啟動它即可。
- 聯系廠商,加入白名單
PM1:
for (RunningServiceInfo service :manager.getRunningServices(Integer.MAX_VALUE)) {
if("com.XXX.XXX.XXXService".equals(service.service.getClassName())){
isServiceRunning = true;
}
}
if (!isServiceRunning ) {
Intent i = new Intent(context, com.XXX.XXX.XXXService.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startService(i);
}
Notification notification =
new
Notification(R.drawable.icon,
"服務開啟"
, System.currentTimeMillis());
notification.flags|= Notification.FLAG_NO_CLEAR;
notification.flags=Notification.FLAG_ONGOING_EVENT;
Intent notificationIntent =
new
Intent(
this
, MainActivity.
class
);
PendingIntent pendingIntent = PendingIntent.getActivity(
this
,
0
, notificationIntent,
0
);
notification.setLatestEventInfo(
this
,
"service"
,
"防止服務被任務管理器所殺"
, pendingIntent);
startForeground(ONGOING_NOTIFICATION, notification);
后來一次 做自定義Notification的時候,通知欄沒有顯示通知,查看后發現 service 也沒被kill 。所以就進一步去研究了下 最后發現 只用兩行代碼就能保持服務不會被kill,並且不會有通知欄通知代碼如下:
|
1
2
|
Notification notification =
new
Notification();
startForeground(
1
, notification);
|
實例代碼如下:
public
class
TestService
extends
Service {
private
static
final
Class[] mStartForegroundSignature =
new
Class[] {
int
.
class
, Notification.
class
};
private
static
final
Class[] mStopForegroundSignature =
new
Class[] {
boolean
.
class
};
private
NotificationManager mNM;
private
Method mStartForeground;
private
Method mStopForeground;
private
Object[] mStartForegroundArgs =
new
Object[
2
];
private
Object[] mStopForegroundArgs =
new
Object[
1
];
@Override
public
IBinder onBind(Intent intent) {
return
null
;
}
@Override
public
void
onCreate() {
super
.onCreate();
mNM = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
try
{
mStartForeground = TestService.
class
.getMethod(
"startForeground"
,
mStartForegroundSignature);
mStopForeground = TestService.
class
.getMethod(
"stopForeground"
,
mStopForegroundSignature);
}
catch
(NoSuchMethodException e) {
mStartForeground = mStopForeground =
null
;
}
// 我們並不需要為 notification.flags 設置 FLAG_ONGOING_EVENT,因為
// 前台服務的 notification.flags 總是默認包含了那個標志位
Notification notification =
new
Notification();
// 注意使用 startForeground ,id 為 0 將不會顯示 notification
startForegroundCompat(
1
, notification);
}
@Override
public
void
onDestroy() {
super
.onDestroy();
stopForegroundCompat(
1
);
}
// 以兼容性方式開始前台服務
private
void
startForegroundCompat(
int
id, Notification n) {
if
(mStartForeground !=
null
) {
mStartForegroundArgs[
0
] = id;
mStartForegroundArgs[
1
] = n;
try
{
mStartForeground.invoke(
this
, mStartForegroundArgs);
}
catch
(IllegalArgumentException e) {
e.printStackTrace();
}
catch
(IllegalAccessException e) {
e.printStackTrace();
}
catch
(InvocationTargetException e) {
e.printStackTrace();
}
return
;
}
mNM.notify(id, n);
}
// 以兼容性方式停止前台服務
private
void
stopForegroundCompat(
int
id) {
if
(mStopForeground !=
null
) {
mStopForegroundArgs[
0
] = Boolean.TRUE;
try
{
mStopForeground.invoke(
this
, mStopForegroundArgs);
}
catch
(IllegalArgumentException e) {
e.printStackTrace();
}
catch
(IllegalAccessException e) {
e.printStackTrace();
}
catch
(InvocationTargetException e) {
e.printStackTrace();
}
return
;
}
// 在 setForeground 之前調用 cancel,因為我們有可能在取消前台服務之后
// 的那一瞬間被kill掉。這個時候 notification 便永遠不會從通知一欄移除
mNM.cancel(id);
}
}
通過查詢資料有人提到這個program:: droidwolf/NativeSubprocess · GitHub
創建 linux 子進程的 so 庫,當初用在 service 免殺上,經測試,在大部分機子上有用。NativeSubprocess 是一個可以讓你在 android 程序中創建 linux 子進程並執行你的 java 代碼的 so 庫。由於市面上典型的內存清理工具值清理 apk 包關聯的進程,而不會處理 linux 原生進程。回頭試試看這樣是否好使。
參考文章:http://www.jb51.net/article/73976.htm
最后的最后還是要說:請不要做流氓軟件!!讓本來就碎片化的Android手機能運行的更順暢。
