注:本篇文章参考自源项目,接手之前部分代码就有了,只不过在Android高版本上不能安装APK,还有部分逻辑未完善,如果有原创哥们不经意间看到这篇文章,可以评论一下原文地址,我下次看到了标注好,无意冒犯。
一、引言
-
当前Android系统在国内已经非常成熟,各大厂更新机制各有千秋,当下比较出名的热更新、插件化、迭代升级,甚至还有最近国内N多某某小动作片还用了大量的H5嵌套,便于页面动态切换。关于热更新、插件化这方面就不多说了,感兴趣的朋友可以在网上看看,大厂框架比较多也比较成熟。近一年应公司业务一直在写SDK,里面用到了大量的插件化、动态加载、HOOK技术,年底忙完抽空写一篇文章详细解读,可以关注一下。
-
其实APK迭代升级这种方式比较常见,只是如果APK已经上传了应用市场,经常进行APK迭代对用户来说比较反感,所以大型应用一般都是热更新为主,除非大改动、大更新才会进行迭代升级。
-
APK迭代一般分为自己平台上传APK,自行升级;还有一种就是应用市场升级,应用市场升级基本就交给市场;这篇文章主要讲一下APK内部升级,方便大伙借鉴。
二、实现步骤
- 请求服务器获取服务器上版本信息,与本地对比决定是否升级,当然代码自己写,强不强制看需求。我这里用的是 versionCode 字段,因为这个是整数,判断起来方便。以下是获取的字段信息。
{ "apk_name":"app_Name", "apk_version":"1.0.0", "apk_code":2, "isUpdata":0, "download":"http://qn.yingyonghui.com/apk/6741348/de3d5ab.apk", "data":[ { "code":"新增功能暗黑模式" }, { "code":"优化部分BUG" } ] }
- 然后进行Code对比,网络请求这块我使用的是okhttp,
Request request=new Request.Builder().url(QUEST_URL).build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { if(response.code() == 200){ String content = response.body().string(); if(!TextUtils.isEmpty(content)){ try { JSONObject jsonObject = new JSONObject(content); int isUpdata = JsonUtils.getInt(jsonObject, "isUpdata"); // 是否强制升级 0,不强制,1,强制升级 int apk_code = JsonUtils.getInt(jsonObject, "apk_code"); // 应用版本号 String download = JsonUtils.getString(jsonObject, "download"); // apk下载地址 JSONArray array = JsonUtils.getJSONArray(jsonObject,"data"); // 升级内容 if(apk_code > Utils.getVersionCode(mainActivity)){ upDataApk(mainActivity,isUpdata,download,array); } else { if(isTos){ mainActivity.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(mainActivity,"当前已经是最新版本了",Toast.LENGTH_SHORT).show(); } }); } } } catch (JSONException e) { e.printStackTrace(); } } } } });
- 下载APK文件
Request request=new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
int lenght=0;
byte[] buf=new byte[2048];
InputStream inputStream=null;
FileOutputStream fileOutputStream=null;
File f = null;
try {
//存储下载文件目录
String sd = SDCardUtils.getExternalSdCardPath();
if (!TextUtils.isEmpty(sd)) {
f = new File(downPath);
if (!f.exists()) {
boolean externalDirSuccess = f.mkdirs();
if (!externalDirSuccess){
f = new File(context.getDir("cachedata", 0) + File.separator);
if (!f.exists()) {
boolean internalDirSuccess = f.mkdirs();
if (!internalDirSuccess){
f = new File(context
.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
.getAbsolutePath()+File.separator);
if (!f.exists()){
boolean dirSuccess = f.mkdir();
if (!dirSuccess){
listener.onDownloadFailed();
return;
}
}
}
}
}
}
}
inputStream = response.body().byteStream();
long total = response.body().contentLength();
File file = new File(f.getAbsoluteFile(), getNameFromUrl(url));
if(file.exists()) {
file.delete();
}
file.getParentFile().mkdirs();
getRoot("chmod -R 777 "+file.getAbsolutePath());
fileOutputStream = new FileOutputStream(file);
long sum = 0;
while ((lenght = inputStream.read(buf)) != -1) {
fileOutputStream.write(buf,0,lenght);
sum+=lenght;
int progress=(int)(sum*1.0f/total*100);
listener.onDownloading(progress);
}
fileOutputStream.flush();
listener.onDownloadSuccess(file);
}catch (Exception e){
listener.onDownloadFailed();
}finally {
try {
if (inputStream != null)
inputStream.close();
if (fileOutputStream != null)
fileOutputStream.close();
}catch (Exception e){
}
}
AppLogger.e(TAG+"onResponse 2");
}
});
- 接下来就是APK安装了
try{
String provide = "com.mars.mxbrowser.fileprovider";
Uri contentUri = FileProvider.getUriForFile(mainActivity.getApplicationContext(), provide, file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
//写的时候没有权限 所以加上权限
String[] command = {"chmod", "777", file.getAbsolutePath() };
ProcessBuilder builder = new ProcessBuilder(command);
try {
builder.start();
} catch (IOException e) {
e.printStackTrace();
}
mainActivity.startActivity(intent);
dialog.dismiss();
}catch (Throwable e){
e.printStackTrace();
}
- 上面安装这里之前是有点问题的,因为高版本上安装需要配置provider,provider是在AndroidManifest.xml,</application>标签下面进行配置,我测试手机是10.0的。以下是provider的配置,这里要主要一下:所配置的authorities一定要和上面安装APK打开器配置的provide一致,否则会报错;还有一些开发人员android:name时配置找不到,比如在一些低版本上,这里我选用的是androidx.core.content.FileProvider,属于androidx,在gradle下配置一下就行了。
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/bd_file_paths" /> </provider>
- 到这里就结束了,打开APK安装器后会弹出应用安装页面,取决于用户会不会去安装,一般应用升级的是用户自己点的,应该是没什么疑问的。我们做SDK时,很多时候都是静默下载安装,用户都比较懵逼,当然高版本上也有限制,也需要用户自己安装。需要Demo代码的可以留言邮箱。