由于项目要求,在应用中做了一个升级功能,涉及到了APK的安装,所以在这里总结一下。如果有错误还请大神们指出
非静默安装
用户看到安装过程,能够进行干涉,能取消。目前很少用到
public void commonInstall(ApkInfo apkInfo) throws IOException{
LogUtils.i(TAG, "commonInstall");
File file = new File(apkInfo.getDownloadFilePath());
if(!file.exists()){
throw new FileNotFoundException();
}
Uri uri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intent);
}
静默安装
用户无法看见安装过程,无法干涉及取消。是目前的主流,分以下两种情况
1.可以获取到/system/xbin/su文件
package com.example.androidinstalldemo.executor;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.util.Log;
import com.example.androidinstalldemo.entity.ApkInfo;
import com.example.androidinstalldemo.utils.LogUtils;
import com.example.androidinstalldemo.utils.PackageManagerUtils;
/**
*
* @author alex_shaw
*
*/
public class SilencePushExecutor {
public static final String TAG = "SilencePushExecutor";
public Context context;
public static SilencePushExecutor executor;
private SilencePushExecutor(Context context) {
this.context = context;
}
public synchronized static SilencePushExecutor getInstance(Context context){
if(null == executor){
executor = new SilencePushExecutor(context);
}
return executor;
}
/**
* @author alex_shaw
*
* @param info 所下载的APK的bean
*/
public void execute(ApkInfo info){
int oldVersionCode = 0;//原始版本的versionCode
int newVersionCode = info.getVersionCode();
File file = new File(info.getDownloadFilePath());//需要进行安装的APK路径
if(!file.exists())
return;
LogUtils.i(TAG, file.getAbsoluteFile());
try{
/********获取原APK的versionCode和versionName************************/
ApplicationInfo appInfo = PackageManagerUtils.getApplicationInfo(context, info.getPackageName());
if(appInfo!=null){
PackageInfo packageInfo = PackageManagerUtils.getPackageInfo(context, appInfo.sourceDir);
oldVersionCode = packageInfo.versionCode;
}
if(newVersionCode > oldVersionCode){
cmdTask(file, appInfo);
}
}catch(IOException e){
LogUtils.e(TAG, e.getMessage());
}catch (InterruptedException e) {
LogUtils.e(TAG, e.getMessage());
}
}
/**
*
* @author alex_shaw
*
* @param file
* @param appInfo
* @throws IOException
* @throws InterruptedException
*/
private void cmdTask(File file, ApplicationInfo appInfo)
throws IOException, InterruptedException {
Process p = Runtime.getRuntime().exec("/system/xbin/su");
DataOutputStream os = new DataOutputStream(p.getOutputStream());
//os.writeBytes("mount -o rw,remount /system \n");
os.writeBytes("adb remount \n");
os.writeBytes("busybox mv " +file.getAbsolutePath()+ " /system/app \n");
os.writeBytes("busybox chmod 777 /system/app/"+file.getName()+" \n");
if(appInfo !=null){
os.writeBytes("busybox rm "+appInfo.sourceDir+" \n");
os.writeBytes("pm uninstall "+appInfo.packageName+" \n");
os.writeBytes("busybox rm -r "+appInfo.dataDir+" \n");
}
os.writeBytes("sync \n");
os.writeBytes("exit \n");
int code = p.waitFor();
Log.i("msg","code = " + code);
}
}
2.无法获取su文件,但有root权限
public void packageManagerInstallInReflect(ApkInfo apkInfo) throws FileNotFoundException{
File file = new File(apkInfo.getDownloadFilePath());
if(!file.exists()){
throw new FileNotFoundException();
}
Uri uri = Uri.fromFile(file);
try {
PackageManager pm = context.getPackageManager();
//此处无法通过所注释的代码获取改方法,提示NoSuchMethodException,应该是IPackageInstallObserver.Stub.class这个类的原因,这是系统的类,在这里用可能有问题
//Method method = pm.getClass().getMethod("installPackage", Uri.class,IPackageInstallObserver.Stub.class,Integer.class,String.class);
Method[] methods = pm.getClass().getMethods();
for(Method m : methods){
if("installPackage".equals(m.getName())){
m.invoke(pm, uri,new MyInstallObserver(),INSTALL_REPLACE_EXISTING,apkInfo.getPackageName());
break;
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private class MyInstallObserver extends IPackageInstallObserver.Stub{
/**
* 安装完成之后的回调,自定义逻辑
* @param packageName 安装的包名
* @param returnCode 安装结果
*/
@Override
public void packageInstalled(String packageName, int returnCode)
throws RemoteException {
LogUtils.i(TAG, packageName + "Install Success,the code = " + returnCode);
}
}
说一下具体的步骤
1.需要一个IPackageInstallObserver.aidl 的文件,从源码里可以找到。
2.将这个aidl文件加入到项目中,但是不要改变它的包名。
3.在我们写IPackageInstallObserver.Stub的实现类的时候,eclipse可能提示我们找不到该包或类,这时候就需要我们收到的import了。
4.需要我们在清单文件中添加INSTALL_PACKAGES的权限
5.打包生成的APK只有放到system/app下才能运行。
附加上我所遇到的所有关于升级失败的原因,
非静默升级,提示解析失败,我遇到原因有两个,
1,APK在下载的时候出现数据丢失的情况,建议在下载完成之后做一次MD5校验,确保其完整性。
2,APK下载完成之后,MD5值正确,但是没有权限,建议将APK以及APK所在的目录做一次chmod 777 操作。
非静默升级,提示签名冲突,这是因为系统里面的APK的签名和你要进行升级安装的APK的签名不一致导致。
静默升级,遇到的一个非常奇怪的问题,在通过mv命令移动APK至system/app目录下时,移动前和移动后的APK的MD5会不一致,我只在朝歌的盒子上发现过这个问题,应该是rom的问题。
另外一个问题,如果说你的APK已经存在于system/app,这个时候你adb install 一个低版本的APK,是可以安装上的,也可以运行,但是重启之后又会被高版本的APK覆盖掉