Android定义getUserId()函数触发SecurityException崩溃问题咨询
崩溃原因
这个崩溃是三星Android 11定制系统的代码bug导致的,和业务逻辑本身无关,触发逻辑如下:
- Kotlin中在MainActivity(继承自AppCompatActivity/Activity,属于Context子类)里定义的
fun getUserId() = 1,编译后会生成签名为public int getUserId()的public方法,和Android框架中Context类自带的同名系统方法签名完全一致。注意Context#getUserId()是系统隐藏API,普通开发时IDE不会提示重写该方法,因此开发者很容易无意中定义同名方法触发冲突。 - 三星在定制Android 11的剪贴板服务逻辑时,修改了EditText点击后的剪贴板更新流程:点击EditText唤起输入法前,系统会通过反射递归查找当前界面对应的Context实例(也就是你的MainActivity实例)的
getUserId()方法,用返回值作为调用剪贴板服务的目标用户ID,原本预期拿到框架方法返回的当前进程所属合法用户ID(主空间应用默认返回0)。 - 由于你在Activity子类中自定义了同签名的public
getUserId()方法,反射逻辑优先匹配到了你定义的方法,拿到的返回值是硬编码的1。系统校验时发现当前应用是属于用户0的普通第三方应用(uid格式为u0axxx),却要访问用户1的剪贴板服务,而普通应用没有android.permission.INTERACT_ACROSS_USERS_FULL这个系统签名级权限,直接抛出SecurityException触发崩溃。 - 把方法名改成其他名称后,反射找不到自定义的同名方法,会正常调用框架层的
getUserId()拿到合法用户ID,所以应用可以正常运行。
验证与规避方案
- 验证:把你自定义的
getUserId()返回值改成0,再点击EditText就不会触发崩溃,完全匹配上述逻辑。 - 规避方式:
- 直接将自定义方法重命名为不和系统方法重名的名称,比如
getCurrentLoginUserId(),是成本最低的解决方案。 - 如果一定要保留
getUserId()这个方法名,将方法修饰符改为private,反射逻辑不会优先匹配非public的自定义方法,也能规避该问题。
- 直接将自定义方法重命名为不和系统方法重名的名称,比如
- 注意:该问题仅出现在搭载三星One UI(对应Android 11版本)的特定机型上,是三星定制代码未做方法归属校验导致的兼容性bug,原生AOSP系统不存在该问题。
内容的提问来源于stack exchange,提问作者Ara Hakobyan
相关产品推荐
相关产品推荐

