Android里帐户同步的实现

简介

我们在手机系统设置里会有“帐户”一项功能,点击进去会发现有一些我们认识的APP在里头,如QQ、微信、邮箱等。没错,这就是Android系统提供的帐户同步功能。任何第三方APP都可以通过此功能将数据在一定时间内同步到服务器中去。比如微信会将当前系统中登录的微信帐户的信息、聊天记录同步到微信服务器上去,又比如Android原生系统中可以使用Google账号进行数据的同步。这里顺便提一下,系统在将APP帐户同步时,也会将深睡中的APP进程拉活,这点我们在文章后面也会说到。好了,下面我们将一步一步地来实现一个帐户同步的Demo。

创建帐户服务

第一步,定义一个action为android.accounts.AccountAuthenticator的Intent的Service,并在meta-data的resource属性指定一定说明该Account基本显示信息的xml文件,AndroidMainifest.xml添加代码如:

<service
    android:name="project.test.com.myapplication.AuthenticationService"
   android:enabled="true"
   android:exported="true">
   <intent-filter>
       <actionandroid:name="android.accounts.AccountAuthenticator" />
   </intent-filter>
   <meta-data
        android:name="android.accounts.AccountAuthenticator"
       android:resource="@xml/authenticator" />
</service>

authenticator.xml:

<?xml version="1.0"encoding="utf-8"?>
<account-authenticatorxmlns:android="http://schemas.android.com/apk/res/android"
   android:accountType="project.test.com.myapplication.account.type"
   android:icon="@mipmap/ic_launcher"
   android:smallIcon="@mipmap/ic_launcher"
   android:label="@string/app_name"
    />

注意

android:accountType表示的是您的Account的类型,它必须是唯一的

第二步,创建帐户Service,并在Service的onBind中调AbstractAccountAuthenticator的getIBinder()返回其用于远程调用的IBinder:

public class AuthenticationService extends Service {
    privateAuthenticationService.AccountAuthenticator mAuthenticator;
 
    private AuthenticationService.AccountAuthenticator getAuthenticator() {
        if(mAuthenticator == null)
           mAuthenticator = new AuthenticationService.AccountAuthenticator(this);
        return mAuthenticator;
    }
 
    @Override
    public void onCreate() {
       mAuthenticator = new AuthenticationService.AccountAuthenticator(this);
    }
 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return getAuthenticator().getIBinder();
    }
 
    class AccountAuthenticator extends AbstractAccountAuthenticator {
 
        public AccountAuthenticator(Context context) {
           super(context);
        }
 
       @Override
        public Bundle editProperties(AccountAuthenticatorResponse response, StringaccountType) {
            return null;
        }
 
       @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,String authTokenType, String[] requiredFeatures, Bundle options) throwsNetworkErrorException {
           return null;
        }
 
       @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Accountaccount, 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, Accountaccount, String authTokenType, Bundle options) throws NetworkErrorException {
           return null;
        }
 
       @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,String[] features) throws NetworkErrorException {
           return null;
        }
    }
}

说明:

我们在AuthenticationService类中定义了一个继承于AbstractAccountAuthenticator的内部类AccountAuthenticator,然后在AuthenticationService的onBind方法中返回了AccountAuthenticator类对象的getIBinder()。

通过这简单的两步,我们运行程序后再在“设置”-“帐户”-“添加帐户”中的列表中可以发现到我们的app了。

如图:

添加帐户

您会发现,像微信,如果当前手机微信没有登录过的话,在帐户列表中点击微信会自动添转到一个登录微信的Activity,但点击我们刚才app是跳转到一个空白没有任务操作,那是因为我们只是创建了帐户服务,还没有对其进行添加帐户的代码处理。

第一步,声明两个必要的权限:

<uses-permissionandroid:name="android.permission.GET_ACCOUNTS" />                           // 查看帐户需要权限
<uses-permissionandroid:name="android.permission.AUTHENTICATE_ACCOUNTS" />               // 添加帐户需要权限

第二步,添加AuthenticatorActivity:

public class AuthenticatorActivity extendsAppCompatActivity {
 
    public static final String ACCOUNT_TYPE ="project.test.com.myapplication.account.type";    // TYPE必须与account_preferences.xml中的TYPE保持一致
    private AccountManager mAccountManager;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_authenticator);
 
       mAccountManager = (AccountManager)getSystemService(ACCOUNT_SERVICE);
       Account[] accounts =mAccountManager.getAccountsByType(ACCOUNT_TYPE);   // 获取系统帐户列表中已添加的帐户是否存在我们的帐户,用TYPE做为标识
        if(accounts.length > 0) {
           Toast.makeText(this, "已添加当前登录的帐户",Toast.LENGTH_SHORT).show();
           finish();
        }
 
        ButtonbtnAddAccount = (Button)findViewById(R.id.btn_add_account);
        btnAddAccount.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               Account account = new Account(getString(R.string.app_name),ACCOUNT_TYPE);
               mAccountManager.addAccountExplicitly(account, null, null);     // 帐户密码和信息这里用null演示
 
               finish();
            }
        });
    }
}

说明:

1、Activity中定义的成员变量ACCOUNT_TYPE用于我们当前APP获取系统帐户的唯一标识,这个在前面account_preferences.xml中也有提过,两处的声明必须是一致;

2、在onCreate中去获取系统帐户列表中已添加的帐户是否存在我们的帐户,用我们的TYPE做为标识,如果存在表示已经添加过,则退出当前页面;

3、如若从未添加过帐户,则模拟了一个登户操作,在操作成功后使用AccountManager的addAccountExplicitly方法往系统帐户中添加我们的帐户。

第三步,在AccountAuthenticator类中的addAccount方法里添加跳转登录的Activity:

@Override
public Bundle addAccount(AccountAuthenticatorResponseresponse, String accountType, String authTokenType, String[] requiredFeatures,Bundle options)
        throwsNetworkErrorException {
    final Bundle bundle = new Bundle();
    final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,response);
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;
}

我们再次运行程序,在“添加帐户”的点击和模拟登录后,便可在“设置”-“帐户”中看到我们的APP已添加了帐户了,如图:

同步帐户数据

当我们都完成了上面添加帐户的操作后,试着在帐户中点击会跳转到一个页面,如图:

这个页面只是列出了我们APP的图标和名称,并没有其它的操作和我们预期中的同步功能。所以接下来我们继续为APP加入同步的服务代码。

第一步,定义一个action为android.content.SyncAdapter的Intent的Service,并在meta-data的resource属性指定一定说明该同步基本显示信息的xml文件,AndroidMainifest.xml添加代码如:

<service
   android:name=".SyncService"
   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>

sync_adapter.xml:

<?xml version="1.0"encoding="utf-8"?>
<sync-adapterxmlns:android="http://schemas.android.com/apk/res/android"
   android:accountType="project.test.com.myapplication.account.type"
   android:allowParallelSyncs="false"
   android:contentAuthority="project.test.com.myapplication.account.provide"
   android:isAlwaysSyncable="true"
   android:supportsUploading="false"
android:userVisible="true"/>

说明:

android:accountType表示的是您的Account的类型,一定要跟前面保持一致

android:allowParallelSyncs 是否支持多账号同时同步

android:contentAuthority 指定要同步的ContentProvider

android:isAlwaysSyncable 设置所有账号的isSyncable为1

android:supportsUploading 设置是否必须notifyChange通知才能同步

android:userVisible 设置是否在“设置”中显示

第二步,创建同步Service,并在Service的onBind中调AbstractThreadedSyncAdapter的getIBinder()返回其用于远程调用的IBinder:

public class SyncService extends Service {
 
    private static final Object sSyncAdapterLock = new Object();
    private static SyncAdapter sSyncAdapter = null;
 
    @Override
    public voidonCreate() {
       synchronized (sSyncAdapterLock) {
            if(sSyncAdapter == null) {
               sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }
 
    @Nullable
    @Override
    public IBinderonBind(Intent intent) {
        returnsSyncAdapter.getSyncAdapterBinder();
    }
 
    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 实现数据同步
            // getContext().getContentResolver().notifyChange(AccountProvider.CONTENT_URI,null, false);        }
    }
}

说明:

1、我们在SyncService类中定义了一个继承于AbstractThreadedSyncAdapter的内部类SyncAdapter,然后在SyncService的onBind方法中返回了SyncAdapter类对象的getIBinder()。

2、SyncAdapter类中onPerformSync方法就是实现同步数据到服务器的代码,这里省略

第三步,上面我们在sync_adapter.xml中指定了一个ContentProvider,所以此刻当然是创建用于上传的ContentProvider:

<provider
   android:name=".AccountProvider"
   android:authorities="project.test.com.myapplication.account.provide"
   android:exported="false"
   android:syncable="true"/>
public class AccountProvider extends ContentProvider{
 
    publicstatic final String AUTHORITY ="project.test.com.myapplication.account.provide";
    publicstatic final String CONTENT_URI_BASE = "content://" + AUTHORITY;
    publicstatic final String TABLE_NAME = "data";
    publicstatic final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" +TABLE_NAME);
 
    @Override
    publicboolean onCreate() {
        returnfalse;
    }
 
    @Nullable
    @Override
    publicCursor query(Uri uri, String[] projection, String selection, String[]selectionArgs, String sortOrder) {
        returnnull;
    }
 
    @Nullable
    @Override
    publicString getType(Uri uri) {
        returnnull;
    }
 
    @Nullable
    @Override
    public Uriinsert(Uri uri, ContentValues values) {
        returnnull;
    }
 
    @Override
    public intdelete(Uri uri, String selection, String[] selectionArgs) {
        return0;
    }
 
    @Override
    public intupdate(Uri uri, ContentValues values, String selection, String[] selectionArgs){
        return0;
    }
}

说明:

这里我们只是用于演示同步数据,并没有对ContentProvider进行实际操作,大家在实际开发中可以自行去完善。

最后一步,执行同步。我们来修改AuthenticatorActivity的模拟登录代码,使在在登录成功后将其设置为自动同步:

……
Button btnAddAccount =(Button)findViewById(R.id.btn_add_account);
btnAddAccount.setOnClickListener(newView.OnClickListener() {
    @Override
    public voidonClick(View v) {
        Accountaccount = new Account(getString(R.string.app_name), ACCOUNT_TYPE);
       mAccountManager.addAccountExplicitly(account, null, null);                          // 帐户密码和信息这里用null演示
        // 自动同步
        Bundle bundle= new Bundle();
       ContentResolver.setIsSyncable(account, AccountProvider.AUTHORITY, 1);
       ContentResolver.setSyncAutomatically(account, AccountProvider.AUTHORITY,true);
       ContentResolver.addPeriodicSync(account, AccountProvider.AUTHORITY,bundle, 30);    // 间隔时间为30秒
        // 手动同步
        //ContentResolver.requestSync(account, AccountProvider.AUTHORITY, bundle);
       finish();
    }
});

说明:

账户信息同步其实有两种方式,一种是自动同步,就是在预设定的时间间隔,让Android系统帮我们完成自动同步数据。这里要注意的是,因为Android本身为了考虑同步所带来的消耗和减少唤醒设备的次数,所以这里预设的时间并一定准确,系统其实在内部做了一些同步的算法处理,例如尽量将所有需要同步数据都安排在某个时间点,所以我们预设的那个时间值仅仅是作为一个参考罢了。另一种方式是手动同步,手动同步则是在应用中调用某个方法直接告诉设备,通知系统同步数据。

当我们再次在已添加的帐户列表中点击我们的APP后会变成这样,如图:

更改同步页面

如果您希望在已添加的帐户列表中点击我们的APP后出现一个自定义的页面,那么就得

修改authenticator.xml文件,加入accountPreferences属性:

<?xml version="1.0"encoding="utf-8"?>
<account-authenticatorxmlns:android="http://schemas.android.com/apk/res/android"
   android:accountType="project.test.com.myapplication.account.type"
   android:icon="@mipmap/ic_launcher"
    android:smallIcon="@mipmap/ic_launcher"
   android:label="@string/app_name"
   android:accountPreferences="@xml/account_preferences"
/>

account_preferences.xml文件:

<?xml version="1.0"encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
   <PreferenceCategoryandroid:title="PreferenceCategory_title" />
   <PreferenceScreen
       android:key="key1"
       android:title="PreferenceScreen_title"
       android:summary="PreferenceScreen_summary">
       <intent
           android:action="key1.ACTION"
           android:targetPackage="key1.package"
           android:targetClass="key1.class" />
   </PreferenceScreen>
</PreferenceScreen>

我们再次运行APP,然后往已添加的帐户列表中点击我们的APP后便会出现一个中间页面,如图:

拉活机制

对于某些APP来说,当然都希望自己的进程尽量的不被杀死。于是乎,就出现的各种进程保活的方法,其中帐户同步也算为其中的一种。因为系统帐户自动同步会在特定时间时同步APP数据到服务器上去。上面实现也看到,登录帐户和执行同步帐户数据都是通过我们自己的代码来实现,所以在同步数据时会把已退出或完全结束的APP再次拉活起来。笔者在部分手机中验证过我们今天介绍的Demo,发现效果还是行得通的。当然由于国内手机厂商的各种订制和阉割,也会出现某部分手机是行不通的,大家有时间可以自行去尝试。这点也算是Android系统的一个小漏洞。希望大家能善用帐户同步功能,养成良好的开发信仰,尊重用户意愿。因为并不是所有用户都是愿意让不相关的APP任其后台常驻的!

总结

本文只为简单介绍和演示Android帐户同步的实现步骤,大家如若需要更完整的帐户同步实现,有兴趣可以自行去研究一下在Anroid SDK中samples目录下也有一个叫SampleSyncAdapter的帐号与同步的实例。另外上面介绍的Demo源码可以点击这里下载

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值