账户同步的作用 : 如果应用的数据发生了改变 , 可以通过账户进行同步 , 进而与服务器进行数据同步操作 , 执行同步时 , 系统会拉活对应的应用进程 ;
实现的话,主要是应用 APP 中可以注册 " 账户服务 Service " , 应用安装后 , 如果系统发现应用中有该类型服务 , 就会为该应用开放添加账户的功能 ;
系统通过 Binder 机制 , 操作用户的 " 账户服务 Service " ;
第三方应用可以通过该账户服务 , 将数据同步 到服务器中 ;
系统在进行应用账户同步时 , 会自动将对应的应用拉活 ;
Google 官方提供了账户同步案例 , https://github.com/googlearchive/android-BasicSyncAdapter , 已经停止维护了 , 但是项目还是有参考价值的 ; ( 源码放在了本博客的资源文件中 )
在上述项目中指向了推荐的后台任务示例 https://github.com/android/background-tasks-samples ; ( 源码放在了本博客的资源文件中 )
说白了,进程拉活只是账户同步的附带作用 ;
具体实现:
1.创建一个账号同步工具类
public class AccountUtil {
private static final String TAG = "AccountHelper";
private static final String ACCOUNT_TYPE = "com.example.appprocesskeepalive.accounttype";
private static final String ACCOUNT_AUTHORITY = "com.example.appprocesskeepalive.provider";
private static final String ACCOUNT_NAME = "app账号同步demo";
/**
* 添加账号
* @param context
*/
public static void addAccount(Context context) {
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
// 获得此类型的账户
// 需要增加权限 GET_ACCOUNTS
Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
if (accounts.length > 0) {
Log.e(TAG, "账户已存在");
return;
}
Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
// 给这个账户类型添加一个账户
// 需要增加权限 AUTHENTICATE_ACCOUNTS
accountManager.addAccountExplicitly(account, "xx", new Bundle());
}
/**
* 设置账户自动同步
*/
public static void sync() {
Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
// 下面三个都需要同一个权限 WRITE_SYNC_SETTINGS
// 设置同步
ContentResolver.setIsSyncable(account, ACCOUNT_AUTHORITY, 1);
// 自动同步
ContentResolver.setSyncAutomatically(account, ACCOUNT_AUTHORITY, true);
// 设置同步周期
ContentResolver.addPeriodicSync(account, ACCOUNT_AUTHORITY, new Bundle(), 1);
}
}
2.账号服务注册
注册一个服务 , 该服务的 onBind 方法返回 AbstractAccountAuthenticator 对象的 Binder , 只要该应用安装 , 就可以在 " 设置 -> 账号 " 中查看该应用的账号
public class AuthService 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);
}
public static class AccountAuthenticator extends AbstractAccountAuthenticator {
private Context mContext;
public AccountAuthenticator(Context context) {
super(context);
mContext=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 {
AccountUtil.addAccount(mContext);
AccountUtil.sync();
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;
}
}
}
3.注册一个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;
}
}
4. 注册一个服务,这个服务提供一个Action给系统以便系统能找到它;然后就是继承和实现AbstractThreadedSyncAdapter,此类中包含实现了ISyncAdapter.Stub内部类,这个内部类封装了远程接口调用
public class SyncService extends Service {
private SyncAdapter mSyncAdapter;
private static final String TAG = "SyncService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mSyncAdapter.getSyncAdapterBinder();
}
@Override
public void onCreate() {
super.onCreate();
mSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
public 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.i("AccountSyncService", "账户同步拉活激活");
Log.i("AccountSyncService","是否还在运行:"+isServiceRunning(App.instance,"com.example.appprocesskeepalive.WakeUpService"));
Intent intent=new Intent(App.instance, WakeUpService.class);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
ContextCompat.startForegroundService(App.instance, intent);
}else {
startService(intent);
}
}
}
public static boolean isServiceRunning(Context context, String serviceName) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> runningServices = activityManager.getRunningServices(Integer.MAX_VALUE);
if (runningServices != null) {
for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) {
if (serviceInfo.service.getClassName().equals(serviceName)) {
return true;
}
}
}
return false;
}
}
核心也是这块onPerformSync,收到账号同步的回调,收到后启动一个前台Service,
public class WakeUpService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 在这里启动你的应用
startForeground(110, getNotification());
Intent intent2 = getPackageManager().getLaunchIntentForPackage("com.example.appprocesskeepalive");
if (intent2 != null) {
//setFlags看自己情况使用,也可以不调用
intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(intent2);
}
return START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "My Foreground Service Channel";
String description = "This channel is used for my foreground service";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("测试保活", name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
private Notification getNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Builder(this, "测试保活")
.setContentTitle("My Foreground Service")
.setContentText("Running...")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.build();
}
}
在service中启动一个activity,但是要注意的是,如果要启动activity的话,需要申请一个悬浮窗权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然后就是配置文件的注册了
<service
android:name=".auth.AuthService"
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_auth" />
</service>
<service
android:name=".auth.SyncService"
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/up" />
</service>
<provider
android:name=".auth.SyncProvider"
android:authorities="@string/account_authority"
android:exported="false"
android:syncable="true" />
<service
android:name=".WakeUpService"
android:enabled="true"
android:exported="true"
android:persistent="true"
android:stopWithTask="false" />
其中 android:persistent是表示app常驻内存的意思,虽然其实没啥大作用,app被杀死了后,它服务该死还是得死--
account_auth.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:accountType="@string/account_type"
android:smallIcon="@mipmap/ic_launcher"/>
这块是用来配置账号同步在设置界面的UI显示的,要注意的时候此处的accountType要跟AbstractAccountAuthenticator执行创建账号、同步账号时的accountType要一样
up.xml
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type"
android:contentAuthority="@string/account_authority"
android:userVisible="true"
android:supportsUploading="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"/>
<!-- android:isAlwaysSyncable 属性 , 表示该账户同步操作 , 是否总是同步 , 这里设置 true , 账户拉活 , 越频繁越好 ;-->
<!-- android:userVisible 属性 , 表示是否在 " 设置 -> 账号 " 界面 , 展示一个账户同步开关 , 这里选择 false , 不给用户展示 , 万一用户给关了 , 就无法进行账户拉活应用进程操作 ;-->
写的可能有点乱,具体实现在这个demo: