文章目錄
-
- 一、 賬戶同步
- 二、 賬戶同步代碼示例
-
- 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