前言
20220617更新,补上未root升级方式,需要android.uid.system+系统签名,Android 10.0,见文末。
目前已知的实现方式:
- 获取su,通过pm命令实现静默安装
- 通过辅助功能实现静默安装(有弹窗,只是不需要手动点击安装)
- root,覆盖安装,将应用下载到system下后重启系统
- 通过pm install实现静默安装,需要系统签名
需要知道
1.不是root过的手机就一定能通过Runtime.getRuntime().exec(“su”);代码获取root权限。虽然通过su或adb root可以切换到root,但是在代码中不同,在源码中有如下判断。
if (myuid != AID_ROOT && myuid != AID_SHELL) {
fprintf(stderr,"su: uid %d not allowed to su\n", myuid);
return 1;
}
2.不是非要获取su才能执行pm install来实现静默升级。这也是我没有通过1来实现静态升级的原因。下面介绍的我就是通过这种方式实现的静默升级。
3.应用放到system/app,需要root
4.如果应用声明为系统应用,那么需要系统签名,否则签名无法通过,开机时会报签名错误,无法识别安装
5.即时不声明为系统应用,不需要系统签名,依然可以放到system/app下
实现静默升级,Android7.1
前提:
- 系统签名,即使su你无法拿到也不影响。
应用必须使用系统签名,因为咱要用系统权限
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
android:sharedUserId="android.uid.system"
如何系统签名,不在本文范围内。
代码如下:
/**
* 静默安装
*
* @param path
*/
private void installPackage(String path) {
String packageName = MyApplication.get().getApplicationContext().getPackageName();
try {
new ProcessBuilder()
.command("pm", "install", "-i", packageName, path)
.start();
} catch (IOException e) {
e.printStackTrace();
}
}
path替换为你自己Apk的路径,包名也记得替换,这个坑我开始折磨了一段时间,我开始用的-r,没有包名,导致一直空指针异常。。。。。
pm install -i 包名 apk路径
Android10.0 PackageInstaller静默升级,
机器未root,不过有系统签名,还是和上面一样,因为要使用系统INSTALL_PACKAGES权限,测试10.0升级成功。下面是代码
public class PackageManagerCompatP {
private static final String TAG = PackageManagerCompatP.class.getSimpleName();
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void install(Context context, String apkFilePath, PackageManager packageManager) {
File apkFile = new File(apkFilePath);
PackageInstaller packageInstaller = packageManager.getPackageInstaller();
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setSize(apkFile.length());
int sessionId = createSession(packageInstaller, sessionParams);
if (sessionId != -1) {
boolean copySuccess = copyInstallFile(packageInstaller, sessionId, apkFilePath);
MainLog.d(TAG,"install: " + copySuccess);
if (copySuccess) {
execInstallCommand(context, packageInstaller, sessionId);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static int createSession(PackageInstaller packageInstaller,
PackageInstaller.SessionParams sessionParams) {
int sessionId = -1;
try {
sessionId = packageInstaller.createSession(sessionParams);
} catch (IOException e) {
e.printStackTrace();
}
return sessionId;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static boolean copyInstallFile(PackageInstaller packageInstaller,
int sessionId, String apkFilePath) {
InputStream in = null;
OutputStream out = null;
PackageInstaller.Session session = null;
boolean success = false;
try {
File apkFile = new File(apkFilePath);
session = packageInstaller.openSession(sessionId);
out = session.openWrite("base.apk", 0, apkFile.length());
in = new FileInputStream(apkFile);
int total = 0, c;
byte[] buffer = new byte[65536];
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
session.fsync(out);
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(out);
closeQuietly(in);
closeQuietly(session);
}
return success;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static void execInstallCommand(Context context, PackageInstaller packageInstaller, int sessionId) {
PackageInstaller.Session session = null;
try {
session = packageInstaller.openSession(sessionId);
Intent intent = new Intent(context, InstallReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
MainLog.d(TAG,"execInstallCommand: ");
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(session);
}
}
private static void closeQuietly(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
}
}
这是广播,可以监听应用已安装
<receiver android:name=".receiver.InstallReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
String appPackageName = context.getPackageName();
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
String packageName = intent.getData().getEncodedSchemeSpecificPart();
if (packageName.equals(appPackageName)) {
// 重新启动APP
Intent intentToStart = context.getPackageManager().getLaunchIntentForPackage(packageName);
context.startActivity(intentToStart);
}
}
参考