关于apk静默安装、卸载的方式,网上有很多类似的文章,包含内容都基本一致,我之所以还会写这样的Blog,只是想减少你不必要浪费的时间 ~
apk相关
你是不是尝试了N种方法,打了N个debug,然后得到的却是各种各样的安装失败 ~
Look Here : 首先类似静默功能一般是被系统所禁止的,只有厂商在自已平台才会开发权限(好比小米的系统应用,默认实现了静默功能,但是如果小米应用移植到vivo就无效了)!
Look Here : 如果你的应用不是系统应用!
Look Here : 如果使用的设备也没有root过!
那么:就不必要看下面的实现方式了,因为写了也白写,系统压根没有为你开放权限!
基础配置
关于静默安装的方式
- shell 调用 pm 命令
- 反射调用 PackageManager 的 install 方法(9.0后方式变更)
AndroidMainfest权限配置
<!--读写外部存储权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--允许装载和卸载文件系统权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 安装应用的权限(8.0及以上会需要用户手动打开允许安装未知应用的权限,但在provider_paths的配置路径里,可以配置不需要用户手动打开权限,也可跳转自动安装)-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!--应用卸载权限-->
<uses-permission android:name="permission.REQUEST_DELETE_PACKAGES" />
<uses-permission
android:name="android.permission.DELETE_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!--以下为应用监听静默安装成功/失败的广播,看需求实现-->
<receiver
android:name=".InstallResultReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.content.pm.extra.STATUS"/>
</intent-filter>
</receiver>
<receiver
android:name=".UnInstallResultReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.content.pm.extra.STATUS"/>
</intent-filter>
</receiver>
9.0前 静默安装
新手注意,要玩好最好要适配6.0、7.0
采用pm命令,目前支持5.0 - 7.0 (8.0不确定,9.0肯定不行)
- 5.0 的指令:
pm install -r [路径/sdcard/demo.apk]
- 7.0 的指令:
pm install -i [包名:com.demo.insatll] [路径/sdcard/demo.apk]
封装方法 installApk(方法实现相同,选其一即可)
public static boolean installApk(String apkPath) {
//7.0String [ ] args = { "pm" , "install" , "-i" , "com.example", apkPath} ;
//7.0用这个,参考的博客说要加 --user,但是我发现使用了反而不成功。
String[] args = {"pm", "install", "-r", apkPath};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
process = processBuilder.start();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
return process.waitFor() == 0 || successMsg.toString().contains("Success");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
return false;
}
封装方法 startInstall(方法实现相同,选其一即可)
/**
*适配android9.0之前的安装方法
*/
private void startInstall(String path) {
Log.e("install apk",path);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
/**
* 7.0以后版本需要额外添加
* 包名需自行填入,path一般为apk路径
* process = new ProcessBuilder("pm", "install", "-i", "项目包名", "-r",path).start();
* */
process = new ProcessBuilder("pm", "install", "-i", "当前应用包名", "-r", "sdcard/xxx.apk").start();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
} catch (Exception e) {
Log.e("install", "Exception " + e.toString());
} finally {
try {
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (Exception e) {
Log.e("install", "Exception " + e.getMessage());
}
if (process != null) {
process.destroy();
}
}
Log.e("install", "errorMsg " + errorMsg.toString());
Log.d("install", "successMsg " + successMsg.toString());
}
9.0适配 - 正常实现
Android 9.0之后采用PackageManager的getPackageInstaller接口,返回PackageInstaller;
以前有前辈查看源码时发现普通应用发起安装时都会通过Intent,这个时候就会被系统的一个应用拦截,这个就是安装界面系统源码路径的packages/apps/PackageInstaller
类,它提供了安装、更新以及卸载等功能,其中包括单 APK 和多 APK 安装 ~
其中InstallInstalling.java
这个类里面主要实现安装逻辑代码,关于Session
主要是监听应用安装状态(个人感觉非必要实现),还有通过广播监听系统是否安装的方式(看个人需求,考虑是否实现) ~
安装流程:通过 IO 流的方式向 Session 内输送 apk 数据;Andorid9.0中的PackageInsatller 对于安装结果回调没有采用普通的函数回调(InstallSessionCallback),而是采用 Intent 的方式完成回调,比较常见的就是广播了
- 通过广播接收Intent安装状态,主要获取
PackageInstaller.EXTRA_STATUS
的Value值,常见的有STATUS_PENDING_USER_ACTION
,STATUS_SUCCESS
,STATUS_FAILURE
,STATUS_FAILURE_ABORTED
,STATUS_FAILURE_BLOCKED
,STATUS_FAILURE_CONFLICT
,STATUS_FAILURE_INCOMPATIBLE
,STATUS_FAILURE_INVALID
,和STATUS_FAILURE_STORAGE
- 关于安装、卸载的Recriver,其实接收的是同一个
android.content.pm.extra.STATUS
广播,在下文实现中创建了俩个Receiver,内部实现也一致,开发者如嫌麻烦可使用同一广播进行接收
~
InstallSessionCallback 回调相关
初始化 - 注册
InstallSessionCallback mSessionCallback = new InstallSessionCallback();
getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
InstallSessionCallback
private PackageInstaller.SessionCallback mSessionCallback;
private class InstallSessionCallback extends PackageInstaller.SessionCallback {
@Override
public void onCreated(int sessionId) {
Log.e("apk install", "onCreated()");
}
@Override
public void onBadgingChanged(int sessionId) {
Log.e("apk install", "onBadgingChanged()");
}
@Override
public void onActiveChanged(int sessionId, boolean active) {
Log.e("apk install", "onActiveChanged()");
}
@Override
public void onProgressChanged(int sessionId, float progress) {
if (sessionId == mSessionId) {
int progres = (int) (Integer.MAX_VALUE * progress);
}
}
@Override
public void onFinished(int sessionId, boolean success) {
// empty, finish is handled by InstallResultReceiver
if (mSessionId == sessionId) {
if (success) {
Log.e("apk install", "onFinished() 安装成功");
} else {
Log.e("apk install", "onFinished() 安装失败");
}
}
}
}
静默卸载
动态发送广播,监听安装状态,非必要实现,看个人需求
/**
* 根据包名卸载应用
* @param packageName
*/
public void uninstall(String packageName) {
Intent broadcastIntent = new Intent(this, UnInstallResultReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1,
broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
}
UnInstallResultReceiver
public class UnInstallResultReceiver extends BroadcastReceiver {
private static final String TAG = "MainActivity";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e("unInstall", "已收到卸载反馈广播");
//安装广播
if (intent != null) {
final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
Log.d(TAG, "APP UnInstall Success!");
// InstallAPP.getInstance().sendInstallSucces();
} else {
String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
Log.e(TAG, "UnInstall FAILURE status_massage" + msg);
//InstallAPP.getInstance().sendFailure(msg);
}
}
}
}
静默安装
/**
* 适配android9的安装方法。
* 全部替换安装
* @return
*/
private int mSessionId = -1;
public Boolean installApk(String apkFilePath) {
File apkFile = new File(apkFilePath);
if (!apkFile.exists()) {
return null;
}
PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo != null) {
String packageName = packageInfo.packageName;
int versionCode = packageInfo.versionCode;
String versionName = packageInfo.versionName;
Log.e("ApkActivity", "packageName=" + packageName + ", versionCode=" + versionCode + ", versionName=" + versionName);
}
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams sessionParams
= new PackageInstaller.SessionParams(PackageInstaller
.SessionParams.MODE_FULL_INSTALL);
sessionParams.setSize(apkFile.length());
try {
mSessionId = packageInstaller.createSession(sessionParams);
} catch (IOException e) {
e.printStackTrace();
}
if (mSessionId != -1) {
boolean copySuccess = onTransfersApkFile(apkFilePath);
if (copySuccess) {
execInstallAPP();
}
}
return null;
}
/**
* 通过文件流传输apk
* @param apkFilePath
* @return
*/
private boolean onTransfersApkFile(String apkFilePath) {
InputStream in = null;
OutputStream out = null;
PackageInstaller.Session session = null;
boolean success = false;
try {
File apkFile = new File(apkFilePath);
session = context.getPackageManager().getPackageInstaller().openSession(mSessionId);
Log.e("ApkActivity","apkFile:"+apkFilePath);
Log.e("ApkActivity","apkFile:"+apkFile.getName());
//看别人传的是base.apk;个人建议如果你应用被授权了,base.apk无效后,建议更换为传入的apk路径
out = session.openWrite("base.apk", 0, apkFile.length());
in = new FileInputStream(apkFile);
int total = 0, c;
byte[] buffer = new byte[1024 * 1024];
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
session.fsync(out);
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != session) {
session.close();
}
try {
if (null != out) {
out.close();
}
if (null != in) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return success;
}
/**
* 执行安装并通知安装结果
*/
private void execInstallAPP() {
PackageInstaller.Session session = null;
try {
Log.e("ApkActivity","mSessionId:"+mSessionId);
session = context.getPackageManager().getPackageInstaller().openSession(mSessionId);
Intent intent = new Intent(context, InstallResultReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,1, intent,PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != session) {
session.close();
}
}
}
监听安装状态,非必要实现,看个人需求
public class InstallResultReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d("install", "已收到安装反馈广播 action:" + action);
if (intent != null) { //安装的广播
final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
// success
Log.d(TAG, "APP Install Success!");
// InstallAPP.getInstance().sendInstallSucces();
} else {
String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
Log.e(TAG, "Install FAILURE status_massage" + msg);
//InstallAPP.getInstance().sendFailure(msg);
}
}
}
}
9.0适配 - 工具封装
AndroidP
/**
* Copyright (C), 2018-2019
* Author: ziqimo
* Date: 2019-12-31 14:55
* Description:
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
public class AndroidP {
private static final String TAG = AndroidP.class.getSimpleName();
// 适配android9的安装方法。
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void install28(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);
if (copySuccess) {
execInstallCommand(context, packageInstaller, sessionId);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private 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 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 {
closeQuietly(out);
closeQuietly(in);
closeQuietly(session);
}
return success;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void execInstallCommand(Context context, PackageInstaller packageInstaller, int sessionId) {
PackageInstaller.Session session = null;
try {
session = packageInstaller.openSession(sessionId);
Intent intent = new Intent(context, InstallResultReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(session);
}
}
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
public class InstallResultReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
// success
} else {
//Log.e(TAG, intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
}
}
}
}
IoUtils
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
public final class IoUtils {
public static void closeQuietly(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
}
public static void closeQuietly(Socket c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
}
}
兴趣爱好 - 别人研究9.0的framework真实源码整理出来的
public class PackageManagerCompatP {
private final static String TAG = PackageManagerCompatP.class.getSimpleName();
public static final long MAX_WAIT_TIME = 25 * 1000;
public static final long WAIT_TIME_INCR = 5 * 1000;
private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
private Context mContext;
public PackageManagerCompatQ(Context context) {
this.mContext = context;
}
private static class LocalIntentReceiver {
private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
try {
mResult.offer(intent, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
public IntentSender getIntentSender() {
Class<?> aClass = null;
try {
aClass = Class.forName("android.content.IntentSender");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (aClass == null) {
return null;
}
try {
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
Log.i(TAG, "declaredConstructor.toString():" + declaredConstructor.toString());
Log.i(TAG, "declaredConstructor.getName():" + declaredConstructor.getName());
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
Class aClass1 = parameterType.getClass();
Log.i(TAG, "parameterTypes...aClass1:" + aClass1.getName());
}
}
} catch (Exception e) {
e.printStackTrace();
}
Constructor constructor = null;
try {
constructor = aClass.getDeclaredConstructor(IIntentSender.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
if (constructor == null) {
return null;
}
Object o = null;
try {
o = constructor.newInstance((IIntentSender) mLocalSender);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return (IntentSender) o;
// new IntentSender((IIntentSender) mLocalSender)
}
public Intent getResult() {
try {
return mResult.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private PackageManager getPm() {
return mContext.getPackageManager();
}
private PackageInstaller getPi() {
return getPm().getPackageInstaller();
}
private void writeSplitToInstallSession(PackageInstaller.Session session, String inPath,
String splitName) throws RemoteException {
long sizeBytes = 0;
final File file = new File(inPath);
if (file.isFile()) {
sizeBytes = file.length();
} else {
return;
}
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(inPath);
out = session.openWrite(splitName, 0, sizeBytes);
int total = 0;
byte[] buffer = new byte[65536];
int c;
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
session.fsync(out);
} catch (IOException e) {
e.printStackTrace();
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(session);
}
}
/**
* 入口方法
* String apkPackageName = ""; //填写安装的包名
* String apkPath = "";//填写安装的路径
**/
public void testReplaceFlagSdcardInternal(String apkPackageName, String apkPath) throws Exception {
// Do not run on devices with emulated external storage.
if (Environment.isExternalStorageEmulated()) {
return;
}
int iFlags = 0x00000008;// PackageManager.INSTALL_EXTERNAL 0x00000008
int rFlags = 0;
//这个暂时用不上
//InstallParams ip = sampleInstallFromRawResource(iFlags, false);
Uri uri = Uri.fromFile(new File(apkPath));
GenericReceiver receiver = new ReplaceReceiver(apkPackageName);
int replaceFlags = rFlags | 0x00000002;//PackageManager.INSTALL_REPLACE_EXISTING 0x00000002
try {
invokeInstallPackage(uri, replaceFlags, receiver, true);
//assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
} catch (Exception e) {
Log.e(TAG, "Failed with exception : " + e);
} finally {
// cleanUpInstall(ip);
}
}
// class InstallParams {
// Uri packageURI;
//
// PackageParser.Package pkg;
//
// InstallParams(String outFileName, int rawResId) throws PackageParserException {
// this.pkg = getParsedPackage(outFileName, rawResId);
// this.packageURI = Uri.fromFile(new File(pkg.codePath));
// }
//
// InstallParams(PackageParser.Package pkg) {
// this.packageURI = Uri.fromFile(new File(pkg.codePath));
// this.pkg = pkg;
// }
//
// long getApkSize() {
// File file = new File(pkg.codePath);
// return file.length();
// }
// }
//
// private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp)
// throws Exception {
// return installFromRawResource("install.apk", android.R.raw.install, flags, cleanUp, false, -1,
// PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
// }
// private void cleanUpInstall(InstallParams ip) {
//
// }
private void cleanUpInstall(String pkgName) throws Exception {
if (pkgName == null) {
return;
}
Log.i(TAG, "Deleting package : " + pkgName);
try {
final ApplicationInfo info = getPm().getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if (info != null) {
//PackageManager.DELETE_ALL_USERS
final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
//这是卸载,不调用
// getPi().uninstall(pkgName,
// 0x00000002,
// localReceiver.getIntentSender());
localReceiver.getResult();
assertUninstalled(info);
}
} catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
private static void assertUninstalled(ApplicationInfo info) throws Exception {
File nativeLibraryFile = new File(info.nativeLibraryDir);
Log.e(TAG, "Native library directory " + info.nativeLibraryDir
+ " should be erased" + nativeLibraryFile.exists());
}
private void invokeInstallPackage(Uri packageUri, int flags, GenericReceiver receiver,
boolean shouldSucceed) {
mContext.registerReceiver(receiver, receiver.filter);
synchronized (receiver) {
final String inPath = packageUri.getPath();
PackageInstaller.Session session = null;
try {
final PackageInstaller.SessionParams sessionParams =
new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
try {
//sessionParams.installFlags = flags;
Field installFlags = sessionParams.getClass().getDeclaredField("installFlags");
installFlags.set(sessionParams, flags);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
final int sessionId = getPi().createSession(sessionParams);
session = getPi().openSession(sessionId);
writeSplitToInstallSession(session, inPath, "base.apk");
final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
session.commit(localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (shouldSucceed) {
if (status != PackageInstaller.STATUS_SUCCESS) {
Log.e(TAG, "Installation should have succeeded, but got code " + status);
}
} else {
if (status == PackageInstaller.STATUS_SUCCESS) {
Log.e(TAG, "Installation should have failed");
}
// We'll never get a broadcast since the package failed to install
return;
}
// Verify we received the broadcast
long waitTime = 0;
while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
try {
receiver.wait(WAIT_TIME_INCR);
waitTime += WAIT_TIME_INCR;
} catch (InterruptedException e) {
Log.i(TAG, "Interrupted during sleep", e);
}
}
if (!receiver.isDone()) {
Log.e(TAG, "Timed out waiting for PACKAGE_ADDED notification");
}
} catch (IllegalArgumentException | IOException | RemoteException e) {
Log.e(TAG, "Failed to install package; path=" + inPath, e);
} finally {
IoUtils.closeQuietly(session);
mContext.unregisterReceiver(receiver);
}
}
}
private abstract static class GenericReceiver extends BroadcastReceiver {
private boolean doneFlag = false;
boolean received = false;
Intent intent;
IntentFilter filter;
abstract boolean notifyNow(Intent intent);
@Override
public void onReceive(Context context, Intent intent) {
if (notifyNow(intent)) {
synchronized (this) {
received = true;
doneFlag = true;
this.intent = intent;
notifyAll();
}
}
}
public boolean isDone() {
return doneFlag;
}
public void setFilter(IntentFilter filter) {
this.filter = filter;
}
}
class ReplaceReceiver extends GenericReceiver {
String pkgName;
final static int INVALID = -1;
final static int REMOVED = 1;
final static int ADDED = 2;
final static int REPLACED = 3;
int removed = INVALID;
// for updated system apps only
boolean update = false;
ReplaceReceiver(String pkgName) {
this.pkgName = pkgName;
filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
if (update) {
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
}
filter.addDataScheme("package");
super.setFilter(filter);
}
public boolean notifyNow(Intent intent) {
String action = intent.getAction();
Uri data = intent.getData();
String installedPkg = data.getEncodedSchemeSpecificPart();
if (pkgName == null || !pkgName.equals(installedPkg)) {
return false;
}
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
removed = REMOVED;
} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
if (removed != REMOVED) {
return false;
}
boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (!replacing) {
return false;
}
removed = ADDED;
if (!update) {
return true;
}
} else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
if (removed != ADDED) {
return false;
}
removed = REPLACED;
return true;
}
return false;
}
}
}
延伸 - 伪静默安装
为何说是伪? 因为这个其实并不是静默安装,而是郭霖大神早期的一款智能安装方法,这里主要记录下大致实现~
res/xml目录下新建accessibility_service_config.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:packageNames="com.android.packageinstaller"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
/>
string.xml文件写入description指定内容
<resources>
<string name="app_name">InstallTest</string>
<string name="accessibility_service_description">智能安装服务,无需用户的任何操作就可以自动安装程序。</string>
</resources>
修改AndroidManifest.xml文件,在里面配置无障碍服务
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.installtest">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
......
<service
android:name=".MyAccessibilityService"
android:label="我的智能安装"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
</application>
</manifest>
实现智能安装功能的具体逻辑了,创建一个MyAccessibilityService类并继承自AccessibilityService
/**
* 智能安装功能的实现类。
* 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
* @author guolin
* @since 2015/12/7
*/
public class MyAccessibilityService extends AccessibilityService {
Map<Integer, Boolean> handledMap = new HashMap<>();
public MyAccessibilityService() {
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo != null) {
int eventType = event.getEventType();
if (eventType== AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED ||
eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
if (handledMap.get(event.getWindowId()) == null) {
boolean handled = iterateNodesAndHandle(nodeInfo);
if (handled) {
handledMap.put(event.getWindowId(), true);
}
}
}
}
}
private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo != null) {
int childCount = nodeInfo.getChildCount();
if ("android.widget.Button".equals(nodeInfo.getClassName())) {
String nodeContent = nodeInfo.getText().toString();
Log.d("TAG", "content is " + nodeContent);
if ("安装".equals(nodeContent)
|| "完成".equals(nodeContent)
|| "确定".equals(nodeContent)) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
}
} else if ("android.widget.ScrollView".equals(nodeInfo.getClassName())) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i);
if (iterateNodesAndHandle(childNodeInfo)) {
return true;
}
}
}
return false;
}
@Override
public void onInterrupt() {
}
}
MainActivity智能安装功能的调用
/**
* 仿360手机助手秒装和智能安装功能的主Activity。
* 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
* @author guolin
* @since 2015/12/7
*/
public class MainActivity extends AppCompatActivity {
......
public void onForwardToAccessibility(View view) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
public void onSmartInstall(View view) {
if (TextUtils.isEmpty(apkPath)) {
Toast.makeText(this, "请选择安装包!", Toast.LENGTH_SHORT).show();
return;
}
Uri uri = Uri.fromFile(new File(apkPath));
Intent localIntent = new Intent(Intent.ACTION_VIEW);
localIntent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(localIntent);
}
}
延伸 - 自启动
此方法未亲自尝试,仅当做一个参考方向
在真实场景中,我们一般都会接到静默安装app后,要求app自启动的需求,要么在自启动时因app已经退出安装,所以无法实现我们的诉求;有人就想着把framework层修改了,找到发广播的地方PackageInstallerService.java ~
自启动Blog
static class PackageInstallObserverAdapter extends PackageInstallObserver {
...
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) {
boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
Notification notification = buildSuccessNotification(mContext,
mContext.getResources()
.getString(update ? R.string.package_updated_device_owner :
R.string.package_installed_device_owner),
basePackageName,
mUserId);
if (notification != null) {
NotificationManager notificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(basePackageName,
SystemMessage.NOTE_PACKAGE_STATE,
notification);
}
}
//add by jueme for start customer app at 20200927
if(basePackageName.equals("com.jueme.android.autoinstall")){
new Thread(new Runnable(){
@Override
public void run(){
try {
Slog.d(TAG,"onPackageInstalled basePackageName "+basePackageName+" returnCode "+returnCode+" msg "+msg);
//睡500ms是因为在测试过程中发现会概率性启动不了app,原因就是在还没有完全安装好就调用了启动的方法,加了延时之后就没发现问题了
Thread.sleep(500);
Intent intent = new Intent();
intent.setClassName("com.jueme.android.autoinstall","com.jueme.android.autoinstall.MainActivity");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} catch (Exception e) {
Slog.e(TAG,"onPackageInstalled Exception "+e.toString());
}
}
}).start();
}
//add end
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName);
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.installStatusToPublicStatus(returnCode));
fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
PackageManager.installStatusToString(returnCode, msg));
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
if (extras != null) {
final String existing = extras.getString(
PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
if (!TextUtils.isEmpty(existing)) {
fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
}
}
try {
mTarget.sendIntent(mContext, 0, fillIn, null, null);
} catch (SendIntentException ignored) {
}
}
...
}
扩展 - 小方法
获取系统签名的方法
- Baby go here系统签名,静默安装(未尝试)
- 静默安装- 关于静默安装 - 有意思的总结