下班,走路,春寒料峭
想,你是人间的四月天
霎时风起,一川烟草,满城风絮,梅子黄时雨
可惜了,此时无雨
喜欢下雨,要等梅子黄时。
青玉案·凌波不过横塘路
作者:[贺铸]
凌波不过横塘路。但目送、芳尘去。锦瑟华年谁与度。月桥花院,琐窗朱户。只有春知处。
飞云冉冉蘅皋暮。彩笔新题断肠句。若问闲情都几许。一川烟草,满城风絮。梅子黄时雨。
书归正传,之前写的Android应用内更新组件,从6.0开始,为了适配7.0改动了一次,为了适配8.0又改了,前段时间又为了适配Android Q又改了,累积下来的冗余代码相当多,程序员都是有强迫症的,恰好最近任务不紧不慢,抽空整理起来。
第一步:检查是否有新的版本
应用内的app.gradle文件中已经配置了versionCode和VersionName,我们这里需要从服务端请求一个配置文件,用配置文件的版本信息和本地的进行对比,从而决定是否提示用户更新。
a.使用PackageManager 获取本地versionCode
PackageManager packageManager = mContext.getPackageManager();
try {
PackageInfo info = packageManager.getPackageInfo(mContext.getPackageName(), 0);
if (info != null) {
int vLocalCode = info.versionCode;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
b.Retrofit2请求服务端配置文件
我这里的配置文件是这样定义的
{
"data": {
"downUrl": "http://www.xx.com/update/app-release.apk",
"isForceUpdate": "1",
"preBaselineCode": "1",
"updateLog": "版本更新内容:\r\n1.更新了...。\r\n2.适配了...。",
"versionCode": "3",
"versionName": "1.1.3"
},
"message": "操作成功!",
"status": 1
}
当然这个文件的定义随意,但必须包含了版本信息,更新提示,apk的下载地址等要素。
第二步:提示用户有新版本,让用户选择是否更新
这一步通过一个自定的Dialog实现,内容大概长成这样:
这个并没有什么技术含量,代码就不贴了,这里吐槽一下,有的应用只放更新的Button,不更新就无法使用,这种做法很不友好,所以这里放了取消的Button,只是关闭当前弹窗,不更新可以继续使用。
第三步:下载新的安装包
private void downLoadApk() {
Call<ResponseBody> download = apiService.download(downloadUrl);
download.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
String path = mContext.getExternalFilesDir(null).getPath() + "/download/myApk";
Thread mThread = new Thread() {
@Override
public void run() {
super.run();
writeResponseToDisk(path, response, downloadListener);
}
};
mThread.start();
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
}
Retrofit2请求,但是这里我们需要获取到下载文件的进度,所以在onResponse回调中,开启子线程,做一些处理
private static void writeResponseToDisk(String path, Response<ResponseBody> response, DownloadListener downloadListener) {
writeFileFromIS(new File(path), response.body().byteStream(), response.body().contentLength(), downloadListener);
}
同是,我们定义了一个DownLoadListener接口,开放方法,对不同的情况做不同的处理
public interface DownloadListener {
void onStart();
void onProgress(int progress);
void onFinish(File file);
void onFail(String errorInfo);
}
继续处理我们的下载内容,其实也就是文件流的写入过程,很基础的东西,唯一不同是我们在不同的过程中调用了不同的监听方法,用来在通知中提示,还有一点需要注意一下,android Q 对于本地的文件读取写入机制做了改动,具体的很多文档都有说,这里不再赘述,这里只把代码贴出来,亲测可用。
private static void writeFileFromIS(File file, InputStream inputStream, long totalLength, DownloadListener downloadListener) {
downloadListener.onStart();
if (!file.exists()) {
if (!Objects.requireNonNull(file.getParentFile()).exists()) {
file.getParentFile().mkdir();
} else {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
downloadListener.onFail("createNewFile IoException");
}
}
}
OutputStream os = null;
long currentLength = 0;
try {
os = new BufferedOutputStream(new FileOutputStream(file));
byte data[] = new byte[sBufferSize];
int len;
while ((len = inputStream.read(data, 0, sBufferSize)) != -1) {
os.write(data, 0, len);
currentLength += len;
//计算当前下载进度
downloadListener.onProgress((int) (100 * currentLength / totalLength));
}
downloadListener.onFinish(file);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
第四步:给用户通知下载进度
其实这按照功能,也是第三步的内容,但是按照写代码的顺序,这是第四部分。
通知进度不外乎几种,一种是Notification,通过系统的通知栏给下载进度,一种是在自己应用内,给个进度条或者什么的,可能还有,才疏学浅,慢慢见识,我这里是第一种方式,使用Notification进行通知。
NotificationManager manager;
NotificationCompat.Builder mBuilder;
Notification notification;
private void initNotification() {
manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(String.valueOf(channelId), "通知", NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(true);
channel.setLightColor(ContextCompat.getColor(mContext, R.color.base_color));
channel.setShowBadge(true);
channel.setDescription("我的项目");
manager.createNotificationChannel(channel);
}
mBuilder = new NotificationCompat.Builder(mContext, channelId);
mBuilder.setContentTitle("正在下载...")
.setSmallIcon(R.mipmap.logo_fupin)
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.logo_fupin))
.setDefaults(Notification.DEFAULT_LIGHTS)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(false)
.setContentText("下载进度:" + "0%")
.setProgress(100, 0, false);
notification = mBuilder.build();
}
这里也需适配android Q,新版本加入了NotificationChannel ,必须设置channelId,否则这个通知栏不会显示的。
这里初始化好后,就可以在监听方法中为所欲为的使用了,例如:
downloadListener = new DownloadListener() {
@Override
public void onFinish(File file) {
mBuilder.setProgress(100, 100, false);
mBuilder.setContentText("下载完成" + 100 + "%");
notification = mBuilder.build();
mBuilder.setAutoCancel(true);
manager.notify(1, notification);
manager.cancel(1);
installApk2(file);
}
};
篇幅有限,这里只贴出onFinish()方法,用于上线连贯
第四步:安装apk
/**
* 安装apk 适配androidQ
*/
private static void installApk2(File file) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri apkUri = FileProvider.getUriForFile(mContext, "我们的包名.provider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
mContext.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
相信很多同学都遇到过下载完成,安装调用不起来的问题,放心,这都是源于Android一次一次的更新,心累,这里只贴代码,核心就是引入了一个FileProvider的东西,这个东西是需要在Manifest.xml文件里配置的
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="我们的包名.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
@xml/file_paths
是存放在xml文件夹下的path配置文件,需要自己去配置一个,位置在这:
内容其实更简单:
<?xml version="1.0" encoding="utf-8"?>
<resources >
<paths>
<external-path path="" name="download"/>
</paths>
</resources>
写到这里,大概一个应用内更新的组件就算完成了,交付测试组。
网友评论