Android進程保活主要包括兩個方面:
- 提高進程的優先級,降低被殺死的概率
- 在進程被殺死後拉活
1.進程優先級
Android 系統將盡量長時間地保持應用進程,但爲了新建進程或運行更重要的進程,最終需要移除舊進程來回收內存。 爲了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的進程,然後是重要性略低的進程,依此類推,以回收系統資源。
重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類進程(第一個進程最重要,將是最後一個被終止的進程):
1.1 前臺進程
用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視爲前臺進程:
- 託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法)
- 託管某個 Service,後者綁定到用戶正在交互的 Activity
- 託管正在“前臺”運行的 Service(服務已調用 startForeground())
- 託管正執行一個生命週期回調的 Service(onCreate()、onStart() 或 onDestroy())
- 託管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前臺進程都爲數不多。只有在內存不足以支持它們同時繼續運行這一萬不得已的情況下,系統纔會終止它們。 此時,設備往往已達到內存分頁狀態,因此需要終止一些前臺進程來確保用戶界面正常響應。
1.2 可見進程
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視爲可見進程:
- 託管不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,如果前臺 Activity 啓動了一個對話框,允許在其後顯示上一 Activity,則有可能會發生這種情況。
- 託管綁定到可見(或前臺)Activity 的 Service。
可見進程被視爲是極其重要的進程,除非爲了維持所有前臺進程同時運行而必須終止,否則系統不會終止這些進程。
1.3 服務進程
- 正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的進程。
儘管服務進程與用戶所見內容沒有直接關聯,但是它們通常在執行一些用戶關心的操作(例如,在後臺播放音樂或從網絡下載數據)。因此,除非內存不足以維持所有前臺進程和可見進程同時運行,否則系統會讓服務進程保持運行狀態。
1.4 後臺進程
- 包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。
這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 通常會有很多後臺進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,因爲當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。 有關保存和恢復狀態的信息,請參閱 Activity文檔。
1.5 空進程
- 不含任何活動應用組件的進程。
保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程。
2.Android進程回收策略(LowMemoryKiller)
爲什麼引入LowMemoryKiller?
進程的啓動分冷啓動和熱啓動,當用戶退出某一個進程的時候,並不會真正的將進程退出,而是將這個進程放到後臺,以便下次啓動的時候可以馬上啓動起來,這個過程名爲熱啓動,這也是Android的設計理念之一。這個機制會帶來一個問題,每個進程都有自己獨立的內存地址空間,隨着應用打開數量的增多,系統已使用的內存越來越大,就很有可能導致系統內存不足。爲了解決這個問題,系統引入LowmemoryKiller(簡稱lmk)管理所有進程,根據一定策略來kill某個進程並釋放佔用的內存,保證系統的正常運行。
LowMemoryKiller的基本原理
所有應用進程都是從zygote孵化出來的,記錄在AMS中mLruProcesses列表中,由AMS進行統一管理,AMS中會根據進程的狀態更新進程對應的oom_adj值,這個值會通過文件傳遞到kernel中去,kernel有個低內存回收機制,在內存達到一定閥值時會觸發清理oom_adj值高的進程騰出更多的內存空間。
什麼是oom_adj?
它是linux內核分配給每個系統進程的一個值,代表進程的優先級,進程回收機制就是根據這個優先級來決定是否進行回收。對於oom_adj的作用,你只需要記住以下幾點即可:
- 進程的oom_adj越大,表示此進程優先級越低,越容易被殺回收;越小,表示進程優先級越高,越不容易被殺回收
- 普通app進程的oom_adj>=0,系統進程的oom_adj纔可能<0
minfree 閾值
存放6個數值,每個數表示內存頁面數,單位是page(一個頁面4kb)
查看方式:
adb shell
su
cat /sys/module/lowmemorykiller/parameters/minfree
得到的數值爲:18432,23040,27648,32256,36864,46080這6個數值分別代表android系統回收6種進程的閾值,這麼看不方便查看,轉換爲M會更直觀,這6個數值的單位爲page 1page = 4K,所以通過 數值*4/1024就能轉換爲M:72M,90M,108M,126M,144M,180M,也就是說1.前臺進程(foreground),2.可見進程(visible),3.次要服務(secondary server),4.後臺進程(hidden),5.內容供應節點(content provider),6.空進程(empty)這6類進程進行回收的內存閾值分別爲72M,90M,108M,126M,144M,180M
oom_adj值
oom_adj的值越小,進程的優先級越高
獲取進程oom_adj方式:
cat /proc/進程id/oom_adj
adj級別 | 值 | 解釋 |
---|---|---|
UNKNOWN_ADJ | 16 | 預留的最低級別,一般對於緩存的進程纔有可能設置成這個級別 |
CACHED_APP_MAX_ADJ | 15 | 緩存進程,空進程,在內存不足的情況下就會優先被kill |
CACHED_APP_MIN_ADJ | 9 | 緩存進程,也就是空進程 |
SERVICE_B_ADJ | 8 | 不活躍的進程 |
PREVIOUS_APP_ADJ | 7 | 切換進程 |
HOME_APP_ADJ | 6 | 與Home交互的進程 |
SERVICE_ADJ | 5 | 有Service的進程 |
HEAVY_WEIGHT_APP_ADJ | 4 | 高權重進程 |
BACKUP_APP_ADJ | 3 | 正在備份的進程 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知的進程,比如那種播放音樂 |
VISIBLE_APP_ADJ | 1 | 可見進程 |
FOREGROUND_APP_ADJ | 0 | 前臺進程 |
PERSISTENT_SERVICE_ADJ | -11 | 重要進程 |
PERSISTENT_PROC_ADJ | -12 | 核心進程 |
SYSTEM_ADJ | -16 | 系統進程 |
NATIVE_ADJ | -17 | 系統起的Native進程 |
(上表的數字可能在不同系統會有一定的出入)
內存閾值在不同的手機上不一樣,一旦低於該值,Android便開始按順序關閉進程. 因此Android開始結束優先級最低的空進程,即當可用內存小於180MB(46080)
3.提高進程優先級方案
3.1 Activity提權(降低oom_adj)
原理:
- 監控手機鎖屏解鎖事件,在屏幕鎖屏時啓動1個像素透明的 Activity,在用戶解鎖時將 Activity 銷燬掉,從而達到提高進程優先級的作用,可以使進程的優先級在屏幕鎖屏時間由4提升爲最高優先級1。
代碼實現:
public class KeepManager {
private static final KeepManager ourInstance = new KeepManager();
public static KeepManager getInstance() {
return ourInstance;
}
private KeepManager() {
}
private KeepReceiver keepReceiver;
private WeakReference<Activity> mKeepActivity;
/**
* 註冊
* @param context
*/
public void registerKeepReceiver(Context context){
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
keepReceiver = new KeepReceiver();
context.registerReceiver(keepReceiver, filter);
}
/**
* 反註冊
* @param context
*/
public void unRegisterKeepReceiver(Context context){
if (null != keepReceiver) {
context.unregisterReceiver(keepReceiver);
}
}
/**
* 啓動1個像素的KeepActivity
* @param context
*/
public void startKeep(Context context) {
Intent intent = new Intent(context, KeepActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
/**
* finish1個像素的KeepActivity
*/
public void finishKeep() {
if (null != mKeepActivity) {
Activity activity = mKeepActivity.get();
if (null != activity) {
activity.finish();
}
mKeepActivity = null;
}
}
public void setKeepActivity(KeepActivity mKeepActivity) {
this.mKeepActivity = new WeakReference<Activity>(mKeepActivity);
}
}
public class KeepReceiver extends BroadcastReceiver {
private static final String TAG = "KeepReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e(TAG, "receive:" + action);
if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
//滅屏 開啓1px activity
KeepManager.getInstance().startKeep(context);
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
//亮屏 關閉
KeepManager.getInstance().finishKeep();
}
}
}
3.2 Service提權(降低oom_adj)
創建一個前臺服務用於提高app在按下home鍵之後的進程優先級,startForeground(ID,Notification):使Service成爲前臺Service。 前臺服務需要在通知欄顯示一條通知
- API level<18:參數2設置爲newNotification(),圖標不會顯示
- API level>=18 & API level<26:在需要提權的service A啓動一個InnerService,兩個服務都startForeground,且綁定相同的id,Stop掉InnerService,通知欄圖標被移除
- API level>=26:必須手動創建通知欄,無法移除通知欄圖標,startForegroundService代替了startService
代碼實現:
public class ForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("deamon", "deamon",
NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (manager == null)
return;
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this, "deamon").setAutoCancel(true).setCategory(
Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
NotificationManager.IMPORTANCE_LOW).build();
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//如果 18 以上的設備 啓動一個Service startForeground給相同的id
//然後結束那個Service
startForeground(10, new Notification());
startService(new Intent(this, InnnerService.class));
} else {
startForeground(10, new Notification());
}
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
4.進程殺死後拉活方案
4.1 系統廣播拉活
在發生特定系統事件時,系統會發出廣播,通過在 AndroidManifest 中靜態註冊對應的廣播監聽器,即可在發生響應事件時拉活。但是從android 7.0開始,對廣播進行了限制,而且在8.0更加嚴格,8.0以後應該是沒用了。
4.2 “全家桶”拉活
有多個app在用戶設備上安裝,只要開啓其中一個就可以將其他的app也拉活。比如手機裏裝了手Q、QQ空間、興趣部落等等,那麼打開任意一個app後,其他的app也都會被喚醒。
普通小廠不太現實,沒有一系列的app
4.3 Service機制(Sticky)拉活
將 Service 設置爲 START_STICKY,利用系統機制在 Service 掛掉後自動拉活
- START_STICKY: “粘性”。如果service進程被kill掉,保留service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建service,由於服務狀態爲開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啓動命令被傳遞到service,那麼參數Intent將爲null。
- START_NOT_STICKY: “非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啓該服務。
- START_REDELIVER_INTENT: 重傳Intent。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統會自動重啓該服務,並將Intent的值傳入。
- START_STICKY_COMPATIBILITY: START_STICKY的兼容版本,但不保證服務被kill後一定能重啓。
只要 targetSdkVersion 不小於5,就默認是 START_STICKY。但是某些ROM 系統不會拉活。並且經過測試,Service 第一次被異常殺死後很快被重啓,第二次會比第一次慢,第三次又會比前一次慢,一旦在短時間內 Service 被殺死4-5次,則系統不再拉起。
4.4 賬戶同步拉活
手機系統設置裏會有“帳戶”一項功能,任何第三方APP都可以通過此功能將數據在一定時間內同步到服務器中去。系統在將APP帳戶同步時,會將未啓動的APP進程拉活
(1)開啓賬戶服務
public class AuthenticationService extends Service {
private AccountAuthenticator accountAuthenticator;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return accountAuthenticator.getIBinder();
}
@Override
public void onCreate() {
super.onCreate();
accountAuthenticator = new AccountAuthenticator(this);
}
static class AccountAuthenticator extends AbstractAccountAuthenticator {
public AccountAuthenticator(Context context) {
super(context);
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
String authTokenType, String[] requiredFeatures,
Bundle options) throws NetworkErrorException {
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
Bundle options) throws NetworkErrorException {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle options) throws
NetworkErrorException {
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle options) throws
NetworkErrorException {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
String[] features) throws NetworkErrorException {
return null;
}
}
}
在manifest中配置service
<service android:name=".account.AuthenticationService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
在xml中添加authenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.dn.daemon.account"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" />
<!--accountType表示賬戶類型,必須唯一-->
(2)添加賬戶
public class AccountHelper {
//與authenticator.xml中accountType一致
private static final String ACCOUNT_TYPE = "com.dn.daemon.account";
private static final String CONTENT_AUTHORITY = "com.dn.daemon.provider";
public static void addAccount(Context context) {
AccountManager accountManager = (AccountManager) context.getSystemService(
Context.ACCOUNT_SERVICE);
Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
if (accounts.length > 0) {
//賬戶已存在
return;
}
Account account = new Account("hxl", ACCOUNT_TYPE);
accountManager.addAccountExplicitly(account, "dn123", new Bundle());//直接添加賬戶
}
}
......
(3)同步服務
創建一個Service作爲同步Service,並且在onBind返回AbstractThreadedSyncAdapter的getSyncAdapterBinder
public class SyncService extends Service {
private static final String TAG = "SyncService";
private SyncAdapter syncAdapter;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
@Override
public void onCreate() {
super.onCreate();
syncAdapter = new SyncAdapter(this, true);
}
static class SyncAdapter extends AbstractThreadedSyncAdapter {
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
Log.e(TAG,"賬戶同步了!");
}
}
}
在manifest中配置service
<service android:name=".account.SyncService">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
</service>
在xml中添加syncadapter.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.dn.daemon.provider"
android:accountType="com.dn.daemon.account"
android:userVisible="false"
android:supportsUploading="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"
/>
<!--contentAuthority 系統在進行賬戶同步的時候會查找 此auth的ContentProvider-->
<!--accountType表示賬戶類型,與authenticator.xml裏要一致-->
<!-- userVisible 是否在“設置”中顯示-->
<!-- supportsUploading 是否必須notifyChange通知才能同步-->
<!-- allowParallelSyncs 允許多個賬戶同時同步-->
<!--isAlwaysSyncable 設置所有賬號的isSyncable爲1-->
(4)創建ContentProvider
public class SyncProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}
}
在manifest中配置ContentProvider
<provider
android:authorities="com.dn.daemon.provider"
android:name=".account.SyncProvider"
android:exported="false"
/>
(5)開啓同步
爲了達到進程保活的效果,可以開啓自動同步。時間間隔雖然設置了1s,但是Android本身爲了考慮同步所帶來的消耗和減少喚醒設備的次數,1s只是一個參考時間
public class AccountHelper {
//與authenticator.xml中accountType一致
private static final String ACCOUNT_TYPE = "com.dn.daemon.account";
private static final String CONTENT_AUTHORITY = "com.dn.daemon.provider";
public static void addAccount(Context context) {
AccountManager accountManager = (AccountManager) context.getSystemService(
Context.ACCOUNT_SERVICE);
Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
if (accounts.length > 0) {
//賬戶已存在
return;
}
Account account = new Account("hxl", ACCOUNT_TYPE);
accountManager.addAccountExplicitly(account, "dn123", new Bundle());//直接添加賬戶
}
public static void autoSync() {
Account dongnao = new Account("hxl", ACCOUNT_TYPE);
//設置同步
ContentResolver.setIsSyncable(dongnao, CONTENT_AUTHORITY, 1);
//設置自動同步
ContentResolver.setSyncAutomatically(dongnao, CONTENT_AUTHORITY, true);
//設置同步週期
ContentResolver.addPeriodicSync(dongnao, CONTENT_AUTHORITY, new Bundle(), 1);
}
}
4.5 JobScheduler拉活
JobScheduler允許在特定狀態與特定時間間隔週期執行任務。可以利用它的這個特點完成保活的功能,效果即開啓一個定時器,與普通定時器不同的是其調度由系統完成。
public class MyJobService extends JobService {
private static final String TAG = "MyJobService";
public static void startJob(Context context) {
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(10,
new ComponentName(context.getPackageName(),
MyJobService.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(1000);
}else{
//每隔1s執行一次job
builder.setPeriodic(1000);
}
jobScheduler.schedule(builder.build());
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.e(TAG,"開啓job");
//7.0以上輪詢
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
startJob(this);
}
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
MyJobService.startJob(this);
4.6 推送拉活
根據終端不同,在小米手機(包括 MIUI)接入小米推送、華爲手機接入華爲推送。
4.7 Native拉活
Native fork子進程用於觀察當前app主進程的存亡狀態。對於5.0以上成功率極低,然而目前5.0以下的手機只佔10%
4.8 雙進程守護
兩個進程共同運行,如果有其中一個進程被殺,那麼另外一個進程就會將被殺的進程重新拉起。相對於其他方案成功率比較高。
public class LocalService extends Service {
private MyBinder myBinder;
class MyBinder extends IMyAidlInterface.Stub{
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
myBinder = new MyBinder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("deamon", "deamon",
NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
if (manager == null)
return;
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this,
"deamon").setAutoCancel(true).setCategory(
Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
NotificationManager.IMPORTANCE_LOW).build();
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//如果 18 以上的設備 啓動一個Service startForeground給相同的id
//然後結束那個Service
startForeground(10, new Notification());
startService(new Intent(this, InnnerService.class));
} else {
startForeground(10, new Notification());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
bindService(new Intent(this, RemoteService.class), new MyServiceConnection(),
BIND_AUTO_CREATE);
return super.onStartCommand(intent, flags, startId);
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//回調
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//
startService(new Intent(LocalService.this, RemoteService.class));
bindService(new Intent(LocalService.this, RemoteService.class), new MyServiceConnection(),
BIND_AUTO_CREATE);
}
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
public class RemoteService extends Service {
private MyBinder myBinder;
class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
myBinder = new MyBinder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("deamon", "deamon",
NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
if (manager == null)
return;
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this,
"deamon").setAutoCancel(true).setCategory(
Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
NotificationManager.IMPORTANCE_LOW).build();
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//如果 18 以上的設備 啓動一個Service startForeground給相同的id
//然後結束那個Service
startForeground(10, new Notification());
startService(new Intent(this, InnnerService.class));
} else {
startForeground(10, new Notification());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
bindService(new Intent(this, LocalService.class), new MyServiceConnection(),
BIND_AUTO_CREATE);
return super.onStartCommand(intent, flags, startId);
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
startService(new Intent(RemoteService.this, LocalService.class));
bindService(new Intent(RemoteService.this, LocalService.class),
new MyServiceConnection(), BIND_AUTO_CREATE);
}
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
@SuppressLint("NewApi")
public class MyJobService extends JobService {
private static final String TAG = "MyJobService";
public static void startJob(Context context) {
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(10,
new ComponentName(context.getPackageName(),
MyJobService.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(1000);
} else {
//每隔1s執行一次job
builder.setPeriodic(1000);
}
jobScheduler.schedule(builder.build());
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.e(TAG, "開啓job");
//7.0以上輪詢
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
startJob(this);
}
//判斷服務是否在運行
boolean isLocalServiceRun = isServiceRunning(this, LocalService.class.getName());
boolean isRemoteServiceRun = isServiceRunning(this, RemoteService.class.getName());
if (!isLocalServiceRun || !isRemoteServiceRun) {
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
}
return false;
}
private boolean isServiceRunning(Context context, String serviceName) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(10);
for (ActivityManager.RunningServiceInfo runningService : runningServices) {
if (TextUtils.equals(runningService.service.getClassName(), serviceName)) {
return true;
}
}
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
manifest設置不同進程
<service android:name=".service.LocalService"
android:exported="true"
android:process=":local"/>
<service android:name=".service.LocalService$InnnerService"
android:exported="true"
android:process=":local"/>
<service android:name=".service.RemoteService"
android:exported="true"
android:process=":remote"/>
<service android:name=".service.RemoteService$InnnerService"
android:exported="true"
android:process=":remote"/>
<service android:name=".service.MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
啓動進程
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
MyJobService.startJob(this);