天天看點

【Android 程序保活】應用程序拉活 ( 賬戶同步拉活 | 賬戶同步 | 源碼資源 )

文章目錄

  • ​​一、 賬戶同步​​
  • ​​二、 賬戶同步代碼示例​​
  • ​​1、 賬戶同步 Service​​
  • ​​2、 賬戶同步 ContentProvider​​
  • ​​3、 AndroidManifest.xml 清單檔案​​
  • ​​4、 sync-adapter 配置檔案​​
  • ​​5、 賬戶同步工具類​​
  • ​​6、 MainActivity 啟動賬戶同步​​
  • ​​7、 運作效果​​
  • ​​三、 源碼資源​​

一、 賬戶同步

​賬戶同步的作用 :​ 如果應用的資料發生了改變 , 可以通過賬戶進行同步 , 進而與伺服器進行資料同步操作 , 執行同步時 , 系統會拉活對應的應用程序 ;

​程序拉活隻是賬戶同步的附帶作用 ;​

賬戶同步時 , 需要應用中有對應的同步服務 , 系統也是通過 Binder 機制與應用進行同步操作 ;

賬戶同步需要在 賬戶同步服務 Service 中進行 , 定義一個 Service 進行賬戶同步 , 其 onBind 方法必須傳回 AbstractThreadedSyncAdapter 的 getSyncAdapterBinder() 值 ;

賬戶同步需要自定義一個 AbstractThreadedSyncAdapter 類 , 并在 Service 中維護一個該類對象 ;

class ThreadSyncAdapter extends AbstractThreadedSyncAdapter{

        public ThreadSyncAdapter(Context context, boolean autoInitialize) {
            super(context, autoInitialize);
        }

        public ThreadSyncAdapter(Context context, boolean autoInitialize,
                                 boolean allowParallelSyncs) {
            super(context, autoInitialize, allowParallelSyncs);
        }

        @Override
        public void onPerformSync(Account account, Bundle extras, String authority,
                                  ContentProviderClient provider, SyncResult syncResult) {
            // 賬戶同步操作
            // 與資料庫 , 伺服器同步操作 , 這裡隻是為了應用程序拉活 , 不實作具體的邏輯
        }
    }      

系統在進行賬戶同步的時候 , 會擷取該 賬戶同步 Service 的 IBinder , 拿到該 IBinder 後 , 會調用 AbstractThreadedSyncAdapter 子類對象中的 onPerformSync 方法 , 執行同步操作 ;

該 onPerformSync 函數是系統在執行同步時執行的函數 , 但是這裡我們的目的是為了拉活應用程序 , 并不是為了進行賬戶同步 , 這裡空着就可以 ;

最後還要在清單檔案中注冊該同步 Service ;

<!-- 賬戶同步服務 -->
        <service
            android:name=".account_service.AccountSyncService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.content.SyncAdapter" />
            </intent-filter>

            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/sync_adapter" />
        </service>      

除了同步 Service 元件之外 , 還必須有一個 ContentProvider 元件 , 系統進行賬戶同步時 , 會查找對應賬戶的 ContentProvider , 需要在應用中注冊 ContentProvider , 還要與 同步 Service 進行關聯 ;

關聯的方法就是在 同步 Service 注冊的清單檔案中添加中繼資料 meta-data , 在 meta-data 标簽下的 android:resource 屬性中 , 指定賬戶同步的相關資源資料 sync-adapter , sync-adapter 标簽中的 android:contentAuthority 屬性就是指定的該 ContentProvider ;

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="keep_progress_alive.account"
    android:contentAuthority="kim.hsl.keep_progress_alive.provider"
    android:allowParallelSyncs="false"
    android:isAlwaysSyncable="true"
    android:userVisible="false"/>      

sync-adapter 标簽的 android:accountType 就是賬戶類型 , 與之前在 ​​【Android 程序保活】應用程序拉活 ( 賬戶同步拉活 | 賬号服務注冊 | 源碼資源 )​​ 部落格注冊的 account-authenticator 标簽的 android:accountType 是一個值 ;

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="keep_progress_alive.account"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name" />      

sync-adapter 标簽的 android:isAlwaysSyncable 屬性 , 表示該賬戶同步操作 , 是否總是同步 , 這裡設定 true , 賬戶拉活 , 越頻繁越好 ;

sync-adapter 标簽的 android:userVisible 屬性 , 表示是否在 " 設定 -> 賬号 " 界面 , 展示一個賬戶同步開關 , 這裡選擇 false , 不給使用者展示 , 萬一使用者給關了 , 就無法進行賬戶拉活應用程序操作 ;

建立 ContentProvider , 然後在清單檔案中注冊 , 其中 provider 标簽的 android:authorities 就是上述 sync-adapter 标簽中的 android:contentAuthority 屬性值 ;

<!-- 賬戶同步 ContentProvider -->
        <provider
            android:authorities="kim.hsl.keep_progress_alive.provider"
            android:name=".account_service.AccountSyncContentProvider" />      

定義好賬戶同步 Service , ContentProvider , 并在清單檔案中注冊 ;

最後調用 ContentResolver 的 setIsSyncable 方法 , 設定賬戶同步開啟 ,

//建立賬戶
        Account account = new Account("kim.hsl", ACCOUNT_TYPE);
        // 設定賬戶同步開啟
        // 注意 : 該操作西藥權限 android.permission.WRITE_SYNC_SETTINGS
        ContentResolver.setIsSyncable(account, "kim.hsl.keep_progress_alive.provider", 1);      

調用 ContentResolver 的 setSyncAutomatically 方法 , 設定賬戶自動同步 , 注意 : 該操作需要權限 android.permission.WRITE_SYNC_SETTINGS ;

// 設定賬戶自動同步
        ContentResolver.setSyncAutomatically(account, "kim.hsl.keep_progress_alive.provider", true);      

調用 ContentResolver 的 setSyncAutomatically 方法 , 設定賬戶自動同步 , 最後一個參數是同步周期 , 這個值隻是參考值, 系統并不會嚴格按照該值執行 , 一般情況下同步的間隔 10 分鐘 ~ 1 小時 ;

// 設定賬戶同步周期
        // 最後一個參數是同步周期 , 這個值隻是參考值, 系統并不會嚴格按照該值執行
        // 一般情況下同步的間隔 10 分鐘 ~ 1 小時
        ContentResolver.addPeriodicSync(account, "kim.hsl.keep_progress_alive.provider", new Bundle(), 1);      

二、 賬戶同步代碼示例

1、 賬戶同步 Service

package kim.hsl.keep_progress_alive.account_service;

import android.accounts.Account;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class AccountSyncService extends Service {

    // 賬戶同步 IBinder 對象
    private ThreadSyncAdapter mThreadSyncAdapter;

    public AccountSyncService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mThreadSyncAdapter.getSyncAdapterBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mThreadSyncAdapter = new ThreadSyncAdapter(getApplicationContext(), true);
    }

    class ThreadSyncAdapter extends AbstractThreadedSyncAdapter{

        public ThreadSyncAdapter(Context context, boolean autoInitialize) {
            super(context, autoInitialize);
        }

        public ThreadSyncAdapter(Context context, boolean autoInitialize,
                                 boolean allowParallelSyncs) {
            super(context, autoInitialize, allowParallelSyncs);
        }

        @Override
        public void onPerformSync(Account account, Bundle extras, String authority,
                                  ContentProviderClient provider, SyncResult syncResult) {
            // 賬戶同步操作
            // 與資料庫 , 伺服器同步操作 , 這裡隻是為了應用程序拉活 , 不實作具體的邏輯
            Log.i("AccountSyncService", "賬戶同步拉活激活");
        }
    }

}      

2、 賬戶同步 ContentProvider

package kim.hsl.keep_progress_alive.account_service;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class AccountSyncContentProvider 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;
    }
}      

3、 AndroidManifest.xml 清單檔案

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kim.hsl.keep_progress_alive">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission
        android:name="android.permission.GET_ACCOUNTS"
        android:maxSdkVersion="22" />
    <uses-permission
        android:name="android.permission.AUTHENTICATE_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/Theme.Keep_Progress_Alive">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--
            設定最近任務清單中不顯示該 Activity 元件 ( 不要被使用者察覺 )
            android:excludeFromRecents="true"

            設定 Activity 親和性
            讓該界面在一個獨立的任務棧中 , 不要與本應用的其它任務棧放在一起
            避免解除鎖屏後 , 關閉 1 像素界面 , 将整個任務棧都喚醒
            android:taskAffinity="kim.hsl.keep_progress_alive.alive"
        -->
        <activity
            android:name=".one_pixel_activity.OnePixelActivity"
            android:excludeFromRecents="true"
            android:taskAffinity="kim.hsl.keep_progress_alive.onepixel"
            android:theme="@style/OnePixelActivityTheme" />

        <!-- 用于提權的前台程序 -->
        <service
            android:name=".foreground_service.ForegroundService"
            android:enabled="true"
            android:exported="true" />

        <!-- 用于提權的前台程序, 關閉通知操作 -->
        <service
            android:name=".foreground_service.CancelNotificationService"
            android:enabled="true"
            android:exported="true" />

        <!-- 系統 Service 機制拉活 -->
        <service
            android:name=".stick_service.StickService"
            android:enabled="true"
            android:exported="true" />

        <!-- 用于賬戶同步拉活 -->
        <service
            android:name=".account_service.AuthenticationService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>

            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/account_authenticator" />
        </service>

        <!-- 賬戶同步服務 -->
        <service
            android:name=".account_service.AccountSyncService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.content.SyncAdapter" />
            </intent-filter>

            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/sync_adapter" />
        </service>

        <!-- 賬戶同步 ContentProvider -->
        <provider
            android:authorities="kim.hsl.keep_progress_alive.provider"
            android:name=".account_service.AccountSyncContentProvider" />


    </application>

</manifest>      

4、 sync-adapter 配置檔案

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="keep_progress_alive.account"
    android:contentAuthority="kim.hsl.keep_progress_alive.provider"
    android:allowParallelSyncs="false"
    android:isAlwaysSyncable="true"
    android:userVisible="false"/>      

5、 賬戶同步工具類

package kim.hsl.keep_progress_alive.account_service;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;

public class AccountUtils {

    /**
     * 添加賬戶類型
     * 在 account-authenticator xml 标簽中的 android:accountType 屬性中定義的
     */
    public static final String ACCOUNT_TYPE = "keep_progress_alive.account";

    /**
     * 添加賬戶
     * @param context
     */
    public static void addAccount (Context context){
        AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);

        // 需要使用 android.permission.GET_ACCOUNTS 權限
        Account[] accounts = accountManager.getAccounts();

        // 該類型賬号不為空
        if (accounts.length > 0){
            // 賬戶已存在 , 不進行處理

        }else{
            //建立賬戶
            Account account = new Account("kim.hsl", ACCOUNT_TYPE);
            // 添加一個新賬戶
            accountManager.addAccountExplicitly(account, "123456", new Bundle());

        }
    }

    /**
     * 告知系統開始自動同步
     */
    public static void autoSyncStart(){
        //建立賬戶
        Account account = new Account("kim.hsl", ACCOUNT_TYPE);
        // 設定賬戶同步開啟
        // 注意 : 該操作需要權限 android.permission.WRITE_SYNC_SETTINGS
        ContentResolver.setIsSyncable(account, "kim.hsl.keep_progress_alive.provider", 1);
        // 設定賬戶自動同步
        ContentResolver.setSyncAutomatically(account, "kim.hsl.keep_progress_alive.provider", true);
        // 設定賬戶同步周期
        // 最後一個參數是同步周期 , 這個值隻是參考值, 系統并不會嚴格按照該值執行
        // 一般情況下同步的間隔 10 分鐘 ~ 1 小時
        ContentResolver.addPeriodicSync(account, "kim.hsl.keep_progress_alive.provider", new Bundle(), 1);
    }

}      

6、 MainActivity 啟動賬戶同步

package kim.hsl.keep_progress_alive;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;

import kim.hsl.keep_progress_alive.account_service.AccountUtils;
import kim.hsl.keep_progress_alive.foreground_service.ForegroundService;
import kim.hsl.keep_progress_alive.one_pixel_activity.KeepProgressAliveManager;
import kim.hsl.keep_progress_alive.stick_service.StickService;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1.  1 像素 Activity 提升應用權限
        // 注冊廣播接收者 , 1 像素 Activity 啟動的 廣播接收者
        //KeepProgressAliveManager.getmInstance().registerReceiver(this);

        // 2. 通過前台 Service 提升應用權限
        // 啟動普通 Service , 但是在該 Service 的 onCreate 方法中執行了 startForeground
        // 變成了前台 Service 服務
        //startService(new Intent(this, ForegroundService.class));

        // 3. 使用 Service 機制拉活
        //startService(new Intent(this, StickService.class));

        // 4. 賬戶同步拉活
        AccountUtils.addAccount(this);
        // 開始同步
        AccountUtils.autoSyncStart();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 1. 取消注冊廣播接收者, 也可以不取消注冊
        //KeepProgressAliveManager.getmInstance().registerReceiver(this);
    }
}      

7、 運作效果

在 Android 10.0 原生系統中 , 等待 15 分鐘左右 , 沒有拉起應用程序 ;

三、 源碼資源

  • ​GitHub 位址 :​ ​​https://github.com/han1202012/Keep_Progress_Alive​​