Android進階——效能優化之程序拉活原理及手段完全解析(二)
引言
上一篇文章Android進階——效能優化之程序保活原理及手段完全解析(一)總結了Android程序和執行緒的相關知識,主要介紹了幾種提升程序優先順序的手段,通常僅僅是提高優先順序只能讓你的程序存活時間久一點,但是真正的被殺死之後就不會自動拉活的,如果你的程序需要儘可能存在後臺還需要拉活措施,在被殺死之後一段時間之內自動拉活。(如非絕對的需求,還是少浪費點使用者的資源吧)
一、系統賬戶同步機制拉活
手機系統設定裡會有Account帳戶一項功能,任何第三方APP都可以通過此功能將我們自己的APP註冊到這個Account帳戶中,並且將資料在一定時間內同步到伺服器中去。系統在將APP帳戶同步時,自動將未啟動的APP程序拉活
1、繼承Service並在內部繼承實現用於返回Binder的AbstractAccountAuthenticator
AuthenticationService繼承自Service本質上是一個AIDL,提供給其他的程序使用的,主要我們實現並且聲明瞭之後,android系統會通過android.accounts.AccountAuthenticator這個Action找到它,並通過它來把我們自己的賬號註冊到系統設定介面,其中Authenticator是一個繼承自AbstractAccountAuthenticator的類,而AbstractAccountAuthenticator是用於實現對手機系統設定裡“賬號與同步”中Account的新增、刪除和驗證等一些基本功能
/**
* Created by cmo on 2018/8/19 14:17
*/
public class AuthenticationService extends Service {
private AccountAuthenticator mAuthenticator;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mAuthenticator.getIBinder();//返回操作資料的Binder
}
@Override
public void onCreate() {
super.onCreate();
mAuthenticator = new AccountAuthenticator(this);
}
/**
* 賬戶操作的
*/
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;
}
}
}
2、在res/xml/資料夾下定義將要顯示在Account列表的資源
以account-authenticator 為根節點的xml檔案,其中icon、label分別是Account列表中的圖示和顯示名稱,而accountType則是操作使用者所必須的引數之一。
<!--res/xml/accountauthenticator.xml-->
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.crazymo.guardback"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" />
3、在清單檔案中配置AuthenticationService
一定要配置上指定的Action:android.accounts.AccountAuthenticator和meta-data
<application>
<!-- 這個是配置賬戶服務的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/accountauthenticator"/>
</service>
</application>
經過以上三步之後,安裝Apk,再次開啟Account你會發現原來的Account列表多了一行資料,說明我們的App也可以支援這個Account系統了
4、建立App的賬戶
接來還需要建立一個我們自己的Account和進行一些必要的配置。
public class AccountHelper {
//authenticator.xml 中配置 的accountType值
public static final String ACCOUNT_TYPE="com.crazymo.guardback";
/**
* 新增Account,需要"android.permission.GET_ACCOUNTS"許可權
* @param context
*/
public static void addAccount(Context context){
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
Account[] accountsType = accountManager.getAccountsByType(ACCOUNT_TYPE);
if(accountsType.length>0){
Log.e("cmo","賬戶已經存在");
return;
}
//給這個賬戶型別新增賬戶 crazymo cmo518
Account account=new Account("crazymo",ACCOUNT_TYPE);
//需要"android.permission.AUTHENTICATE_ACCOUNTS"許可權
accountManager.addAccountExplicitly(account,"cmo518",new Bundle());
}
/**
* 設定賬戶同步,即告知系統我們需要系統為我們來進行賬戶同步,只有設定了之後系統才會自動去
* 觸發SyncAdapter#onPerformSync方法
*/
public static void autoSyncAccount(){
Account account=new Account("crazymo",ACCOUNT_TYPE);
//設定可同步
ContentResolver.setIsSyncable(account,"com.crazymo.guardback.provider",2);
//設定自動同步
ContentResolver.setSyncAutomatically(account,"com.crazymo.guardback.provider",true);
//設定同步週期參考值,不受開發者控制完全由系統決定何時同步,測試下來最長等了差不多幾十分鐘才同步一次,不同系統表現不同
ContentResolver.addPeriodicSync(account,"com.crazymo.guardback.provider",new Bundle(),1);
}
}
呼叫addAccount這個方法之後就會在系統設定的Account介面多了一個Account
5、建立賬戶同步Service
建立一個Service作為同步Service,並且在onBind返回AbstractThreadedSyncAdapter的getSyncAdapterBinder
/**
* Created by cmo on 2018/8/19 22:35
* 用於執行賬戶同步,當系統執行賬戶同步時則會自動拉活所在的程序,不需要手動配置好之後,系統會自動繫結並調起
*/
public class SyncService extends Service {
private SyncAdapter mSyncAdapter;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mSyncAdapter.getSyncAdapterBinder();
}
@Override
public void onCreate() {
super.onCreate();
mSyncAdapter = new SyncAdapter(getApplicationContext(), 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) {
//todo 賬戶同步 工作
Log.e("cmo","同步賬戶");
//與網際網路 或者 本地資料庫同步賬戶
}
}
}
contentAuthority屬性是配置系統在進行賬戶同步的時候會查詢此auth的ContentProvider,allowParallelSyncs 允許多個同步。
<!--res/xml/sync_adapter.xml-->
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.crazymo.guardback"
android:contentAuthority="com.crazymo.guardback.provider"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"
android:userVisible="false"/>
賬戶同步還需要提供一個ContentProvider
public class SyncContentProvider 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;
}
}
6、告知系統我們的Account需要進行同步服務
經過以上幾步,基本完成了賬戶同步的機制的搭建,但是還需要主動告知系統我們,即通過呼叫AccountHelper.autoSyncAccount();
7、完整的清單配置檔案和MainActivity程式碼
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.crazymo.guardback">
<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<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/accountauthenticator"/>
</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/sync_adapter" />
</service>
<provider
android:authorities="com.crazymo.guardback.provider"
android:name=".account.SyncContentProvider"/>
</application>
</manifest>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AccountHelper.addAccount(this);//新增賬戶
AccountHelper.autoSyncAccount();//呼叫告知系統自動同步
}
}
以上就是利用賬戶同步進行拉活的主要核心思想(至於真正同步的程式碼不在此文章討論),測試過程中發現(最高測試版本到Android 8.0),不同系統表現不同,至於同步週期完全是由系統進行控制的,雖然比較穩定但是週期不可控。
二、JobSchedule 機制拉活
JobScheduler允許在特定狀態與特定時間間隔週期執行任務,所以我們也可以利用它的這個機制來完成拉活的功能,其效果就像是開啟一個定時器,與普通定時器不同的是其排程由系統完成,也比較可靠穩定,但是會受到白名單等模式的影響,在某些ROM中甚至無法拉活。
1、實現JobService
package com.crazymo.guardback.jobschedule;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.util.Log;
/**
* Created by cmo on 2018/8/21 21:06
*/
public class GuardJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
Log.e("cmo", "開啟job");
//如果7.0以上 輪詢
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
startGuardJob(this);
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
public static void startGuardJob(Context context) {
if(context!=null) {
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
// setPersisted 在裝置重啟依然執行
JobInfo.Builder builder = new JobInfo.Builder(10, new ComponentName(context
.getPackageName(), GuardJobService.class
.getName())).setPersisted(true);
//小於7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// 每隔1s 執行一次 job
builder.setPeriodic(1_000);
} else {
//延遲執行任務
builder.setMinimumLatency(1_000);
}
jobScheduler.schedule(builder.build());
}
}
}
2、在清單中註冊JobService
<application>
...
<service
android:name=".jobschedule.GuardJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
##3、手動開啟JobSchedule
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GuardJobService.startGuardJob(this);//通過JobSchedule 拉活
}
}
三、雙程序Service互相拉活
這裡所講的雙程序守護並非是以前通過Native fork子程序用於觀察當前app主程序的存亡狀態,那種Native形式對於5.0以上成功率極低。
如上圖所述,所謂雙程序Service互相拉活,本質就是利用了系統Binder機制並結合前臺服務提權,目前此種方式也是成功率很高的一種方式。
1、實現一個AIDL檔案
此處如果僅僅是為了拉活,不需要遠端呼叫某些功能的話,可以不用具體實現,但是不能缺少。
// IGuardService.aidl
package com.crazymo.deguard;
// Declare any non-default types here with import statements
interface IGuardService {
/**
* 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、實現執行在主程序的Service
package com.crazymo.deguard.service;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.crazymo.deguard.IGuardService;
/**
* Created by cmo on 2018/8/21 22:12
* 提權Service
*/
public