如需转载请标明出处 https://www.jianshu.com/p/c0f7b3418c24
首先,看了很多安卓版本更新的文章,然后有小伙伴们反映文章不够全面。然后决定写一篇版本更新的文章。
版本更新设计到两个概念:非强制更新,强制更新
(一)非强制更新:就是客户端请求服务端,通过请求的版本号 version code(整数),和服务器的最新版本号进行对比,如果当前版本号,小于服务器版本号,则客户端需要更新,否则不需要更新,客户端可以选择是否更新。
强制更新:就是客户端请求服务端,服务端返回需要强制更新的命令,客户端就需要强制用户升级,不升级,就不允许继续使用客户端。
这里引出了,为什么要强制更新。
1、强制解决掉旧版APP中带有的某些bug,提升用户体验以及安全性,保障用户;
2、减轻工作量。因为,一个服务器API接口每多支持一个版本,就会给服务器接口开发者带来很大的工作量;
3、在新版本的APP里,会有新的特性和新的功能。强制用户更新,能够让用户在大概率上跟上产品经理给用户设定的套路,希望用户去使用新的功能,新的特性,已达到自己想要的目的。
版本更新的时机
1.可以是整个app的接口进行携带版本号(所有请求服务端页面),然后服务端和最新的版本号进行判断,返回是否更新,或者强制更新
2.可以在app设置页面,增加检查更新的功能。
(二)整体流程
请求服务端--->不需要更新
请求服务端 --->非强制更新--->下载服务器的apk--->安装apk
请求服务端 ---->强制更新--->下载服务器的apk--->安装apk
(三)相关的坑(含代码)
下载apk保存在sd卡,需要android动态检查权限, 6.0动态检查权限
7.0以上安装apk,Uri.fromFile(file)异常奔溃, 7.0以上通过隐式意图调用系统安装程序安装APK
可参考eg:https://www.jianshu.com/p/577816c3ce93
8.0以上通知栏不显示通知 8.0通知不显示问题
可参考eg:https://blog.csdn.net/u010231682/article/details/80732879
8.0以上安装失败 8.0未知应用安装权限
可参考eg:https://blog.csdn.net/changmu175/article/details/78906829
这里下载用的后台service进行下载
1.下载
private void downLoad(String fileUrl) {
MyObserverNoDialog<ResponseBody> observerNoDialog = new MyObserverNoDialog<ResponseBody>() {
@Override
public void onNext(final ResponseBody body) {
writeResponseBodyToDisk(body);
}
@Override
protected void onError(ResultException e) {
builder1.setContentText("下载失败,请稍后重试"); //消息内容
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
}
};
Retrofit client = RetrofitUtil.getRetrofit();
HomePageService service = client.create(HomePageService.class);
service.versionUpdate(fileUrl)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(observerNoDialog);
}
2.写入到sd卡
private void writeResponseBodyToDisk(ResponseBody body) {
try {
long time = System.currentTimeMillis();//当前时间的毫秒数
//创建下载APK的路径
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File futureStudioIconFile = new File(Environment.getExternalStorageDirectory(), time + "updata.apk");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
long currentTime = System.currentTimeMillis();
if (currentTime- lasstTime > 500) {
builder1.setProgress(100, (int) (fileSizeDownloaded * 100 / fileSize), false);
builder1.setContentText("正在下载:" + fileSizeDownloaded * 100 / fileSize + "/100"); //消息内容
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
lasstTime = currentTime;
}
}
if (fileSizeDownloaded == fileSize) {
//builder1.setDefaults(Notification.DEFAULT_ALL); //设置默认的提示音,振动方式,灯光
builder1.setProgress(100, 100, false);
builder1.setContentText("下载完成"); //消息内容
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
}
install(getApplicationContext(), futureStudioIconFile);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.安装
/**
* 通过隐式意图调用系统安装程序安装APK
*/
public static void install(Context context, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) { //判读版本是否在7.0以上
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri =
FileProvider.getUriForFile(context, "com.zz.zz.fileprovider", file);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
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");
}
context.startActivity(intent);
}
4.未知权限检测
boolean haveInstallPermission1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//先获取是否有安装未知来源应用的权限
haveInstallPermission1 = mContext.getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission1) {//没有权限
Utils.showAlertDialog(mContext, "提示", "安装应用需要打开未知来源权限,请去设置中开启权限", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startInstallPermissionSettingActivity();
}
}
}, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
return;
} else {
PermissionApplyUtil.getInstance().checkStoragePermission(mContext, new PermissionApplyUtil.PermissionCheckCallBack() {
@Override
public void onHasPermission() {
Intent intent = new Intent(mContext, VersionUpdateService.class);
intent.putExtra("Key_Down_Url", apkUrl);
mContext.startService(intent);
((TextView) view).setText("正在下载");
view.setEnabled(false);
}
});
}
} else {
PermissionApplyUtil.getInstance().checkStoragePermission(mContext, new PermissionApplyUtil.PermissionCheckCallBack() {
@Override
public void onHasPermission() {
Intent intent = new Intent(mContext, VersionUpdateService.class);
intent.putExtra("Key_Down_Url", apkUrl);
mContext.startService(intent);
((TextView) view).setText("正在下载");
view.setEnabled(false);
}
});
}
这个service源码。
public class VersionUpdateService extends Service {
private String appName;
private String url;
// 通知栏
private Notification notification = null;
private NotificationManager notificationManager = null;
// 通知栏跳转Intent
private Intent updateIntent = null;
private PendingIntent pendingIntent = null;
Notification.Builder builder1;
private long lasstTime = 0;
/***
* 创建通知栏
*/
RemoteViews contentView;
/**
* 8.0通知不显示问题
*
* @param
* @return
*/
private static final int PUSH_NOTIFICATION_ID = (0x001);
private static final String PUSH_CHANNEL_ID = "PUSH_NOTIFY_ID";
private static final String PUSH_CHANNEL_NAME = "PUSH_NOTIFY_NAME";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null)
url = intent.getStringExtra("Key_Down_Url");
createNofity();
downLoad(url);
return super.onStartCommand(intent, flags, startId);
}
public void createNofity() {
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(PUSH_CHANNEL_ID, PUSH_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder1 = new Notification.Builder(this, PUSH_CHANNEL_ID);
} else {
builder1 = new Notification.Builder(this);
}
builder1.setSmallIcon(R.mipmap.ic_placeholder); //设置图标
builder1.setTicker("开始下载");
builder1.setContentTitle(appName); //设置标题
builder1.setContentText("正在下载:"); //消息内容
builder1.setWhen(System.currentTimeMillis()); //发送时间
// builder1.setDefaults(Notification.DEFAULT_ALL); //设置默认的提示音,振动方式,灯光
builder1.setAutoCancel(true);//打开程序后图标消失
// // 设置下载过程中,点击通知栏,回到主界面
// updateIntent = new Intent(this, MainActivity.class);
// pendingIntent =PendingIntent.getActivity(this, 0, updateIntent, 0);
// builder1.setContentIntent(pendingIntent);
//设置进度条
builder1.setProgress(100, 0, false);
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
}
private void downLoad(String fileUrl) {
MyObserverNoDialog<ResponseBody> observerNoDialog = new MyObserverNoDialog<ResponseBody>() {
@Override
public void onNext(final ResponseBody body) {
writeResponseBodyToDisk(body);
}
@Override
protected void onError(ResultException e) {
builder1.setContentText("下载失败,请稍后重试"); //消息内容
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
}
};
Retrofit client = RetrofitUtil.getRetrofit();
HomePageService service = client.create(HomePageService.class);
service.versionUpdate(fileUrl)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(observerNoDialog);
}
private void writeResponseBodyToDisk(ResponseBody body) {
try {
long time = System.currentTimeMillis();//当前时间的毫秒数
//创建下载APK的路径
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File futureStudioIconFile = new File(Environment.getExternalStorageDirectory(), time + "updata.apk");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
long currentTime = System.currentTimeMillis();
if (currentTime- lasstTime > 500) {
builder1.setProgress(100, (int) (fileSizeDownloaded * 100 / fileSize), false);
builder1.setContentText("正在下载:" + fileSizeDownloaded * 100 / fileSize + "/100"); //消息内容
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
lasstTime = currentTime;
}
}
if (fileSizeDownloaded == fileSize) {
//builder1.setDefaults(Notification.DEFAULT_ALL); //设置默认的提示音,振动方式,灯光
builder1.setProgress(100, 100, false);
builder1.setContentText("下载完成"); //消息内容
notification = builder1.build();
notificationManager.notify(PUSH_NOTIFICATION_ID, notification); // 通过通知管理器发送通知
}
install(getApplicationContext(), futureStudioIconFile);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过隐式意图调用系统安装程序安装APK
*/
public static void install(Context context, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) { //判读版本是否在7.0以上
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri =
FileProvider.getUriForFile(context, "com.zz.zz.fileprovider", file);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
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");
}
context.startActivity(intent);
}
}
未知应用权限源码
boolean haveInstallPermission1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//先获取是否有安装未知来源应用的权限
haveInstallPermission1 = mContext.getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission1) {//没有权限
Utils.showAlertDialog(mContext, "提示", "安装应用需要打开未知来源权限,请去设置中开启权限", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startInstallPermissionSettingActivity();
}
}
}, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
return;
} else {
PermissionApplyUtil.getInstance().checkStoragePermission(mContext, new PermissionApplyUtil.PermissionCheckCallBack() {
@Override
public void onHasPermission() {
Intent intent = new Intent(mContext, VersionUpdateService.class);
intent.putExtra("Key_Down_Url", apkUrl);
mContext.startService(intent);
((TextView) view).setText("正在下载");
view.setEnabled(false);
}
});
}
} else {
PermissionApplyUtil.getInstance().checkStoragePermission(mContext, new PermissionApplyUtil.PermissionCheckCallBack() {
@Override
public void onHasPermission() {
Intent intent = new Intent(mContext, VersionUpdateService.class);
intent.putExtra("Key_Down_Url", apkUrl);
mContext.startService(intent);
((TextView) view).setText("正在下载");
view.setEnabled(false);
}
});
}
总结:其实呢,这些坑都是在实际开发中遇到的,然后查阅相关的资料,一步一步的解决。项目完成后,就得总结下,吸取下教训。这里主要是版本兼容性问题,所以作为开发者还是特时刻关注andorid 版本的更新的内容,新特性,才能在开发中少走弯路,游刃有余。
希望能帮助到各位看官,如有帮助,点个赞哦!~
若发现错误,请留言,一起讨论,能力有限,敬请原谅。
网友评论