一、效果图
1.1 Android设备->设置->账户与安全
1.2 点击“账户标签DEMO”
如果“账户标签DEMO”此时只添加了一个账号的话,点击该条目之后将会直接进入如下界面:1.3 账户标签DEMO中添加了两个账号
如果“账户标签DEMO”此时只添加了多个账号的话,点击该条目之后将会直接进入如下界面:二、撸代码
2.1 AccountManager的使用
申明权限 <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
在java代码中使用AccountManager
package com.study.yang.accoutmanagerdemokotlin
import android.net.Uri
import android.view.View
import android.widget.ArrayAdapter
import android.accounts.Account
import android.accounts.AccountAuthenticatorActivity
import android.accounts.AccountManager
import android.content.*
import android.os.*
import com.study.yang.accoutmanagerdemokotlin.adapter.MyCursorAdapter
import com.study.yang.accoutmanagerdemokotlin.data.DefineAccount
import com.study.yang.accoutmanagerdemokotlin.provider.AUTHORITY
import kotlinx.android.synthetic.main.activity_login.*
import java.util.*
/**
* I want to use Kotlin to implement the program,but
* it's not good to use Kotlin here.
*/
class LoginActivity : AccountAuthenticatorActivity() {
private lateinit var accountManage: AccountManager
private val ACCOUNT_TYPE = "com.study.account"
val accountStrings = arrayListOf<String>()
var handler = object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
var account = msg?.obj as DefineAccount
handlerLoginResult(account.email, account.pwd)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
accountManage = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
//根据账户类型获取账号
accountManage.getAccountsByType(ACCOUNT_TYPE)
?.takeIf {
//判断当前获取到的账号列表是否为空
it.isNotEmpty()
}?.let {
//可变参数时需要引入“*”号
Arrays.asList(*it)
}?.map {
var account = it as Account
accountStrings.add(it.name)
if (it.name == "1") {
val password = accountManage.getUserData(it, "password")
val email = accountManage.getUserData(it, "account")
println(password)
println(email)
/**
* 更新相应账户的UserData
*/
accountManage.setUserData(it, "password", "1111")
}
}
/**
* 给AutoCompleteTextView添加自动提示功能
*/
email.setAdapter(ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, accountStrings))
}
/**
* 登录
*/
fun login(view: View) {
val email = email.text.toString().trim()
val password = password.text.toString().trim()
val msg = handler.obtainMessage()
var defineAccount = DefineAccount(email, password)
msg.obj = defineAccount
handler.sendMessageDelayed(msg, 2000)
}
/**
* 查询用户
*/
fun queryUser(view: View) {
var uri = Uri.parse("content://com.study.account.provider/account")
//cursor不能关闭,一旦关闭CursorAdapter自动更新时将会报错
//此处传入的uri及其子层级的uri发生变动的时候都会被更新
val cursor = contentResolver.query(uri, null, null, null, null)
var cursorAdapter = MyCursorAdapter(this, cursor)
lv.adapter = cursorAdapter
}
/**
* 处理登录结果
*/
private fun handlerLoginResult(email: String, password: String) {
val account = Account(email, ACCOUNT_TYPE)
val userData = Bundle()
userData.putString("account", email)
userData.putString("password", "==$password==")
userData.putString("auth_token", "")
var uri = Uri.parse("content://com.study.account.provider/account")
var values = ContentValues()
values.put("email", email)
values.put("pwd", "==$password==")
contentResolver.insert(uri, values)
/**
* 添加账户,建立App的同步机制
* 例如:
* 在应用中注册能够接受频繁广播事件的广播,在某种程度上可以达到App包活的效果
* 最好是设置自动同步,自动同步是由设备自动触发
*/
accountManage.addAccountExplicitly(account, password, userData)
//自动同步
/**
* 当前账号是否可同步,大于1意味着可以同步
*/
ContentResolver.setIsSyncable(account, AUTHORITY, 1)
/**
* 当前账号是否自动同步
*/
ContentResolver.setSyncAutomatically(account, AUTHORITY, true)
/**
* 当前账号同步的频率
*/
ContentResolver.addPeriodicSync(account, AUTHORITY, userData, 30)
//手动同步
// ContentResolver.requestSync(account, AUTHORITY, userData)
}
}
2.2 AbstractAccountAuthenticator的使用
这个类将会被继承使用,不过需要配合Service使用。应用启动时系统将会通过android:name="android.accounts.AccountAuthenticator"
来启动AccountAuthenticatorService。AccountAuthenticatorService源码如下所示:
package com.study.yang.accoutmanagerdemokotlin.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.study.yang.accoutmanagerdemokotlin.authenticator.Authenticator
class AccountAuthenticatorService : Service() {
lateinit var authenticator: Authenticator
override fun onCreate() {
super.onCreate()
authenticator = Authenticator(this)
}
override fun onBind(intent: Intent): IBinder {
return authenticator.iBinder
}
}
在清单文件中配置AccountAuthenticatorService ,具体配置如下:
<service
android:name=".service.AccountAuthenticatorService"
android:enabled="true"
android:exported="true">
<intent-filter>
<!--这个Action是由系统提供的-->
<action android: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-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountPreferences="@xml/account_preferences"
android:accountType="com.study.account"
android:icon="@drawable/ic_account_balance"
android:label="@string/account_label"
android:smallIcon="@drawable/ic_drafts_black">
<!--
android:accountPreferences这个属性添加之后,当
只添加一个账户的时候也会显示这个应用中的应用账户列表
android:accountType的账户类型,具有唯一性
android:icon和android:smallIcon都是展示图标的,
不过大多数情况下都是显示android:icon指定的图标
android:label指定账户列表下对应应用的标签
-->
</account-authenticator>
Authenticator继承至AbstractAccountAuthenticator,具体源码如下所示:
package com.study.yang.accoutmanagerdemokotlin.authenticator
import android.accounts.AbstractAccountAuthenticator
import android.accounts.Account
import android.accounts.AccountAuthenticatorResponse
import android.accounts.AccountManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.study.yang.accoutmanagerdemokotlin.LoginActivity
import com.study.yang.accoutmanagerdemokotlin.R
class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {
var context = context
/**
* 至于这个方法,没有找到触发的方式
* 个人理解:是根据authTokenType获取authenticator.xml中配置的android:label
* 所以在这个地方赋值为@string/account_label
*/
override fun getAuthTokenLabel(authTokenType: String?): String {
println("getAuthTokenLabel")
return context.getString(R.string.account_label)
}
/**
* accountManage.confirmCredentials(it, null, this@LoginActivity, null, null)
* 调用confirmCredentials
*/
override fun confirmCredentials(response: AccountAuthenticatorResponse?, account: Account?, options: Bundle?): Bundle {
println("confirmCredentials")
return Bundle()
}
/**
* accountManage.updateCredentials(it, ACCOUNT_TYPE, null, this@LoginActivity, null, null)
*/
override fun updateCredentials(response: AccountAuthenticatorResponse?, account: Account?, authTokenType: String?, options: Bundle?): Bundle {
println("updateCredentials")
return Bundle()
}
/**
* accountManage.getAuthToken(it, ACCOUNT_TYPE, null, this@LoginActivity, null, null)
* accountManage.blockingGetAuthToken(account, ACCOUNT_TYPE, true)
* 都会调用getAuthToken方法,blockingGetAuthToken必须在子线程中调用,否则会造成死锁
*/
override fun getAuthToken(response: AccountAuthenticatorResponse?, account: Account?, authTokenType: String?, options: Bundle?): Bundle {
println("getAuthToken")
return Bundle()
}
/**
* accountManage.hasFeatures(it, features, null, null)
*/
override fun hasFeatures(response: AccountAuthenticatorResponse?, account: Account?, features: Array<out String>?): Bundle {
println("hasFeatures")
return Bundle()
}
/**
* accountManage.editProperties(ACCOUNT_TYPE, this@LoginActivity, null, null)
*/
override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?): Bundle {
println("editProperties")
return Bundle()
}
/**
* 该方法在手机中“设置->安全与账户->
* 用户和账户列表页面点击“添加”按钮->
* 添加账户列表->选中自己的应用->
* 调用addAccount方法”
*/
override fun addAccount(response: AccountAuthenticatorResponse?, accountType: String?, authTokenType: String?, requiredFeatures: Array<out String>?, options: Bundle?): Bundle {
println("addAccount")
val addAccountIntent = Intent(context, LoginActivity::class.java)
addAccountIntent.putExtra("authTokenType", authTokenType)
if (options != null) {
addAccountIntent.putExtras(options)
}
//一定要把response传入intent的extra中,便于将登录操作的结果回调给AccountManager
addAccountIntent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
var bundle = Bundle()
bundle.putParcelable(AccountManager.KEY_INTENT, addAccountIntent)
return bundle
}
}
2.3 AbstractThreadedSyncAdapter使用
这个类将会被继承使用,不过需要配合Service使用。应用启动时系统将会通过android:name="android.content.SyncAdapter"
来启动SyncAdapterService。SyncAdapterService源码如下所示:
package com.study.yang.accoutmanagerdemokotlin.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.study.yang.accoutmanagerdemokotlin.adapter.SyncAdapter
class SyncAdapterService : Service() {
lateinit var syncAdapter: SyncAdapter
override fun onCreate() {
super.onCreate()
syncAdapter = SyncAdapter(this, true)
}
override fun onBind(intent: Intent): IBinder {
return syncAdapter.syncAdapterBinder
}
}
在清单文件中配置SyncAdapterService ,具体配置如下:
<service
android:name=".service.SyncAdapterService"
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>
其中sync_adapter.xml具体内容如下所示:
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.study.account"
android:allowParallelSyncs="false"
android:contentAuthority="com.study.account.provider"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="false">
<!--
android:accountType跟authenticator.xml中的android:accountType一致
android:allowParallelSyncs是否支持并行同步
android:contentAuthority指定相应内容提供者的authority
android:isAlwaysSyncable是否支持同步
android:supportsUploading是否支持上传
android:userVisible用户是否可见,false时用户看不到APP的同步时间
-->
</sync-adapter>
SyncAdapter继承至AbstractThreadedSyncAdapter,具体源码如下所示:
package com.study.yang.accoutmanagerdemokotlin.adapter
import android.accounts.Account
import android.content.AbstractThreadedSyncAdapter
import android.content.ContentProviderClient
import android.content.Context
import android.content.SyncResult
import android.net.Uri
import android.os.Bundle
class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedSyncAdapter(context, autoInitialize) {
/**
* 不管系统自动同步还是手动同步,都会调用这个方法
* 在这个方法中可以处理与服务器交互从而同步数据
*
* account:当前同步的账号
* extras:同步的额外参数,目前没发现效果
* authority:sync_adapter.xml中配置的contentAuthority的值
* provider:与authority相匹配的ContentProvider的Client
* syncResult:同步结果
*/
override fun onPerformSync(account: Account?, extras: Bundle?, authority: String?,
provider: ContentProviderClient?, syncResult: SyncResult?) {
// context.contentResolver.notifyChange()
println("${account?.name}===${account?.type}")
println("$authority")
println(extras)
var uri = Uri.parse("content://com.study.account.provider/account")
val projections = arrayOf("email", "pwd")
val cursor = provider?.query(uri, projections, "email=?", arrayOf(account?.name), null)
cursor?.takeIf {
it.count > 0
}.let {
it?.moveToFirst()
println(it?.getString(it.getColumnIndex("pwd")))
}
}
}