1. 程式人生 > >Android進階——效能優化之程序拉活原理及手段完全解析(二)

Android進階——效能優化之程序拉活原理及手段完全解析(二)

引言

上一篇文章Android進階——效能優化之程序保活原理及手段完全解析(一)總結了Android程序和執行緒的相關知識,主要介紹了幾種提升程序優先順序的手段,通常僅僅是提高優先順序只能讓你的程序存活時間久一點,但是真正的被殺死之後就不會自動拉活的,如果你的程序需要儘可能存在後臺還需要拉活措施,在被殺死之後一段時間之內自動拉活。(如非絕對的需求,還是少浪費點使用者的資源吧)

一、系統賬戶同步機制拉活

手機系統設定裡會有Account帳戶一項功能,任何第三方APP都可以通過此功能將我們自己的APP註冊到這個Account帳戶中,並且將資料在一定時間內同步到伺服器中去。系統在將APP帳戶同步時,自動將未啟動的APP程序拉活

,具體操作參見Google官方demo
這裡寫圖片描述

1、繼承Service並在內部繼承實現用於返回Binder的AbstractAccountAuthenticator

AuthenticationService繼承自Service本質上是一個AIDL,提供給其他的程序使用的,主要我們實現並且聲明瞭之後,android系統會通過android.accounts.AccountAuthenticator這個Action找到它,並通過它來把我們自己的賬號註冊到系統設定介面,其中Authenticator是一個繼承自AbstractAccountAuthenticator的類,而AbstractAccountAuthenticator是用於實現對手機系統設定裡“賬號與同步”中Account的新增、刪除和驗證等一些基本功能

。很明顯AbstractAccountAuthenticator裡面有個繼承於IAccountAuthenticator.Stub的內部類,以用來對AbstractAccountAuthenticator的遠端介面呼叫進行包裝。所以可以通過AbstractAccountAuthenticator的getIBinder()方法,返回內部類的IBinder形式.

/**
 * 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.AccountAuthenticatormeta-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