apk的版本是现在App都基本必带的功能,一般是app每次启动的时候在启动页面获取服务器版本和app本地的版本进行比较,判断版本是都是否相同。还有种就是在我们的个人设置里面添加对app的版本更新检查,当然2种位置方式性质都是一样,只是看你的app怎么选择而已。
app的更新方式一般来说大致3种:
- 由服务器给出指定的更新地址,获取到服务器给出的下载地址后,直接跳转浏览器进行下载
- 可以通过当前app的包名跳转手机自带的应用市场,查找app进行下载更新
- 第三种方法相对于第一和第二种,需要自己干的事情就比较多了,在检测出版本更新,根据服务器给出的下载地址,通过service进行下载,并且将下载进度反馈到通知栏进行显示或者直接卡在当前屏幕弹出一个dialog使用户等待下载完毕,不过现在比较采用还是直接后台service下载,显示到通知栏
今天重点说下第三种即通过后台默认下载显示到进度栏通知,下载完成后自动安装的情况。
好吧,那就开始了
首先,检测版本是否需要更新,这里可以使用versionCode或者versionName这2种数值进行比较都可以,例:
PackageInfo pi = null;
try {
pi = getPackageManager().getPackageInfo("你的package", 0);
String name = pi.versionName;
//判断服务器和本地版本是否相同,触发版本更新条件正常来说都是服务器版本高于本地版本 才进行版本更新
if (versionData.getData().getVersion().compareTo(name)>0) {
//有新版本
} else {
//无版本
}
提示的对话框
AlertDialog dialog = new AlertDialog.Builder(activity).setTitle
("版本更新").setMessage(apk_messages)
.setNeutralButton("下次再说", (dialog1, which) -> {
dialog1.dismiss();
DialogUtils.switchTo(activity, MainActivity.class);
})
.setNegativeButton("立即更新", (dialog12, which) -> {
//构建service跳转开启service进行后台下载
Intent updateIntent = new Intent(activity, UpdateAppService.class);
updateIntent.putExtra("titleId", R.string.app_name);
updateIntent.putExtra(UpdateAppService.APK_UIL, apk_url);
activity.startService(updateIntent);//开始下载
dialog12.dismiss();
DialogUtils.showToastShort(activity,"正在下载,请稍后.....");
//点击更新后,继续回到主页面 不影响用户的正常使用
DialogUtils.switchTo(activity, MainActivity.class);
}).show();
dialog.setCanceledOnTouchOutside(false);//可选 点击dialog其它地方dismiss无效
dialog.setCancelable(false);//可选 点击返回键无效
接下来就是版本更新的核心service了
创建UpdateAppService继承service,并且构建需要的参数,实现onStartCommand方法,接收传递过来的apk更新地址,名称等数据
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//获取我们传递的参数
titleId = intent.getIntExtra("titleId", 0);
strAppUrl = intent.getStringExtra(APK_UIL);
// 获取路径并且创建存放新apk的file
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
updateDir = new File(Environment.getExternalStorageDirectory(), "data/sc.tengsen.theparty.com.update.updateapkdemo");
updateFile = new File(updateDir.getPath(), getResources().getString(titleId) + "_V" + ".apk");
Log.i("zl", "完整的file路径:" + updateFile);
}
//创建通知栏,完成初始化
this.updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//启动线程
new Thread(new UpdateRunner()).start();
return super.onStartCommand(intent, flags, startId);
}
创建线程,对不同的下载阶段进行处理,并且检查文件配置等是否完善:
class UpdateRunner implements Runnable {
Message message = updateHandler.obtainMessage();
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
message.what = 1;
//检查文件夹是否创建
try {
if (!updateDir.exists()) {
updateDir.mkdirs();
}
//检查文件是否创建
if (!updateFile.exists()) {
updateFile.createNewFile();
}
//本地创建无问题之后 开始下载 并且返回下载的进度
long downloadSize = downloadUpdateFile(strAppUrl, updateFile);
if (downloadSize > 0) {
//下载成功
updateHandler.sendMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
message.what = 0;
//下载失败
updateHandler.sendMessage(message);
}
}
}
private Handler updateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
// 下载失败
stopSelf();
break;
case 1:
//下载成功
//自动安装
installApp();
// 停止服务
stopSelf();
break
default:
stopSelf();
break;
}
}
};
下载部分 采用android原生httpConnection
进行下载,当然其他的网络封装也可以使用,这个根据个人习惯使用就好。
public long downloadUpdateFile(String downloadUrl, File saveFile) throws Exception {
//创建下载过程中需要的几个统计值
int downloadCount = 0;
int currentSize = 0;
long totalSize = 0;
int updateTotalSize = 0;
//创建下载实例
HttpURLConnection httpConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
//对地址和地址进行处理 并且设置参数属性
URL url = new URL(downloadUrl);
httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestProperty("User-Agent", "PacificHttpClient");
if (currentSize > 0) {
httpConnection.setRequestProperty("RANGE", "bytes=" + currentSize + "-");
}
httpConnection.setConnectTimeout(10000);
httpConnection.setReadTimeout(20000);
updateTotalSize = httpConnection.getContentLength();//已经更新的总长度
//返回报错404
if (httpConnection.getResponseCode() == 404) {
throw new Exception("fail!");
}
is = httpConnection.getInputStream();
fos = new FileOutputStream(saveFile, false);//对指定文件位置
//转换成字节数据
byte buffer[] = new byte[4096];
int redaSize = 0;
int persnet = 0;
//开启while循环更新通知消息
while ((redaSize = is.read(buffer)) > 0) {
fos.write(buffer, 0, redaSize);
totalSize += redaSize;
//获取当前下载进度数
persnet = (int) (totalSize * 100 / updateTotalSize);
if ((downloadCount == 0) || persnet - 1 > downloadCount) {
PendingIntent pendingIntent = PendingIntent.getActivity(UpdateAppService.this, 0, new Intent(Intent.ACTION_VIEW), 0);
downloadCount += 1;
Log.v("进度:", persnet + "%");
//由于android sdk在8.0之后必须使用渠道才能正常使用通知栏后,所以这里要判断区分下当前sdk版本
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel channel = new NotificationChannel("0", this.getPackageName(), NotificationManager.IMPORTANCE_HIGH);
channel.setSound(null, null);
updateNotificationManager.createNotificationChannel(channel);
this.updateNotification = new Notification.Builder(UpdateAppService.this, "0")
.setContentTitle("正在下载" + persnet + "%")
.setProgress(100, persnet, false)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build();
} else {
this.updateNotification = new Notification();
this.updateNotification.flags = Notification.FLAG_AUTO_CANCEL;
Notification.Builder builder = new Notification.Builder(this);//新建Notification.Builder对象
builder.setContentTitle("正在下载" + persnet + "%");
builder.setProgress(100, persnet, false);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setSound(null);
builder.setContentIntent(pendingIntent);//执行intent
updateNotification = builder.getNotification();//将builder对象转换为普通的notification
}
//发出通知 指定渠道
updateNotificationManager.notify(0, updateNotification);
}
}
} finally {
//下载完成后 要断开和关闭
if (httpConnection != null) {
httpConnection.disconnect();
}
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
}
return totalSize;
}
最后一步就是调用安装
private void installApp() {
if (updateFile == null || !updateFile.getPath().endsWith(".apk")) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
//判读版本是否在7.0以上
if (Build.VERSION.SDK_INT >= 24) {
Uri apkUri = FileProvider.getUriForFile(this, "你的package.fileprovider", updateFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive");
}
startActivity(intent);
updateNotificationManager.cancelAll();//移除通知栏消息
}
这里主要注意在android 7.0以后,android手机不能直接调起系统自带的安装器,需要配置你的package.fileprovider
在res文件下创建xml文件夹,创建 filepaths文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
name="external"
path="" />
<files-path
name="files"
path="" />
<cache-path
name="cache"
path="" />
</paths>
</resources>
但是这里还有个问题,如果你有引用TakePhoto、Udesk、Bugly等等第三方库,你会发现要么你自己创建的这个provider的不能用了,所以这个时候我们需要把自己创建和三方的进行区分。我这里采用的是Google推荐的使用FileProvider去适配新的版本的方式,创建一个类。在使用的时候继承provider,并且在项目的AndroidMainfest文件做出配置:
public class FileNewProvider extends FileProvider {
}
<provider
android:name=".utils.FileNewProvider"
android:authorities="你的package.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
这样基本就能与三方使用的进行区分了,保证自己项目的Provider生效
功能讲述基本就这些了,代码有详细注释,而且所有代码基本都在上面了,小弟才开始写 ,当然也参考别人大神写过的,如果思路,语言有啥不正确的地方,希望大家帮忙指出,一起进步