Android應用進程保活
- Android應用進程保活方法介紹
在Android應用程序中,為了保證應用的正常運行和穩定性,有時需要對應用進程進行保活。以下是一些實現進程保活的方法:
- 使用前臺服務(Foreground Service):將服務調用startForeground()方法,并傳入一個通知對象,將該服務置于前臺運行狀態。這樣可以使得該服務的優先級更高,從而減少被系統殺死的概率。
- 使用JobScheduler:使用setPeriodic()方法可以讓應用程序周期性地執行任務,從而避免長時間占用CPU資源,setPersisted(true)方法則表示當設備重啟后,該任務仍然需要繼續執行。
- 使用AlarmManager:使用這個API可以讓應用程序在指定的時間間隔內執行任務。例如,可以設置一個鬧鐘,每隔一段時間喚醒應用程序并執行一些操作。
- 使用守護進程:啟動一個后臺守護進程,監控應用程序的狀態并在應用程序被殺死時重新啟動它,使用守護進程需要申請額外的權限。
- 使用雙進程保活:啟動兩個相互綁定的進程,在其中一個進程被殺死時,另一個進程可以重新啟動它。
- 使用WorkManger: 這是目前比較新的保活機制,用于取代JobScheduler。
需要注意的是,為了避免濫用和浪費系統資源,Android系統不斷升級后,已經嚴格限制應用程序使用過多的后臺資源和保活機制。
- JobScheduler用法簡介
JobScheduler是系統服務,由系統負責調度第三方應用注冊的JobScheduler,定時完成指定任務。
在應用中創建一個 JobService服務,JobService需要 API Level 21以上才可以使用,該服務注冊時必須聲明 android.permission.BIND_JOB_SERVICE權限:
通常使用JobScheduler需要以下幾個步驟:
1、獲取 JobScheduler對象:通過Binder機制獲取該JobScheduler系統服務;
//創建 JobScheduler JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
2、指定 JobScheduler任務信息 JobInfo:綁定任務 ID,指定任務的運行組件,也就是之前創建并注冊的 JobService, 最后要設置該任務在重啟后也要執行;
//第一個參數指定任務 ID //第二個參數指定任務在哪個組件中執行 // setPersisted方法需要 android.permission.RECEIVE_BOOT_COMPLETED權限 // setPersisted方法作用是設備重啟后 ,依然執行 JobScheduler定時任務 JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(10, new ComponentName(context.getPackageName(), KeepAliveJobService.class.getName())) .setPersisted(true);
3、設置時間信息:7.0以下的系統可以設置間隔, 7.0以上的版本需要設置延遲執行,否則無法啟動;
// 7.0 以下的版本,可以每隔 5000毫秒執行一次任務 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){ ?? jobInfoBuilder.setPeriodic(5_000); }else{ ?? // 7.0?以上的版本 ,?設置延遲 5?秒執行 ?? //?該時間不能小于 JobInfo.getMinLatencyMillis?方法獲取的最小值 ?? jobInfoBuilder.setMinimumLatency(5_000); }
4、開啟定時任務;
//開啟定時任務 jobScheduler.schedule(jobInfoBuilder.build());
5、7.0以上的特殊處理,由于在7.0以上的系統中設置了延遲執行,需要在 JobService的 onStartJob方法中再次開啟一次 JobScheduler任務執行,也就是重復上述1 ~ 4執行, 這樣就實現了周期性執行的目的;
public class KeepAliveJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { Log.i("KeepAliveJobService", "JobService onStartJob開啟"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ //如果當前設備大于 7.0 ,延遲 5秒 ,再次執行一次 startJob(this); } return false; } }
3.WorkManager用法簡介
WorkManager是適合用于持久性工作的推薦解決方案,它可處理三種類型的持久性工作:
- 立即執行:必須立即開始且很快就完成的任務,可以加急。
- 長時間運行:運行時間可能較長(有可能超過 10分鐘)的任務。
- 可延期執行:延期開始并且可以定期運行的預定任務。
通常使用WorkManager需要以下幾個步驟:
- 將依賴項添加到應用的build.gradle文件中;
- 定義工作:工作使用 Worker類定義,doWork()方法在 WorkManager提供的后臺線程上異步運行。如需為 WorkManager創建一些要運行的工作,則需擴展 Worker類并替換 doWork()方法;
public class XxxWorker extends Worker { publicXxxWorker( @NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @Override public Result doWork() { // Do the work here xxxxx(); // Indicate whether the work finished successfully with the Result return Result.success(); } }
3.創建 WorkRequest:定義工作后,必須使用 WorkManager服務進行調度該工作才能運行;
WorkRequest xxxWorkRequest = new OneTimeWorkRequest.Builder(XxxWorker.class) .build();
4.將 WorkRequest提交給系統:需要使用enqueue()方法將WorkRequest提交到WorkManager;
WorkManager .getInstance(myContext) .enqueue(uploadWorkRequest);
在定義工作時要考慮要考慮下面常見的需求:
4.雙進程保活
雙進程保活的方式就是在運行了一個主進程之外,還運行了一個 “本地前臺進程”,并綁定“遠程前臺進程”,“遠程前臺進程”與“本地前臺進程”實現了相同的功能,代碼基本一致,這兩個進程都是前臺進程,都進行了提權,并且互相綁定,當監聽到綁定的另外一個進程突然斷開連接,則本進程再次開啟前臺進程提權,并且重新綁定對方進程,以達到拉活對方進程的目的。
雙進程保活的實現步驟如下:
- 定義 AIDL接口 IMyAidlInterface,每個服務中都需要定義繼承 IMyAidlInterface.Stub的 Binder類,作為進程間通信的橋梁(這是個默認的 AIDL接口 ),監聽進程的連接斷開;
// Declare any non-default types here with import statements interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
2.實現一個判定服務運行工具:
import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.text.TextUtils; import org.w3c.dom.Text; import java.util.List; public class ServiceUtils { /** *判定 Service是否在運行 * @param context * @return */ public static boolean isServiceRunning(Context context, String serviceName){ if(TextUtils.isEmpty(serviceName)) return false; ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); //最多獲取 200個正在運行的 Service List infos = activityManager.getRunningServices(200); //遍歷當前運行的 Service信息,如果找到相同名稱的服務 ,說明某進程正在運行 for (ActivityManager.RunningServiceInfo info: infos){ if (TextUtils.equals(info.service.getClassName(), serviceName)){ return true; } } return false; } }
3.定義一個用于本地與遠程連接的類:
class Connection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { //服務綁定成功時回調 } @Override public void onServiceDisconnected(ComponentName name) { //再次啟動前臺進程 startService(); //綁定另外一個遠程進程 bindService(); } }
4..定義一個本地前臺服務類:
import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.Color; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import androidx.core.app.NotificationCompat; import static androidx.core.app.NotificationCompat.PRIORITY_MIN; /** *本地前臺服務 */ public class LocalForegroundService extends Service { /** *遠程調用 Binder對象 */ private MyBinder myBinder; /** *連接對象 */ private Connection connection; /** * AIDL遠程調用接口 *其它進程調與該 RemoteForegroundService服務進程通信時 ,可以通過 onBind方法獲取該 myBinder成員 *通過調用該成員的 basicTypes方法 ,可以與該進程進行數據傳遞 */ class MyBinder extends IMyAidlInterface.Stub { @Override public void basicTypes( int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { //通信內容 } } @Override public IBinder onBind(Intent intent) { return myBinder; } @Override public void onCreate() { super.onCreate(); //創建 Binder對象 myBinder = new MyBinder(); //啟動前臺進程 startService(); } private void startService(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ // startForeground(); //創建通知通道 NotificationChannel channel = new NotificationChannel("service", "service", NotificationManager.IMPORTANCE_NONE); channel.setLightColor(Color.BLUE); channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //正式創建 service.createNotificationChannel(channel); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "service"); Notification notification = builder.setOngoing(true) .setSmallIcon(R.mipmap.ic_launcher) .setPriority(PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) .build(); //開啟前臺進程 , API 26以上無法關閉通知欄 startForeground(10, notification); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){ startForeground(10, new Notification()); // API 18 ~ 25以上的設備 ,啟動相同 id的前臺服務 ,并關閉 ,可以關閉通知 startService(new Intent(this, CancelNotificationService.class)); } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){ ?????????? //?將該服務轉為前臺服務 ?????????? //?需要設置 ID?和?通知 ?????????? //?設置 ID?為 0 ,?就不顯示已通知了 ,?但是 oom_adj?值會變成后臺進程 11 ?????????? //?設置 ID?為 1 ,?會在通知欄顯示該前臺服務 ?????????? // 8.0?以上該用法報錯 ?????????? startForeground(10, new Notification()); ?????? } ?? } ?? /** ??? *?綁定?另外一個?服務 ??? * LocalForegroundService?與 RemoteForegroundService?兩個服務互相綁定 ??? */ ?? private void bindService(){ ?????? //?綁定另外一個?服務 ?????? // LocalForegroundService?與 RemoteForegroundService?兩個服務互相綁定 ?????? //?創建連接對象 ?????? connection = new Connection(); ?????? //?創建本地前臺進程組件意圖 ?????? Intent bindIntent = new Intent(this, RemoteForegroundService.class); ?????? //?綁定進程操作 ?????? bindService(bindIntent, connection, BIND_AUTO_CREATE); ?? } ?? @Override ?? public int onStartCommand(Intent intent, int flags, int startId) { ?????? //?綁定另外一個服務 ?????? bindService(); ?????? return super.onStartCommand(intent, flags, startId); ?? } }
5.定義一個遠程前臺服務類:
import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.Color; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import androidx.core.app.NotificationCompat; import static androidx.core.app.NotificationCompat.PRIORITY_MIN; /** *遠程前臺服務 */ public class RemoteForegroundService extends Service { /** *遠程調用 Binder對象 */ private MyBinder myBinder; /** *連接對象 */ private Connection connection; /** * AIDL遠程調用接口 *其它進程調與該 RemoteForegroundService服務進程通信時 ,可以通過 onBind方法獲取該 myBinder成員 *通過調用該成員的 basicTypes方法 ,可以與該進程進行數據傳遞 */ class MyBinder extends IMyAidlInterface.Stub { @Override public void basicTypes( int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { //通信內容 } } @Override public IBinder onBind(Intent intent) { return myBinder; } @Override public void onCreate() { super.onCreate(); //創建 Binder對象 myBinder = new MyBinder(); //啟動前臺進程 startService(); } private void startService(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ // startForeground(); //創建通知通道 NotificationChannel channel = new NotificationChannel("service", "service", NotificationManager.IMPORTANCE_NONE); channel.setLightColor(Color.BLUE); channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //正式創建 service.createNotificationChannel(channel); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "service"); Notification notification = builder.setOngoing(true) .setSmallIcon(R.mipmap.ic_launcher) .setPriority(PRIORITY_MIN) .setCategory(Notification.CATEGORY_SERVICE) .build(); //開啟前臺進程 , API 26以上無法關閉通知欄 startForeground(10, notification); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){ startForeground(10, new Notification()); // API 18 ~ 25以上的設備 ,啟動相同 id的前臺服務 ,并關閉 ,可以關閉通知 startService(new Intent(this, CancelNotificationService.class)); } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){ ?????????? //?將該服務轉為前臺服務 ?????????? //?需要設置 ID?和?通知 ?????????? //?設置 ID?為 0 ,?就不顯示已通知了 ,?但是 oom_adj?值會變成后臺進程 11 ?????????? //?設置 ID?為 1 ,?會在通知欄顯示該前臺服務 ?????????? // 8.0?以上該用法報錯 ?????????? startForeground(10, new Notification()); ?????? } ?? } ?? /** ??? *?綁定?另外一個?服務 ??? * LocalForegroundService?與 RemoteForegroundService?兩個服務互相綁定 ??? */ ?? private void bindService(){ ?????? //?綁定?另外一個?服務 ?????? // LocalForegroundService?與 RemoteForegroundService?兩個服務互相綁定 ?????? //?創建連接對象 ?????? connection = new Connection(); ?????? //?創建本地前臺進程組件意圖 ?????? Intent bindIntent = new Intent(this, LocalForegroundService.class); ?????? //?綁定進程操作 ?????? bindService(bindIntent, connection, BIND_AUTO_CREATE); ?? } ?? @Override ?? public int onStartCommand(Intent intent, int flags, int startId) { ?????? //?綁定另外一個服務 ?????? bindService(); ?????? return super.onStartCommand(intent, flags, startId); ?? } }
6.啟動兩個服務:
import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService(new Intent(this, LocalForegroundService.class)); startService(new Intent(this, RemoteForegroundService.class)); } }
5.雙進程保活+JobScheduler整合方案
這種方案是在 JobService的onStartJob方法中判定“雙進程保活”中的雙進程是否掛了,如果這兩個進程掛了,就重新將掛掉的進程重啟。
這里給出一個雙進程保活+JobScheduler整合方案中JobScheduler部分的示意代碼,而雙進程保活部分保持不變。
public class KeepAliveJobService extends JobService { @Override public boolean onStartJob(JobParameters params) { Log.i("KeepAliveJobService", "JobService onStartJob開啟"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ //如果當前設備大于 7.0 ,延遲 5秒 ,再次執行一次 startJob(this); } //判定本地前臺進程是否正在運行 boolean isLocalServiceRunning = ServiceUtils.isServiceRunning(this, LocalForegroundService.class.getName()); if (!isLocalServiceRunning){ startService(new Intent(this, LocalForegroundService.class)); } //判定遠程前臺進程是否正在運行 boolean isRemoteServiceRunning = ServiceUtils.isServiceRunning(this, RemoteForegroundService.class.getName()); if (!isRemoteServiceRunning){ startService(new Intent(this, RemoteForegroundService.class)); } return false; } @Override public boolean onStopJob(JobParameters params) { Log.i("KeepAliveJobService", "JobService onStopJob關閉"); return false; } public static void startJob(Context context){ //創建 JobScheduler JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); //第一個參數指定任務 ID //第二個參數指定任務在哪個組件中執行 // setPersisted方法需要 android.permission.RECEIVE_BOOT_COMPLETED權限 // setPersisted方法作用是設備重啟后 ,依然執行 JobScheduler定時任務 JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(10, new ComponentName(context.getPackageName(), KeepAliveJobService.class.getName())) .setPersisted(true); // 7.0以下的版本,可以每隔 5000毫秒執行一次任務 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){ ?????????? jobInfoBuilder.setPeriodic(5_000); ?????? }else{ ?????????? // 7.0?以上的版本 ,?設置延遲 5?秒執行 ?????????? //?該時間不能小于 JobInfo.getMinLatencyMillis?方法獲取的最小值 ????????? jobInfoBuilder.setMinimumLatency(5_000); ?????? } ?????? //?開啟定時任務 ????? jobScheduler.schedule(jobInfoBuilder.build()); ?? } }
6.參考文獻
1、【Android進程保活】應用進程拉活 (雙進程守護 + JobScheduler保活):
https://hanshuliang.blog.csdn.net/article/details/115607584
2、【Android進程保活】應用進程拉活 (雙進程守護保活 ):
https://hanshuliang.blog.csdn.net/article/details/115604667
3、【Android進程保活】應用進程拉活 ( JobScheduler拉活):
https://hanshuliang.blog.csdn.net/article/details/115584240
4、Android實現進程保活的思路:
https://blog.csdn.net/gs12software/article/details/130502312
5、WorkManager使用入門:
https://developer.android.google.cn/develop/background-work/background-tasks/persistent/getting-started
-
Android
+關注
關注
12文章
3936瀏覽量
127399 -
應用
+關注
關注
2文章
438瀏覽量
34157 -
進程
+關注
關注
0文章
203瀏覽量
13961
發布評論請先 登錄
相關推薦
評論