前言
本章我们继续上一章节,讲解 PKMS 相关知识点;
静默安装
静默安装说的就是:在用户无感知的情况下,给用户的手机安装了某个 app,或者是用户触发安装之后,不需要额外的任何操作即可以安装目标 app 到手机上的行为;
手机内置的应用市场 app,其实现了静默安装的能力,三方 app 如果想实现这个能力是不可能的。因为需要系统的签名,这个签名厂商是不会开放的,所以我们无法实现想要的静默安装能力;
签名
那么,这个签名是什么呢?就是在 PKMS 的构造方法中,添加到系统 Settings 的 UID
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.se", SE_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
这里额外插入一个小的知识点,如果做系统应用开发,如何让系统应用在系统启动的时候被装载进来,需要使用 adb push 相关命令;
1. adb remount
2. adb shell
3. chmod777 system/app
4. exit
5. adb push xxx/yy.apk system/app
6. adb reboot
这里 xxx/yy.apk 是 apk 的绝对路径,system/app 是系统目录;这些操作的本质就是:拷贝 + 扫描,将你定制的系统应用拷贝到 system/app 中然后重启系统执行扫描操作;
厂商静默安装实现
如果想实现系统的『静默安装』能力,我们需要准备哪些能力呢?首先我们需要三个系统的 aidl 文件;
android.content.pm.IPackageDeleteObserver.aidl
android.content.pm.IPackageInstallObserver.aidl
android.content.pm.IPackageMoveObserver.aidl
/**
* API for deletion callbacks from the Package Manager.
*
* {@hide}
*/
oneway interface IPackageDeleteObserver {
void packageDeleted(in String packageName, in int returnCode);
}
/**
* API for installation callbacks from the Package Manager.
* @hide
*/
oneway interface IPackageInstallObserver {
void packageInstalled(in String packageName, int returnCode);
}
/**
* Callback for moving package resources from the Package Manager.
* @hide
*/
oneway interface IPackageMoveObserver {
void onCreated(int moveId, in Bundle extras);
void onStatusChanged(int moveId, int status, long estMillis);
}
然后我们直接调用 PKMS 的 installPackage 方法
String fileName = Environment.getExternalStorageDirectory() + File.separator + File.separator + "wms.apk";
Uri uri = Uri.fromFile(new File(fileName));
int installFlags = 0;
PackageManager pm = getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo("com.example.pkmsdemo", PackageManager.GET_UNINSTALLED_PACKAGES);
if (pi != null) {
installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
} catch (PackageManager.NameNotFoundException e) {
}
MyPakcageInstallObserver observer = new MyPakcageInstallObserver();
pm.installPackage(uri, observer, installFlags, "com.example.wmsdemo");
到这里的时候,还是不够,我们需要在 AndroidManifest 中声明 android:sharedUserId=“android.uid.system” 以及对应的权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
由于在上面增加了 android:sharedUserId=“android.uid.system” 就需要厂商的签名,需要厂商提供签名文件,而这个厂商的签名属于厂商的机密,这个【platform.x509.pem platform.pem】文件是拿不到的 java -jar signapk.jar platform.x509.pem platform.pem 需要签名的apk 签名之后的 apk
只有拿到签名文件,才能像 MainActivity 的那种方式安装;
运行时权限
Android 在 6.0 引入了动态权限,需要用户在使用到的时候进行授权;常规的动态权限申请,大家直接看开发文档或者百度一下就可以自行实现;我们接下来基于这个运行时权限使用 aspectj 来封装一个权限申请框架;
权限注解
aspectj 是基于注解的注解的方式,首先我们需要定义几个注解,注解如何定义,可以查看我前面的文章 Java中的注解、反射、手写ButterKnife核心实现 关于注解的详细讲解;
/**
* 权限申请注解
*/
@Target(ElementType.METHOD) // 方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface Permission {
// 具体申请的权限
String[] value();
// 默认的
int requestCode() default -1;
}
我们还需要另外两个注解,一个是权限取消时的回调注解,一个是权限拒绝时的回调注解;
权限取消注解
/**
* 权限取消注解
*/
@Target(ElementType.METHOD) // 方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface PermissionCancel {
int requestCode() default -1;
}
权限拒绝注解
/**
* 权限拒绝注解
*/
@Target(ElementType.METHOD) // 方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface PermissionDenied {
int requestCode() default -1;
}
权限申请结果回调
我们还需要一个接口,来回调相关权限申请结果
public interface IPermission {
void ganted(); // 已经授权
void cancel(); // 取消权限
void denied(); // 拒绝权限
}
使用声明的注解
// 申请权限 函数名可以随意些
@Permission(value = Manifest.permission.READ_EXTERNAL_STORAGE, requestCode = 200)
public void testRequest() {
Toast.makeText(this, "权限申请成功...", Toast.LENGTH_SHORT).show();
}
桥接 Activity
权限的申请,需要 onRequestPermissionsResult 回调中处理请求结果,而这个方法是 Context 的,我们需要通过一个透明的 Activity 桥接一下请求以及回调结果;
public class PermissionActivity extends Activity {
/** 定义权限处理的标记,接收用户传递进来的 */
private final static String PARAM_PERMSSION = "param_permission";
private final static String PARAM_PERMSSION_CODE = "param_permission_code";
public final static int PARAM_PERMSSION_CODE_DEFAULT = -1;
/** 真正接收权限存储的变量 */
private String[] permissions;
private int requestCode;
/** 方便回调的监听,告诉调用者申请结果:已授权,被拒绝,被取消 */
private static IPermission iPermissionListener;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置为 透明 的 Activity
setContentView(R.layout.activity_my_permission);
// 接收传递进来的要申请的权限,READ_EXTERNAL_STORAGE 以这个为例子
permissions = getIntent().getStringArrayExtra(PARAM_PERMSSION);
requestCode = getIntent().getIntExtra(PARAM_PERMSSION_CODE, PARAM_PERMSSION_CODE_DEFAULT);
if (permissions == null && requestCode < 0 && iPermissionListener == null) {
this.finish();
return;
}
// 能够走到这里,就开始去检查,是否已经授权了
boolean permissionRequest = PermissionUtils.hasPermissionRequest(this, permissions);
// 已经授权了,不需要申请,直接回调结果
if (permissionRequest) {
// 通过监听接口,告诉外交,已经授权了
iPermissionListener.ganted();
this.finish();
return;
}
// 能够走到这里,就证明,还需要去申请权限
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
// 申请的结果
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
// 如果申请三个权限 grantResults.len = 3
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 返回的结果,需要去验证一下,是否完全成功
if (PermissionUtils.requestPermissionSuccess(grantResults)) {
iPermissionListener.ganted(); // 已经授权成功了
this.finish();
return;
}
// 没有成功
// 如果用户点击了,拒绝(勾选了"不再提醒") 等操作
if (!PermissionUtils.shouldShowRequestPermissionRationale(this, permissions)) {
// 用户拒绝,不再提醒
iPermissionListener.denied();
this.finish();
return;
}
// 取消
iPermissionListener.cancel();
this.finish();
return;
}
// 让此 Activity 不要有任何动画
@Override
public void finish() {
super.finish();
overridePendingTransition(0, 0);
}
/**
* 此权限申请专用的 Activity,对外暴露 static
*/
public static void requestPermissionAction(Context context, String[] permissions,
int requestCode, IPermission iPermissionListener) {
PermissionActivity.iPermissionListener = iPermissionListener;
// 启动这个空白的Activity 并接收相关要申请的权限,并发起权限申请
Intent intent = new Intent(context, PermissionActivity.class);
// 启动模式
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Bundle bundle = new Bundle();
bundle.putInt(PARAM_PERMSSION_CODE, requestCode);
bundle.putStringArray(PARAM_PERMSSION, permissions);
intent.putExtras(bundle);
context.startActivity(intent);
}
}
注解处理 Aspectj
接下来我们需要通过 Aspectj 来处理相关的注解;
Aspectj 中有两个概念,一个是切点,一个是切面;切点就是我们要切入的地方,切面就是我们从切入的地方,切下来之后的内容;
切点函数
Aspectj 中通过 @Pointcut 注解来标记切点,我们来声明切点函数;
@Aspect
public class PermissionAspect {
// 通过 @Pointcut 来标记切点函数,传递需要处理的注解,也就是我们前面声明的 Permisson 注解;
// @Permission == permission
@Pointcut("execution(@com.example.permission.annotation.Permission * *(..)) && @annotation(permission)")
public void pointActionMethod(Permission permission) {} // 切点函数
}
切面函数
Aspectj中通过 @Around 注解来标记切面函数,我们来声明切面函数
@Aspect
public class PermissionAspect {
// 注解中传入的切点函数名必须和声明的一样才能找到
@Around("pointActionMethod(permission)")
public void aProceedingJoinPoint(final ProceedingJoinPoint point, Permission permission) throws Throwable {
// 先定义一个上下文操作创建
Context context = null;
// thisObject == null 环境有问题
final Object thisObject = point.getThis();
// context初始化
if (thisObject instanceof Context) {
context = (Context) thisObject;
} else if (thisObject instanceof Fragment) {
context = ((Fragment) thisObject).getActivity();
}
// 判断是否为null
if (null == context || permission == null) {
throw new IllegalAccessException("null == context || permission == null is null");
}
final Context finalContext = context;
// 调用空白的 Activity 申请权限;
// 通过 Aspectj 拿到了 @Permission(value = Manifest.permission.READ_EXTERNAL_STORAGE, requestCode = 200) 注解,并获取注解上的所有信息;
PermissionActivity.requestPermissionAction(context, permission.value(), permission.requestCode(), new IPermission() {
// 已经授权
@Override
public void ganted() {
// 申请成功
try {
// 被 Permission 标记的函数,正常执行下去,不拦截,此时就会执行 testRequest 函数
point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void cancel() {
// 被拒绝
PermissionUtils.invokeAnnotion(thisObject, PermissionCancel.class);
}
@Override
public void denied() {
// 被拒绝(不再提醒的)
PermissionUtils.invokeAnnotion(thisObject, PermissionDenied.class);
}
});
}
}
Aspectj 是通过『动态修改字节码』来实现切面处理;本质就是:把切面函数的具体实现剪切到了注解标记的函数中;
public void testRequest() {
// 调用 空白的 Activity 申请权限
MyPermissionActivity.requestPermissionAction(context, permission.value(), permission.requestCode(), new IPermission() {
// 已经授权
@Override
public void ganted() {
// 申请成功
try {
Toast.makeText(this, "权限申请成功...", Toast.LENGTH_SHORT).show();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
// 省略部分代码
}
}
好了,权限管理就到这里吧
下一章预告
WMS 启动流程分析
欢迎三连
来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~