实现真正意义上的静默安装,两种方式:

1
2
3
4
5
pm install -r <test.apk>

# 或 通过反射调用

PackageManager.installPackage(...)

使用pm 命令需要满足三个条件,使用反射机制要满足前两个条件

  • 在Manifest.xml根节点中加入 android:sharedUserId=”android.uid.system”
  • 为Apk加上系统平台数字签名
  • apk要预装在/system/app下面

这些需要在有源码的基础上才可以做到,这些条件可如下实现。

Manifest.xml


1
2
3
4
5
6
7
8
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="site.your.package"
android:sharedUserId="android.uid.system">

...

</manifest>

数字签名


在安卓源码中拿到以下工具

  • 在android源码下 build/target/product/security 找到两个密钥文件 platform.x509.pem platform.pk8
  • out/host/Linux-x86/framework/signapk.jar找到系统封装工具signapk.jar
  • 使用命令给 build 好的apk进行签名
1
java -jar signapk.jar /path/to/platform.x509.pem /path/to/platform.pk8 /path/to/test.apk /path/to/test-sign.apk

test-sign.apk

系统预装App


在如下路径创建目录 Test 存放对应的test-sign.apk (视平台而定)

1
android/device/softwinner/common/prebuild/apk/Test/test-sign.apk

在该目录下创建Android.mk, 编辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Example
LOCAL_PATH :=$(call my-dir)
include $(CLEAR_VARS)
# APK_MODULE_NAME(模块的唯一名字)
LOCAL_MODULE := Test
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform
LOCAL_DEX_PREOPT := false
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE :=PRESIGNED
# apk的文件名
LOCAL_SRC_FILES := test-sign.apk
# 使用@直接引用apk内部的so
LOCAL_PREBUILD_JNI_LIBS :=@lib/$(TARGET_ARCH)/libjni.so
include $(BUILD_PREBUILT)

在方案mk文件(device/vendor-name/device-name/product-name.mk)中的 PRODUCT_PACKAGES项中加入:

1
PRODUCT_PACKAGES += Test (模块的唯一名字)

至此, app被提升为系统预装app, 系统启动后进入 adb shell, 可在 /system/app/ 下面看到Test.apk。

反射调用 installPackage()


需要在项目根目录建立相关的package 和 class:

静默安装

IPackageDeleteObserver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IPackageDeleteObserver {

abstract class Stub extends android.os.Binder implements android.content.pm.IPackageDeleteObserver {
public Stub() {
throw new RuntimeException("Stub!");
}

public static android.content.pm.IPackageDeleteObserver asInterface(android.os.IBinder obj) {
throw new RuntimeException("Stub!");
}

public android.os.IBinder asBinder() {
throw new RuntimeException("Stub!");
}

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
throw new RuntimeException("Stub!");
}
}

void packageDeleted(java.lang.String packageName, int returnCode)
throws android.os.RemoteException;
}

IPackageInstallObserver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IPackageInstallObserver {

abstract class Stub extends android.os.Binder implements android.content.pm.IPackageInstallObserver {
public Stub() {
throw new RuntimeException("Stub!");
}

public static android.content.pm.IPackageInstallObserver asInterface(android.os.IBinder obj) {
throw new RuntimeException("Stub!");
}

public android.os.IBinder asBinder() {
throw new RuntimeException("Stub!");
}

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
throw new RuntimeException("Stub!");
}
}

void packageInstalled(java.lang.String packageName, int returnCode)
throws android.os.RemoteException;
}

PackageDeleteObserver:

1
2
3
4
5
6
7
public class PackageDeleteObserver extends IPackageDeleteObserver.Stub {

@Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {

}
}

PackageInstallObserver:

1
2
3
4
5
6
7
public class PackageInstallObserver extends IPackageInstallObserver.Stub {

@Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {

}
}

ContextPackageManager(类名自定义):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ContextPackageManager {

//如果已经存在的包使用这个flag
private static final int INSTALL_REPLACE_EXISTING = 0x00000002;
// install for all user
private static final int INSTALL_ALL_USERS = 0x00000040;

public static void installPackage(Context context, File apk) {

PackageManager pm = context.getPackageManager();
Class<?>[] types = new Class[]{Uri.class, IPackageInstallObserver.class, int.class, String.class};
try {
Method installPackage = pm.getClass().getMethod("installPackage", types);
installPackage.invoke(pm, Uri.fromFile(apk), new PackageInstallObserver(), INSTALL_ALL_USERS, null);
} catch (Exception e) {
e.printStackTrace();
}
}

public static void updatePackage(Context context, File apk) {

PackageManager pm = context.getPackageManager();
Class<?>[] types = new Class[]{Uri.class, IPackageInstallObserver.class, int.class, String.class};
try {
Method installPackage = pm.getClass().getMethod("installPackage", types);
installPackage.invoke(pm, Uri.fromFile(apk), new PackageInstallObserver(), INSTALL_REPLACE_EXISTING, null);
} catch (Exception e) {
e.printStackTrace();
}
}

public static void deletePackage(Context context) {

PackageManager pm = context.getPackageManager();
Class<?>[] types = new Class[]{String.class, IPackageDeleteObserver.class, int.class};
try {
Method deletePackage = pm.getClass().getMethod("deletePackage", types);
deletePackage.invoke(pm, DeviceConfig.PACKAGE_NAME, new PackageDeleteObserver(), 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用处一行代码即可:

1
ContextPackageManager.updatePackage(context, new File(appPath));

pm insatll 命令安装


调用pm 命令,如下:

1
ShellUtil.executeCommand(new String[]{"pm install -r /sdcard/.../test.apk"});

ShellUtil.executeCommand():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void executeCommand(String[] commands) {
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = null;
StringBuilder errorMsg = null;

DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec("sh");
os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) {
continue;
}

// donnot use os.writeBytes(commmand), avoid chinese charset error
os.write(command.getBytes());
os.writeBytes("exit\n");
os.flush();
}
os.writeBytes("\n");
os.flush();

int result = process.waitFor();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}

if (process != null) {
process.destroy();
}
}
}

pm静默安装参考Trinea