前提条件
在android 系统下静默安装apk需要时系统级应用 或 加了白名单具备系统权限App;
解决方案
一: 在低版本SDK中应该是SDK4.0(API15)及以下,具体没考证,可以通过PackageManager类的installPackage方法来静默安装;
//context 上下文;packageName包名; file 文件
private void installSilent(Context context, String packageName, File file) {
PackageManager pm = context.getPackageManager();
pm.installPackage(Uri.fromFile(file), new IPackageInstallObserver() {
@Override
public void packageInstalled(String basePackageName, int returnCode) throws RemoteException {
if(basePackageName==null||returnCode!=1){
//静默安装失败,采用通过Intent跳转手动安装
}else{
//静默安装成功
}
}
@Override
public IBinder asBinder() {
return null;
}
}, PackageManager.INSTALL_REPLACE_EXISTING, packageName);
}
二:当在SDK版本为O 即8.0(API27)及以下,SDK4.0(API15)以上,PackageManager包下就找不到installPackage()方法。
分析原因为在SDK8.0等版本下,google考虑安全等原因,移除或私有了PackageManager中一些函数、删除了 IPackageInstallObserver.class,IPackageInstallObserver$Stub.class;PackageManager中移除了个别方法
- import android.content.pm.IPackageInstallObserver;
- import android.content.pm.IPackageInstallObserver$Stub;
- import android.content.pm.PackageManager;
解决方案:
1、在你的SDK路径下找到 \platforms\android-API版本号\android.jar,修改后缀为.zip;然后解压android.zip; 注意:解压一个低版本PackageManager等未做删减的,再解压一个你项目所使用的SDK版本的android.jar;
2、在android.zip文件夹中进入 android\android\content\pm ;然后将低版本PackageManager等未做删减的 PackageManager.class、IPackageInstallObserver.class 、IPackageInstallObserver$Stub.class;,拷贝一份然后粘贴到你项目所使用的SDK版本的android.jar中,然后再将android文件压缩成android.zip,然后再修改后缀为.jar;
3、再将修改后的android.jar拷贝到SDK路径 \platforms\android-API版本号\ 下,覆盖掉被google删减的android.jar; 注意覆盖前最好先备份一下原始android.jar;
4、重启android studio;类和方法就能导报出来了;
如下图:
三:当在SDK版本为P 即9.0(API28)及以上, 上面的解决方法不再适用。这时需要使用 PackageInstaller类 创建Session,并广播通知系统来静默安装;
直接贴代码如下:注意 该操作是耗时的,切勿在主线程中操作,否则会ANR;一定通过子线程来操作;
public class InstallManager {
private static final String TAG = "InstallManager";
/******************************************************* android P 静默安装适配 *****************************************************************/
private final static String ACTION = "wait.install.result";
private final static SynchronousQueue<Intent> mInstallResults = new SynchronousQueue<>();
/**
* @param context
* @param filePath
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static synchronized boolean installForP(Context context, final String filePath) {
boolean success = false;
File apkFile = new File(filePath);
PackageInstaller packageInstaller = context.getPackageManager().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, filePath);
if (copySuccess) {
boolean installSuccess = execInstallCommand(context, packageInstaller, sessionId, filePath);
if (installSuccess) {
success = true;
}
}
}
return success;
}
@TargetApi(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;
}
@TargetApi(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);
Log.i(TAG, "streamed " + total + " bytes");
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
session.close();
}
return success;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean execInstallCommand(Context context, PackageInstaller packageInstaller, int sessionId, String filePath) {
PackageInstaller.Session session = null;
boolean success = false;
try {
session = packageInstaller.openSession(sessionId);
Intent intent = new Intent();
intent.setAction(ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
final Intent result = mInstallResults.poll(60, TimeUnit.SECONDS);
if (result != null) {
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
success = true;
} else {
Log.e(TAG, result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
}
} else {
success = false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
return success;
}
}
注:公司是做手机及OS的, 以上方案均在我负责的项目中验证过(负一屏、浏览器、应用市场),是OK的;