使用Android 中AccountManager 组件构建自己的账号登陆系统,遇到了一些坑,记录下来,防止自己忘记。
任务由来:现在我们团队正在和G**合作,给对方提供一整套系统(ROM)包括:Launcher,开机向导SetupWizard 以及定制过的keyBoard等等等一系列东西。在 SetupWizard 中涉及到G** 账号登陆和 Google 账号登陆,G**在需求文档中明确说需要使用 AccountManager 这个东西储存已经登陆过的账号信息。
AccountManager 是个啥?它有什么用处?它在登陆自定义账号,存储登陆信息的时候有什么特点?我:不知道。
要么说我们人类会特么使用工具呢,我特么就想最好是我能在github 上能找到一个demo,然后改吧改吧,卧槽,可以用了!功夫不负有心人,github 上有一个翻译大神:
其他任何地方的文档,基本上都是在这个翻译之后的copy。暂且就先按照这个文档走吧。AccountManager 是什么,为什么要用 AccountManager 都在这文档里讲到了。而且你大可以按照译文自己实现一遍,这里是我按照译文写的demo:
是不是很简单,AccountManager 的使用呢,这里就完了。
扯淡,后面还有很多坑呢。
登陆G**账号的时候,G** 的方式是给我url ,我负责在WebView中加载就可以了,卧槽,这特么和上面demo中的不一样,我已有的样例中是使用原生API进行联网请求登陆的哇!~
之前没接触过Java 和h5 的互相调用,我还担心这个地方用到了呢,然后思考的方向给偏了,后来经过我右边那个哥们提醒,我才转过来:都是登陆过程,从服务器请求回来一串字符串而已,我不需要关心太多,只需要在webView中对shouldOverrideUrlLoading( )方法进行拦截判断就行了。搞定。(当然搞定之前废了挺大劲,G** 给的文档不完整,而且在整个登陆过程中的数据传递和参数设置还兼具错误...啊啊啊,不得不吐槽一下:外国人做事情也不怎么认真啊!)
真的搞定了么,其实并没有,我只是封装使用了accountManager.addAccountExplicitly( ) 方法添加了一个自定义账号到我自己的应用,但是当我在 系统设置 -> 账号 -> 添加账号 -> 添加一个GEAccount 的时候,会直接白屏,为啥,因为当点击添加一个GEAccount 账号的时代码会直接回调到我自定义类GEAuthenticator( ) 中的addAccount( )方法,而我没有在这个方法中做任何处理,直接返回了null,我厉害吧?~
卧槽,也就是说,我这个工作只是完成了也就一半吧?卧槽。
这时候我又考虑偏了:现在G**账号的登陆,会不会不适用AccountManage 啊?因为此处的账号登陆和我之前遇到的账号登陆过程是不同的:在这里,我们有两个app(分别是SetupWizard 和 Smart-Home) 需要用到G** 账号的登陆,但是当用户在某个app里面注册以及登陆成功之后,在AccountManager 里面是不保存任何用户的账号和密码的,这里面只会保存对应app的对应账号对应的UserID 和MDT数据(因为本来嘛,用户在WebView 里面完成的注册和登陆,我这里又没涉及到Java 和 h5互调,所以我肯定是拿不到用户的名字和密码啦)。
注意标红色描述:对应app 对应账号对应的xxx数据。那我就想了,在系统设置 -> 账号 -> 添加账号 -> 添加一个GEAccount 的时候,回调到了自定义类GEAuthenticator( ) 中的addAccount( )方法,我特么怎么知道此时用户想特么创建哪个app 的账号啊(因为我在保存UserID 和MDT数据 的时候,我是使用了自定义账户类型,把不同app 的唯一 client_id 作为名字或者说是KEY来保存的,我如果不知道用户想创建哪个app 的账号,我他么怎么知道在WebView 中加载哪个url 啊?另外,我特么怎么在注册/登陆成功之后创建自定义账号并保存信息到AccountManager 啊??)
后来我想,直接做一个弹框,让用户选择想创建的账号不就可以了么。我擦。。
好吧,这个问题算是又解决,不过不得不说,在设置中创建G**账号的方式,G**至今都没有给一个明确的流程,所以这个流程是我自己体验了一把产品经理。
好了,到这里呢,AccountManager 的使用呢就完了。。。嘛?没有。
为啥会报错:accountManager.notifyAccountAuthenticated(account);//V23 看了api 对系统版本要23。。
因为我们做的这套东西是需要在美国上市使用的,需要过GMS 认证流程,在这套流程中,有一个要求:这特么整套东西都必须在Android 8.x 的基础上跑起来~有木有很刺激啊,我特么还没见过8.x 呢。。。
创建8.x 模拟器,在server编译image ,给模拟器刷image,然后跑自己的应用,好复杂。
哎,终于以为大功告成了,突然,今天,我特么看到一个报错:
java.lang.SecurityException: uid 10080 cannot explicitly add accounts of type: com.xxxxx.accountauthenticator
卧槽,卧槽,卧卧槽。
经过右边那哥们提醒,找到源码中报错位置:
hegd@server5:~/aosp-android8/frameworks$ find . -name AccountManagerService.java
./base/services/core/java/com/android/server/accounts/AccountManagerService.java
hegd@server5:~/aosp-android8/frameworks$
应该是line 458 抛出的错误,跟代码,后来在line 5476 看到应该是在getTypesManagedByCaller( ) 里给我返回了一个false,为啥呢?上网搜到:
这哥们的回答不就是安卓开发论坛里的原话么:
This method requires the caller to have a signature match with the authenticator that owns the specified account.
但是,此时的我还没有意识到问题到底出在哪里,算了,不找了,反正找不到了,额,明天再看。
继续跟,安卓开发论坛的确说到过使用addAccountExplicitly( ) 方法的时候需要注意 :This method requires the caller to have a signature match with the authenticator that owns the specified account.
可是现在已经报错了,我特么该怎么办,我也很绝望啊。。
赫然看到这个 AccountManagerService.java 类的 line 5474 :
// returns true for applications with the same signature as authenticator.
再结合我出这个错误的情景:
我首先运行SetupWizard ,登陆G** 账号成功,然后再运行我的demo ,登陆G** 的账号,就会在 accountManager.addAccountExplicitly() 出错了。把app 运行顺序反过来运行, 是一样的报错。那如果我把我自己的app 签上SetupWizard 的签名后会是什么情况?果然不报错了。那我给我自己demo 随便签一个名呢?报一样的错。。
(另外再参考以下:
Google:https://developer.android.com/reference/android/accounts/AccountManager#addAccountExplicitly(android.accounts.Account,%20java.lang.String,%20android.os.Bundle)
https://developer.android.com/training/id-auth/custom_auth#TaskFour
Stackoverflow:https://stackoverflow.com/questions/33270613/android-app-crashes-on-addaccountexplicitlyaccount-password-null
CSDN :https://blog.csdn.net/cbk861110/article/details/50516240
掘金 :https://juejin.im/entry/58b3f04b8d6d810057f65c99
Android系统会为每一个应用分配一个唯一的UID,具有相同UID的应用才能共享数据,另个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以
总结:
方法 :addAccountExplicitly( ) 要求调用者与拥有指定账户的认证者进行签名匹配。
)
到这,我终于明白了:This method requires the caller to have a signature match with the authenticator that owns the specified account.
多么痛的领悟。
匆忙中写下来,可能会有遗漏之处,如果你在使用AccountManager 的时候有任何问题,欢迎评论留言我们一起尝试解决。
Github : myDemo