Android(安卓)APP升级(兼容Android7.0报错,Android8.0不能自动安装)
16lz
2021-01-26
之前有写过利用腾讯Bugly实现APP的热更新以及版本升级Android 热更新框架Bugly-9步完成热更新/自动更新/异常上报分析,今天来讲一下不借助第三方的应用升级。
演示效果:
原理:
1.将新版本上传到自己的服务器,有服务器将最新版本信息记录
2.当用户打开app或者手动触发版本检查时向服务器请求版本信息以及最新版本apk的下载地址
3.判断当前版本是不是最新版本,如果不是则通过下载地址下载apk
4.下载完成后吊起安装程序进行安装覆盖
5.实现了版本更新
本次例子用到框架:
1.easypermissions 权限控制
2.gson json对象解析
3.xUtils-2.6.14.jar 网络请求
所用权限(注意最后一个权限):
注意点:
1."android.permission.REQUEST_INSTALL_PACKAGES"这个权限必须加上,否则可能会在apk下载完成后不能吊起安装程序(一闪而逝)
2.Android8以上如果要在通知栏显示下载进度,需要进行notification的适配Android8.0 notification channel
3.在Android6.0以上需要进行权限的申请
4.Android7.0以上文件读取需要通过FileProvider进行操作关于 Android 7.0 适配中 FileProvider 部分的总结
下面看主要代码:
MainActivity
package cn.humanetplan.updateappdemo;import android.Manifest;import android.content.Intent;import android.support.annotation.NonNull;import android.view.View;import android.widget.TextView;import com.google.gson.Gson;import com.lidroid.xutils.HttpUtils;import com.lidroid.xutils.exception.HttpException;import com.lidroid.xutils.http.RequestParams;import com.lidroid.xutils.http.ResponseInfo;import com.lidroid.xutils.http.callback.RequestCallBack;import com.lidroid.xutils.http.client.HttpRequest;import java.util.List;import pub.devrel.easypermissions.EasyPermissions;;public class MainActivity extends BaseActivity implements EasyPermissions.PermissionCallbacks{ TextView tv_check, tv_versionName; @Override public void DoSthBeforeInflate() { } @Override protected int setContentLayout() { return R.layout.activity_main; } @Override protected void init() { tv_check = findViewById(R.id.tv_check); tv_versionName = findViewById(R.id.tv_versionName); tv_versionName.setText("当前版本V"+BuildConfig.VERSION_NAME); tv_check.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CheckVersion(); } }); } VersionBean versionBean; private void CheckVersion() { tipDialog.show(); HttpUtils httpUtils = new HttpUtils(); RequestParams params = new RequestParams(); httpUtils.send(HttpRequest.HttpMethod.GET, "http://47.107.173.197/Android/GetVersionInfo.php", new RequestCallBack() { @Override public void onSuccess(ResponseInfo responseInfo) { tipDialog.dismiss(); try { Gson gson = new Gson(); versionBean = gson.fromJson(responseInfo.result, VersionBean.class); if (versionBean != null && versionBean.isSuccess()) { DialogUtils.ShowTipsDialog(MainActivity.this, "发现新版本,是否立即更新?", "当前版本V"+versionBean.getVersionName()+"\n"+versionBean.getRemark(), new DialogReturnListner() { @Override public void onResultReturn(String... params) { } @Override public void onResultReturn(int p1, String... params) { } @Override public void onResultReturn(boolean p1, String... params) { if (p1) { if (EasyPermissions.hasPermissions(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE)){ StartUpdate(versionBean); }else { EasyPermissions.requestPermissions(MainActivity.this,"升级程序将App下载到手的过程中需要用到手机文件操作权限,请同意后才能进行正常升级",1234,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE); } } } }); } } catch (Exception e) { } } @Override public void onFailure(HttpException e, String s) { tipDialog.dismiss(); } }); } private void StartUpdate(VersionBean versionBean) { if (versionBean==null){ return; } ToastUtils.showToast(MainActivity.this, "正在更新..."); Intent intent=new Intent(MainActivity.this,ServiceLoadNewVersion.class); intent.putExtra("path",versionBean.getApkPath()); startService(intent); } @Override public void onPermissionsGranted(int requestCode, @NonNull List perms) { if (requestCode==1234){ if (perms.contains(Manifest.permission.READ_EXTERNAL_STORAGE) && perms.contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)){ StartUpdate(versionBean); } } } @Override public void onPermissionsDenied(int requestCode, @NonNull List perms) { ToastUtils.showToast(this,"没有获取相关的权限,无法正常操作!"); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // Forward results to EasyPermissions EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); }}
ServiceLoadNewVersion
package cn.humanetplan.updateappdemo;import android.app.Notification;import android.app.NotificationChannel;import android.app.NotificationManager;import android.app.PendingIntent;import android.app.Service;import android.content.Intent;import android.graphics.BitmapFactory;import android.net.Uri;import android.os.Build;import android.os.Environment;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.support.v4.app.NotificationCompat;import android.support.v4.content.FileProvider;import android.util.Log;import android.widget.RemoteViews;import com.lidroid.xutils.HttpUtils;import com.lidroid.xutils.exception.HttpException;import com.lidroid.xutils.http.HttpHandler;import com.lidroid.xutils.http.RequestParams;import com.lidroid.xutils.http.ResponseInfo;import com.lidroid.xutils.http.callback.RequestCallBack;import java.io.File;/** * 下载 新 版本 的 服务 */public class ServiceLoadNewVersion extends Service { private RemoteViews remoteViews = null; private Notification notification = null; private NotificationManager notificationManager = null; private PendingIntent pReDownLoadIntent = null; private Handler myHandler; // notification id private final int NOTIFICATION_ID = 1000; private final int START = 1001; private final int LOADING = 1002; private final int FINISHED = 1003; private final int LOAD_ERROR = 1004; private String url; private String filePath; String apkName = "updateTest.apk"; String loagPath = ""; public ServiceLoadNewVersion() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { try { String path = intent.getStringExtra("path"); url = path; myHandler = new MyHandler(); initUrls(); initNotification(); // 开始 下载 new DownLoadThread(url, filePath).start(); } catch (Exception e) { } return super.onStartCommand(intent, flags, startId); } /** * 开始 下载 */ private void initUrls() { File file = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) , apkName); if (file.exists()) { file.delete(); } filePath = file.getAbsolutePath(); } NotificationCompat.Builder builder = null; /** * 配置 通知栏显示 样式 */ private void initNotification() { String id = "chanel_update"; String name = "水务集团"; notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //如果是8以上的系统。需要传一个channelId. builder = new NotificationCompat.Builder(this, id); } else { builder = new NotificationCompat.Builder(this); } builder.setContentTitle(getResources().getString(R.string.app_name) + "新版本下载"). setContentText("下载进行中...") .setVibrate(new long[]{0}) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.applogo)) .setSmallIcon(R.mipmap.applogo) .setProgress(100, 0, false); // 下载 失败 重新 下载 的 PendingIntent Intent reDownLoadIntent = new Intent(this, this.getClass()); reDownLoadIntent.putExtra("path", loagPath); pReDownLoadIntent = PendingIntent.getService(this, 200, reDownLoadIntent, PendingIntent.FLAG_CANCEL_CURRENT); remoteViews = new RemoteViews(getPackageName(), R.layout.view_download_notification); remoteViews.setTextViewText(R.id.textViewTitle, "正在下载"); remoteViews.setTextViewText(R.id.textViewProgress, "进度0%"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//android8.0以上通知的适配 NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW); mChannel.enableVibration(false); mChannel.setVibrationPattern(new long[]{0}); notificationManager.createNotificationChannel(mChannel); notification = builder.build(); } else { notification = builder.build(); } } class MyHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case START: notificationManager.notify(NOTIFICATION_ID, notification); break; case LOADING: builder.setProgress(100, msg.arg1, false); builder.setContentText("下载进行中" + msg.arg1 + "/100"); notification = builder.build(); notificationManager.notify(NOTIFICATION_ID, notification); //关键部分,如果你不重新更新通知,进度条是不会更新的 break; case FINISHED: notification.flags |= Notification.FLAG_AUTO_CANCEL;// //关键部分,如果你不重新更新通知,进度条是不会更新的 notificationManager.notify(NOTIFICATION_ID, notification); notificationManager.cancel(NOTIFICATION_ID); installApkNew(null); break; case LOAD_ERROR: builder.setContentTitle("新版本下载失败"); builder.setContentText("下载失败,点击重新下载!"); notification.contentIntent = pReDownLoadIntent;// notification.flags = Notification.FLAG_NO_CLEAR; // 点击通知 不消失 //关键部分,如果你不重新更新通知,进度条是不会更新的 notificationManager.notify(NOTIFICATION_ID, notification); break; } } } //安装apk protected void installApkNew(Uri uri) { try { File file = new File(filePath); 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(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file); //添加这一句表示对目标应用临时授权该Uri所代表的文件 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); } else { Uri mUri = Uri.fromFile(file); mUri = Uri.parse(mUri.toString().replace("content", "file")); intent.setDataAndType(mUri, "application/vnd.android.package-archive"); } startActivity(intent); } catch (Exception e) { e.printStackTrace(); Log.i("ServiceLoadNewVersion", "exc " + e.toString()); } } class DownLoadThread extends Thread { private String url; private String filePath; public DownLoadThread(String url, String filePath) { this.url = url; this.filePath = filePath; } @Override public void run() { HttpUtils http = new HttpUtils(); RequestParams params = new RequestParams(); HttpHandler handler = http.download( url,//url filePath, // 文件保存路径, params, true, // 如果目标文件存在,接着未完成的部分继续下载。服务器不支持RANGE时将从新下载。 false, // 如果从请求返回信息中获取到文件名,下载完成后自动重命名。 new RequestCallBack() { @Override public void onStart() { myHandler.sendEmptyMessage(START); } @Override public void onLoading(long total, long current, boolean isUploading) { Message message = myHandler.obtainMessage(); message.what = LOADING; message.arg1 = (int) ((float) current / (float) total * 100); myHandler.sendMessage(message); } @Override public void onSuccess(ResponseInfo responseInfo) { myHandler.sendEmptyMessage(FINISHED); } @Override public void onFailure(HttpException error, String msg) { myHandler.sendEmptyMessage(LOAD_ERROR); } }); } }}
Manifest
<?xml version="1.0" encoding="utf-8"?>
代码比较简单,没有太多注释。
完整代码: UpdateAppDemo.zip
Github:https://github.com/shouPol/UpdateDemo
更多相关文章
- Android新手入门2016(4)--Android(安卓)SDK下载代理设置
- [置顶] 【通知】▁▂▃ Himi 著作《Android游戏编程之从零开始》
- BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法
- 让最新的 Android(安卓)Q Beta 3 强制重启的 Project Mainline,到
- Android源代码下载过程中无法下载repo的解决方法
- 安卓 android studio 报错 The specified Android(安卓)SDK Buil
- Android学习系列(34)--App应用之发布各广告平台版本
- 基於 Android(安卓)2.3.7 的 CyanogenMod 7.1 正式版推出
- android 自动检测版本升级