前不久遇到一个需求,说是要实现app的静默安装并且安装完了之后要能够自启动。我上网查了很多资料,方法基本都差不多,但是基本上每种方法我都试过了,能够实现静默安装,但是自启动的时候就遇到了问题。很多类似的博客下面几乎都有人提问说为啥不能够自启动。我当时在操作的时候也发现自启动失败,后来我反复推敲尝试实现了这个功能,大多数博客介绍的方法是没有问题的,问题在于博主没有把过程讲述得很清楚,从而给读者造成了误解,特此发文一篇希望帮助广大受阻的读者。
静默更新这个需求是有一定的前提的,一般这类的需求只会出现在一些厂商定制的pad或特定设备上面,系统要给足了root权限,手机上有一类做法可以在不需要root权限的前提下实现更新,但模式算不得静默更新,当然这不是我们今天要讨论的问题,我也就不在这里多说了。
一、安装包下载
安装包下载相关的文章数不胜收,主要是代码涉及静默安装,我这里就把我的代码拿出来做个参考吧。
public class DownLoadUtils {
public static void download(final String apkUrl) {
Thread thread;
thread = new Thread(new Runnable() {
@Override
public void run() {
downloadFile(apkUrl);
}
});
thread.start();
}
//下载apk
public static File downloadFile(String apkUrl) {
final String fileName = "update.apk";
File tmpFile = new File("/sdcard/update");
if (!tmpFile.exists()) {
tmpFile.mkdir();
}
final File file = new File("/sdcard/update/" + fileName);
try {
URL url = new URL(apkUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStream is = conn.getInputStream();
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
conn.connect();
double count = 0;
if (conn.getResponseCode() >= 400) {
//连接超时
} else {
while (count <= 100) {
Log.e("install", "download" + count + "");
if (is != null) {
int numRead = is.read(buf);
if (numRead <= 0) {
break;
} else {
fos.write(buf, 0, numRead);
}
} else {
break;
}
}
}
conn.disconnect();
fos.close();
is.close();
InstallApkUtils.excuteSuCMD(InstallApkUtils.sdPath);//执行静默安装
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
}
上面的代码中,当安装包下载完成之后,开始执行静默安装方法。
二、静默安装
强调一点,文章中提到的静默安装是在系统开发了root权限的前提之下,如果没有获取root权限,还是采用系统的安装流程吧!
public class InstallApkUtils {
public static Context mContext = null;
public static String sdPath = "/sdcard/update/update.apk";//下载sd路径
//判断是否update目录下有文件
public static boolean isHasFile() {
try {
File f = new File(sdPath);
if (!f.exists()) {
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
public static void excuteSuCMD(String currenttempfilepath) {
if (isHasFile()) {
Process process = null;
OutputStream out = null;
InputStream in = null;
try {
//请求root
process = Runtime.getRuntime().exec("su");
out = process.getOutputStream();
//调用安装
out.write(("pm install -r " + currenttempfilepath + "\n").getBytes());
in = process.getInputStream();
int len = 0;
byte[] bs = new byte[256];
while (-1 != (len = in.read(bs))) {
String state = new String(bs, 0, len);
if (state.equals("success\n")) {
//安装成功后的操作
//静态注册自启动广播
Intent intent = new Intent();
//与清单文件的receiver的anction对应
intent.setAction("android.intent.action.PACKAGE_REPLACED");
//发送广播
mContext.sendBroadcast(intent);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
Log.e("install", "apk is not exist");
}
}
}
安装完成之后我们会发送一条广播
//静态注册自启动广播
Intent intent = new Intent();
//与清单文件的receiver的anction对应
intent.setAction("android.intent.action.PACKAGE_REPLACED");
//发送广播
mContext.sendBroadcast(intent);
三、注册并接收广播
上面的安装中提到了安装结束会发送一个广播,这条广播的作用就是如果发生了安装包的替换就重新启动应用。
注意:很多人说安装完成之后,应用并没有自己启动,问题在于更新后的安装包没有注册并接收上一个版本发出的广播,下面所提到的源代码一定要在新安装的apk包中实现。
public class UpdateReceiver extends BroadcastReceiver {
public static final String UPDATE_ACTION = "android.intent.action.PACKAGE_REPLACED";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(UPDATE_ACTION)) {
Intent intent2 = new Intent(context, TestActivity.class);
intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent2);
}
//接收安装广播
if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) {
String packageName = intent.getDataString();
// System.out.println("安装了:" + packageName + "包名的程序");
}
//接收卸载广播
if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) {
String packageName = intent.getDataString();
// Log.e("install","卸载了:" + packageName + "包名的程序");
}
}
}
然后在AndroidManifest.xml文件中注册UpdateReceiver,当然你也可以选择在源代码中动态注册。
<!--静默更新、自启动-->
<receiver android:name=".utils.UpdateReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
当然需要用到的权限不要忘了声明
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
四、总结
整个过程大概流程如下:
- 下载apk到指定的文件夹下(声明相关的权限);
- 注册广播,用来监听app的变动情况实现app的自启动,广播的注册和接收处理在所有的app版本里都要存在;
- 获取root权限,从文件夹获取安装包,执行安装程序;
- 安装完成之后,发送广播重新启动app。
我当时在实现的过程中,发现app没有重新启动,但是退出app再重新进入已经是更新过的版本,后来我反复阅读别人的文章,发现是我自己没有理解透彻整个实现的过程,我放在服务器上的安装包没有实现广播的接收流程,所以导致了app没有重新启动,后来重新上传了安装包测试通过。