
最近在做静默安装和卸载功能的时候,应用程序放到系统分区(/system/priv-app/)执行pm命令实现静默安装和卸载的方式在7.0上安装、卸载失败。Google 在N上加强了安全权限,对此用本文来记录如何解决7.0上的静默安装和静默卸载。
静默安装
复现错误
通过7.0之前的安装方法,执行静默安装sdcard下的一个应用,然后安装失败,抛出以下异常log:
1 | 06-27 11:01:10.483 D/MainActivity: onClick: /sdcard/com.ifeng.news2.apk |
Error: java.lang.SecurityException: Permission Denial: runInstallCreate from pm command asks to run as user -1 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL
解决错误
参考:Android7.0的静默安装失败问题研究
得知pm安装的命令:Runtime.getRuntime().exec(“pm install -i 包名 –user 0 apkpath”)
一开始,我被这个“包名”坑了:
1 | 06-27 14:58:44.239 D/MainActivity: onClick: /sdcard/com.ifeng.news2.apk |
Error: java.lang.SecurityException: Package com.ifeng.news2 does not belong to 10045
那么,我们来看看belong to 10045的包是什么?
可以看到10045对应的包是我当前的这个测试应用的包名com.wt.testshortcut,而不是指定需要安装apk应用的包名com.ifeng.news2
因此,我们再改之:
1 | 06-27 16:17:58.843 D/MainActivity: onClick: /sdcard/com.ifeng.news2.apk |
从上面的log可以看出,我自定义的ApkStatusReceiver监听包安装、卸载、替换的广播,收到了一个包名为com.ifeng.news2的应用被添加,也就是我刚刚我们需要静默安装上去的应用,此时手机已确认该应用被成功安装。
最终pm install的命令就是:pm install -i 作为安装者的应用包名 –user 0 需要安装的应用在移动设备上的路径
源码部分
以上截图,可以看到第01515行的pm安装命令: pm install [-lrtsfd] [-i PACKAGE] [–user USER_ID] [PATH]
以上截图,可以看到-i: specify the installer package name (指定安装程序包名称),而不是指定被安装程序包名称,由此也可以推断出上面的安装命令
后记
- 不要忘记在AndroidManifest.xml添加 “android.permission.INTERACT_ACROSS_USERS_FULL” 权限
- 如果项目不是在Android源码里面编译的,而是直接在Android Studio上面开发完成,然后push到/system/priv-app/下面的,在执行sdcard下apk的安装,是需要请求外置存储读写权限的,若没有开启,将会抛出异常:
1 | PackageUtils: installSilent successMsg:, ErrorMsg:Error: failed to write; /sdcard/com.ifeng.news2.apk (Permission denied) |
Error: failed to write; /sdcard/com.ifeng.news2.apk (Permission denied)
最后,我们来看看在Terminal终端执行pm install的情况:
可以看到,之前在7.0上面执行失败的命令和成功命令都能够在Terminal终端上面执行静默安装成功。
以上就是关于7.0上静默安装的内容,接下来看看静默卸载 …..
静默卸载
复现错误
在7.0上使用pm uninstall package 命令,会抛出一下异常log:
1 | 06-27 20:12:02.631 D/MainActivity: onClick: com.ifeng.news2 |
通过以上的log,分析可以看出,出现的错误和上面静默安装的错误相同,都是:
ErrorMsg:Security exception: Permission Denial: runUninstall from pm command asks to run as user -1 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL
解决错误
我们现在来看上面静默安装(#源码部分)的第一张图,看到第01522行,命令显示的是:pm uninstall [-k] [–user USER_ID] PACKAGE
确定我们的判断是没有问题的,在7.0上执行pm指令的时候,都需要跟上[–user USER_ID]
注:其中[-k]是代表是否需要保留需要卸载应用包名的数据
因此,我们修改执行命令,同静默安装一样增加–user 0:
1 | 06-27 20:28:59.893 D/MainActivity: onClick: com.ifeng.news2 |
通过以上的log分析出,是在执行到Preconditions.checkNotNull()方法的时候抛出了异常的。
下面我们一起来看看这个流程,逆向倒推:
Preconditions.checkNotNull() <=== AppOpsService.checkPackage() <=== AppOpsManager.checkPackage() <=== PackageInstallerService.uninstall() <=== PackageManagerShellCommand.runUninstall() <=== PackageManagerShellCommand.onCommand()
源码分析
通过对源码的分析,定位到了PackageInstallerService.uninstall()方法
PackageInstallerService.uninstall()里面调用AppOpsManager.checkPackage(),需要传递callerPackageName的参数,因为它是null了,导致后面CheckNotNull()方法抛出了java.lang.NullPointerException异常
这个callerPackageName,不是我需要去卸载应用的包名,而是我需要去执行卸载操作的应用的包名。分析上面的pm uninstall命令,也并没有明确指定哪儿需要传入我当前应用的包名。
好吧,我也尝试像静默安装一样添加当前包名的参数,使用Terminal终端来执行pm uninstall命令卸载:
结果!命令无效 …..
再来往前看PackageManagerShellCommand.runUninstall()这个方法
这个方法是运行卸载,取出command命令里面的每一段。其中可以看到option选项只有“-k”、“–user”,“packageName”是我们需要指定卸载掉的包名。再往下看到最底部,有一个uninstall的方法,里面的第二个参数传的是“null”,它正好是我们需要传过去的callerPackageName。
是不是这个地方设置了null,导致了后面为null呢?
我尝试着给这个null赋值一个固定的包名:com.wt.testshortcut
1 | 06-28 12:13:39.306 1393-1717/system_process D/PackageManagerShellCommand: =====>opt: -k |
此时的callerPackageName固定为com.wt.testshortcut,结果倒是没有报null的错误了,但是命令还是无效,并且应用还出现了ANR现象 …..
尝试着给这个null赋值一个固定的包名的做法是不行的
经过同事的讨论分析,只要满足PackageInstallerService.uninstall()中不走进对callingUid的判断,不去AppOpsManager.checkPackage()代码即可。也就是需要callingUid=Process.SHELL_UID(0)或者callingUid=Process.ROOT_UID (2000)
最后的解决方式:代码里在使用pm uninstall [-k] –user 0 PackageName的基础上,并在AndroidMainifest.xml下添加android:shareUerId=”android.uid.shell”,并需要在系统里进行Android源码编译,成功的完成了静默卸载。
后记
记得之前在Android M 上也是静默卸载失败,当时替换为下面的这种方式解决了卸载的问题:
1 | Intent intent = new Intent(); |
参考:Android M静默卸载解决方案的探索
但是上面的这种方式在Android N上也没有任何效果了。
针对上面静默卸载的方式,大家有兴趣再分析看看有没有其他的解决办法。
本文为原创文章,转载请注明出处,如有不足,欢迎指正,感谢您的阅读。