花了几天时间终于把android安装卸载这块的东西,了解了个一二三。
普通安装卸载
普通安装卸载这块就不多说了,直接使用下面的方法就可以实现安装和下载了(不需要声明任何权限),不过普通的方式会弹出对话框,让用户来选择是否安装和卸载等等操作。
/**
* 普通安装
* @param context
* @param filePath 文件绝对路径
*/
public static void installByNormal(Context context, String filePath) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(Uri.parse("file://"+filePath), "application/vnd.android.package-archive");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
/**
* 普通卸载
* @param context
* @param packageName APK包名
*/
public static void uninstallByNormal(Context context, String packageName) {
Uri uri = Uri.parse("package:"+packageName);
Intent i = new Intent(Intent.ACTION_DELETE,uri);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
静默安装卸载
使用静默安装卸载的方式,首先要确保的几点是
- 在AndroidManifest.xml中声明
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
- 将实现静默安装卸载的APK放在系统目录下,即通过adb push的方式将APK放在/system/app目录
做了上面两步,就可以使用下面指令的方式来实现APK的静默安装和卸载啦,当然,第二步,肯定是我们的用下面的方式实现了之后,再去做的,这里我只是提前告诉大家,必须要做的事情。
指令方式
- ProcessBuilder方式
/**
* 静默安装 通过ProcessBuilder的方式
* @param context
* @param filePath
* @return 0 means normal, 1 means file not exist, 2 means other exception error
*/
public static int installSlientByProcessBuilder(String filePath) {
File file = new File(filePath);
if (filePath == null || filePath.length() == 0 || (file = new File(filePath)) == null || file.length() <= 0
|| !file.exists() || !file.isFile()) {
return 1;
}
String[] args = { "pm", "install", "-r", filePath };
ProcessBuilder processBuilder = new ProcessBuilder(args);
// 遍历ProcessBuilder的环境变量
// Map<String, String> environment = processBuilder.environment();
// Iterator<String> iterator = environment.keySet().iterator();
// while (iterator.hasNext()) {
// String next = iterator.next();
// Log.d("installSlient", "environment:"+next+",value:"+environment.get(next));
// }
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
int result;
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);
}
} catch (IOException e) {
e.printStackTrace();
result = 2;
} catch (Exception e) {
e.printStackTrace();
result = 2;
} finally {
try {
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
} else {
result = 2;
}
Log.d("installSlient", "result:"+result+"successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
/**
* 静默卸载 通过ProcessBuilder的方式
* @param context
* @param packageName APK包名
* @return 0 means normal, 1 means other exception error
*/
public static int uninstallSlientByProcessBuilder(String packageName) {
int result = 0;
String[] args = new String[]{"pm", "uninstall", packageName};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successReader = null;
BufferedReader errorReader = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
process = processBuilder.start();
successReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = null;
while( (line = successReader.readLine()) != null ) {
successMsg.append(line);
}
while( (line = errorReader.readLine()) != null ) {
errorMsg.append(line);
}
} catch (IOException e) {
e.printStackTrace();
result = 1;
} finally {
try {
if (successReader != null){
successReader.close();
}
if (errorReader != null){
errorReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null){
process.destroy();
}
}
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
} else {
result = 1;
}
Log.d("installSlient", "result:"+result+"successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
- Runtime.getRuntime().exec方式
/**
* 静默安装 通过指令的方式
* @param apkPath 要安装的apk路径
* @param isRoot 系统是否root,如果root就设置为true,没有root就设置为false
* @return 0 means normal, 1 means file not exist, 2 means other exception error
*/
public static int installSlientByCommand(String apkPath, boolean isRoot){
int result = 0;
String command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib pm install -r " + apkPath + " \n";
Process process = null;
DataOutputStream dataOutputStream = null;
BufferedReader successReader = null;
BufferedReader errorReader = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
process = Runtime.getRuntime().exec(isRoot ? "su":"sh");
dataOutputStream = new DataOutputStream(process.getOutputStream());
dataOutputStream.writeBytes(command);
dataOutputStream.flush();
dataOutputStream.writeBytes("exit\n");
dataOutputStream.flush();
result = process.waitFor();
successReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = null;
while( (line = successReader.readLine()) != null ) {
successMsg.append(line);
}
while( (line = errorReader.readLine()) != null ) {
errorMsg.append(line);
}
} catch (IOException e) {
e.printStackTrace();
result = 2;
} catch (InterruptedException e) {
e.printStackTrace();
result = 2;
} finally {
try {
if (dataOutputStream != null){
dataOutputStream.close();
}
if (successReader != null){
successReader.close();
}
if (errorReader != null){
errorReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null){
process.destroy();
}
}
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
}
Log.d("installSlient", "result:"+result+"successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
/**
* 静默卸载 通过指令的方式
* @param apkPath 要安装的apk路径
* @param isRoot 系统是否root,如果root就设置为true,没有root就设置为false
* @return 0 means normal, 1 means other exception error
*/
public static int uninstallSlientByCommand(String apkPath, boolean isRoot){
int result = 0;
String command = "pm uninstall " + apkPath + " \n";
Process process = null;
DataOutputStream dataOutputStream = null;
BufferedReader successReader = null;
BufferedReader errorReader = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
try {
process = Runtime.getRuntime().exec(isRoot ? "su":"sh");
dataOutputStream = new DataOutputStream(process.getOutputStream());
dataOutputStream.writeBytes(command);
dataOutputStream.flush();
dataOutputStream.writeBytes("exit\n");
dataOutputStream.flush();
result = process.waitFor();
successReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = null;
while( (line = successReader.readLine()) != null ) {
successMsg.append(line);
}
while( (line = errorReader.readLine()) != null ) {
errorMsg.append(line);
}
} catch (IOException e) {
e.printStackTrace();
result = 1;
} catch (InterruptedException e) {
e.printStackTrace();
result = 1;
} finally {
try {
if (dataOutputStream != null){
dataOutputStream.close();
}
if (successReader != null){
successReader.close();
}
if (errorReader != null){
errorReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null){
process.destroy();
}
}
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
}
Log.d("installSlient", "result:"+result+"successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
使用Runtime.getRuntime().exec的方式,可以看到我们做了一个isRoot的判断,为什么要做这么一个判断呢,是因为我在没有root的机器执行su时,会报错,提示我的用户id不能root。所以这个时候我们就只能使用sh这条指令啦。至于判断是否root的方法,可以直接调用Runtime.getRuntime().exec(“su”)如果不返回Success就说明没有root,这算是一种取巧吧。
反射方式
/**
* 通过反射的方式实现静默安装
* @param apkPath
* @return
*/
public static boolean installSlientByReflection(String apkPath){
boolean result = false;
try {
Uri apkUri =Uri.parse(apkPath);
Class<?> pmService;
Class<?> activityTherad;
Method method;
activityTherad = Class.forName("android.app.ActivityThread");
Class<?> pmTypes[] = getParamTypes(activityTherad,"getPackageManager");
method = activityTherad.getMethod("getPackageManager", pmTypes);
Object PackageManagerService = method.invoke(activityTherad);
pmService = PackageManagerService.getClass();
Class<?> installTypes[] = getParamTypes(pmService, "installPackage");
method = pmService.getMethod("installPackage", installTypes);
method.invoke(PackageManagerService, apkUri, null, 0, null);
result = true;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
return result;
}
private static Class<?>[] getParamTypes(Class<?> cls, String mName) {
Class<?> cs[] = null;
Method[] mtd = cls.getMethods();
for (int i = 0; i < mtd.length; i++) {
if (!mtd[i].getName().equals(mName)) {
continue;
}
cs = mtd[i].getParameterTypes();
}
return cs;
}
通过反射的方式只写了安装的,卸载没有写。至于反射的原理,可以看我下面提供的参考文章有讲到,讲的非常好。
智能安装卸载
智能安装卸载说白了就是模拟用户将要点击操作的事情,都通过系统来完成,免去手动点击的麻烦。下面来看下,智能安装卸载的几个要点:
- 在AndroidManifest.xml中配置service
<service
android:name="com.example.custominstalldemo.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_config" />
</service>
通过service的配置,来分析下我们需要哪些文件
android:name=”com.example.custominstalldemo.MyAccessibilityService”主要实现模拟点击事件的类下面会介绍到。
android:label=”智能安装服务” 我们在系统设置里面的“辅助功能”中显示的一项内容的名字,如下图:
相信大家看图就能明白android:label的意思了。
再来看下
android:resource=”@xml/accessibility_config”
其中@xml/accessibility_config就是我们需要在工程目录res/xml/accessibility_config.xml创建的一个文件。具体内容看下面的介绍。
service配置中的内容差不多就这些是我们需要自己处理的,其他的内容都是必须的,固定的。
- com.example.custominstalldemo.MyAccessibilityService这个类是我们智能安装卸载主要处理的类,继承自AccessibilityService。下面代码实现了智能安装、卸载和停止APP的功能,他的作用就是模拟用户点击对应的按键。
import java.util.List;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
public class MyAccessibilityService extends AccessibilityService{
public static int INVOKE_TYPE = 0;
public static final int TYPE_KILL_APP = 1;
public static final int TYPE_INSTALL_APP = 2;
public static final int TYPE_UNINSTALL_APP = 3;
public static void reset(){
INVOKE_TYPE = 0;
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event != null){
switch (INVOKE_TYPE) {
case TYPE_INSTALL_APP:
install(event);
break;
case TYPE_UNINSTALL_APP:
uninstall(event);
break;
case TYPE_KILL_APP:
killApplication(event);
break;
default:
break;
}
}
}
@Override
public void onInterrupt() {
}
private void install(AccessibilityEvent event){
AccessibilityNodeInfo source = event.getSource();
if (source != null && "com.android.packageinstaller".equals(source.getPackageName())){
perform(source, "安装", "android.widget.Button", AccessibilityNodeInfo.ACTION_CLICK);
perform(source, "下一步", "android.widget.Button", AccessibilityNodeInfo.ACTION_CLICK);
perform(source, "打开", "android.widget.Button", AccessibilityNodeInfo.ACTION_CLICK);
}
}
private void uninstall(AccessibilityEvent event){
AccessibilityNodeInfo source = event.getSource();
if (source != null && "com.android.packageinstaller".equals(source.getPackageName())){
perform(source, "确定", "android.widget.Button", AccessibilityNodeInfo.ACTION_CLICK);
}
}
private void killApplication(AccessibilityEvent event){
AccessibilityNodeInfo source = event.getSource();
if (source != null && "com.android.settings".equals(source.getPackageName())){
perform(source, "强行停止", "android.widget.Button", AccessibilityNodeInfo.ACTION_CLICK);
perform(source, "确定", "android.widget.Button", AccessibilityNodeInfo.ACTION_CLICK);
}
}
private void perform(AccessibilityNodeInfo source, String viewName, String viewClass, int action){
List<AccessibilityNodeInfo> nodeList = source.findAccessibilityNodeInfosByText(viewName);
if (nodeList != null && !nodeList.isEmpty()){
for (AccessibilityNodeInfo node : nodeList) {
if (viewClass.equals(node.getClassName()) && node.isEnabled()){
node.performAction(action);
}
}
}
}
}
accessibility_config.xml
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" android:description="@string/accessibility_service_description" android:notificationTimeout="100" />
accessibility-service还有一个属性就是android:packageNames=”“可以指定要监听的包。
android:description这个是描述系统设置里辅助功能下我们做的智能安装服务的内容描述,看下图:
其他的属性就不说了,了解的也不深。
智能安装卸载的主要部分讲完了,现在要做的就是调用它。
/**
* 使用智能安装卸载前,我们要先把其对应的辅助功能打开
*/
public void openAutoInstall(){
Intent i = new Intent();
i.setAction(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(i);
}
/**
* 下面三个方法就是普通的调用,不过加了一个标志,这样我们的辅助服务就可以识别现在是哪种操作,然后做对应的事情。
* @param context
* @param filePath 文件绝对路径
*/
public void autoInstall(Context context, String filePath){
MyAccessibilityService.INVOKE_TYPE = MyAccessibilityService.TYPE_INSTALL_APP;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(Uri.parse("file://"+filePath), "application/vnd.android.package-archive");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
public void autoUninstall(Context context, String packageName){
MyAccessibilityService.INVOKE_TYPE = MyAccessibilityService.TYPE_UNINSTALL_APP;
Uri uri = Uri.parse("package:"+packageName);
Intent i = new Intent(Intent.ACTION_DELETE,uri);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
public void autoKillApp(String packageName){
MyAccessibilityService.INVOKE_TYPE = MyAccessibilityService.TYPE_KILL_APP;
Intent i = new Intent();
i.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
i.setData(Uri.parse("package:"+packageName));
startActivity(i);
}
参考文章
Android静默安装实现方案,仿360手机助手秒装和智能安装功能
Android常用代码之普通及系统权限静默安装APK
公共技术点之 Java 反射 Reflection
Android Accessibility(辅助功能) –实现Android应用自动安装、卸载