0%

Android - 静默安装apk的2种方式

背景

安装apk的方式有以下两种

  • adb install 通过adb安装
  • Installer activity 通过系统的包管理Activity安装

实际调用上述两种方式并不能成功,即使手机root也不行,所以需要将app设置为系统app

Manifest配置

1
android:sharedUserId="android.uid.system"
  • 下载 签名文件“platform.pk8”和“platform.x509.pem”

位置:aosp/build/target/product/security/

如果厂商没有修改google原生的签名文件,直接在aosp中下载即可,否则需要联系厂商。

  • 生成jks
1
keytool -importkeystore -deststorepass android -destkeypass android -destkeystore platform.jks -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias androiddebugkey

将app使用platform.jks签名即可

代码

以下代码根据系统源码整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void install(String path) {	
PackageInstaller.Session session = context.getPackageManager().getPackageInstaller().openSession(1);
File file = new File(path);
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file,ParcelFileDescriptor.MODE_READ_ONLY);
session.write("base.apk",0,file.length(),parcelFileDescriptor);
Intent intent = new Intent(context,Receiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,11,intent,PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
}


public static class Receiver extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
if (status == PackageInstaller.STATUS_SUCCESS) {
// success
} else {
//fail
}
}
}

adb install源码分析

源码位置:system/core/adb/client

main.cpp

1
2
3
4
5
6
int main(int argc, char* argv[], char* envp[]) {
__adb_argv = const_cast<const char**>(argv);
__adb_envp = const_cast<const char**>(envp);
adb_trace_init(argv);
return adb_commandline(argc - 1, const_cast<const char**>(argv + 1));
}

commandline.cpp

1
2
3
4
5
6
int adb_commandline(int argc, const char** argv) {
if (!strcmp(argv[0], "install")) {
if (argc < 2) error_exit("install requires an argument");
return install_app(argc, argv);
}
}

adb_install.cpp

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
int install_app(int argc, const char** argv) {

switch (installMode) {
case INSTALL_PUSH:
return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
use_fastdeploy, use_localagent);
case INSTALL_STREAM:
return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
use_fastdeploy, use_localagent);
case INSTALL_DEFAULT:
default:
return 1;
}
}


static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy,
bool use_localagent) {
printf("Performing Push Install\n");

// Find last APK argument.
// All other arguments passed through verbatim.
int last_apk = -1;
for (int i = argc - 1; i >= 0; i--) {
if (android::base::EndsWithIgnoreCase(argv[i], ".apex")) {
error_exit("APEX packages are only compatible with Streamed Install");
}
if (android::base::EndsWithIgnoreCase(argv[i], ".apk")) {
last_apk = i;
break;
}
}

if (last_apk == -1) error_exit("need APK file on command line");

int result = -1;
std::vector<const char*> apk_file = {argv[last_apk]};
std::string apk_dest =
"/data/local/tmp/" + android::base::Basename(argv[last_apk]);

if (use_fastdeploy == true) {
#if defined(ENABLE_FASTDEPLOY)
TemporaryFile metadataTmpFile;
TemporaryFile patchTmpFile;

FILE* metadataFile = fopen(metadataTmpFile.path, "wb");
extract_metadata(apk_file[0], metadataFile);
fclose(metadataFile);

create_patch(apk_file[0], metadataTmpFile.path, patchTmpFile.path);
apply_patch_on_device(apk_file[0], patchTmpFile.path, apk_dest.c_str());
#else
error_exit("fastdeploy is disabled");
#endif
} else {
if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk;
}

argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */
result = pm_command(argc, argv);

cleanup_apk:
if (use_fastdeploy == true) {
#if defined(ENABLE_FASTDEPLOY)
delete_device_patch_file(apk_file[0]);
#endif
}
delete_device_file(apk_dest);
return result;
}

static int pm_command(int argc, const char** argv) {
std::string cmd = "pm";

while (argc-- > 0) {
cmd += " " + escape_arg(*argv++);
}

return send_shell_command(cmd);
}

pm

1
2
#!/system/bin/sh
cmd package "$@"

这里package调用了PackageManagerService

PackageManagerService.java

1
2
3
4
5
6
7
8
9
10
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new PackageManagerShellCommand(this)).exec(
this, in, out, err, args, callback, resultReceiver);
}
}

PackageManagerShellCommand.java

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
class PackageManagerShellCommand extends ShellCommand {
@Override
public int onCommand(String cmd) {
switch(cmd) {
case "install":
return runInstall();
}
}
private int runInstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final InstallParams params = makeInstallParams();
final String inPath = getNextArg();

setParamsSize(params, inPath);
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
if (inPath == null && params.sessionParams.sizeBytes == -1) {
pw.println("Error: must either specify a package size or an APK file");
return 1;
}
final boolean isApex =
(params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
String splitName = "base." + (isApex ? "apex" : "apk");
if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
if (doCommitSession(sessionId, false /*logSuccess*/)
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
abandonSession = false;
pw.println("Success"); //最后打印Success给终端
return 0;
} finally {
if (abandonSession) {
try {
doAbandonSession(sessionId, false /*logSuccess*/);
} catch (Exception ignore) {
}
}
}
}
}

所以使用Runtime 执行时,最后输出流中有Success表示安装成功

eg:

1
2
3
4
➜ adb push amap_bundle_portal-debug.apk /data/local/tmp/
amap_bundle_portal-debug.apk: 1 file pushed, 0 skipped. 40.7 MB/s (127578303 bytes in 2.990s)
➜ adb shell pm install /data/local/tmp/amap_bundle_portal-debug.apk
Success

Installer activity安装

源码位置:/aosp/frameworks/base/packages/PackageInstaller

PackageInstallerActivity.java

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class PackageInstallerActivity {
private void bindUi() {
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
// 开始安装
startInstall();
}
}
}, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}, null);
setupAlert();

mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mOk.setEnabled(false);
}
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
//安装中
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
}