一、参考文献

简单实现安卓app自动更新功能 - 简书

安卓app自动更新功能完美实现_白云天的博客-CSDN博客_android 自动更新

Android 实现自动更新及强制更新功能_farley的成长之路-CSDN博客_android开发自动更新

二、主要思路

  1. 查询线上版本号,然后拿本地版本号与之对比。
  2. 若线上版本号比本地版本号大,则下载线上版本号
  3. 把下载好的版本号安装,并替换当前旧版本

三、 服务器端

1、更新的APK 放在服务器端

Android 中tomcat搭建本地服务器 实现apk更新下载_lizhenmingdirk的专栏-CSDN博客

选用 Apache Tomcat 搭建一个本地的服务器,测试用。

Apache Tomcat官方地址:  Apache Tomcat® - Welcome!  我下载的为最新版本:7.0.40

(1)下载

(2)任意解压在磁盘上
     (3)在tomcat的解压文件中,找到bin目录。在bin文件下面找到startup.bat 。双击即可启动

(4)在浏览器中输入:http://localhost:8080/ 出现一个tomcat的介绍页面 ,就表示成功了。

(5)把你需要更新的 apk放入 apache-tomcat-7.0.40\webapps\ROOT 文件夹下,默认访问的文件夹。

(6) 访问:浏览器输入 http://localhost:8080/test.apk  弹出下载界面,那就成功了。实际的远程服务器上则将 http://localhost:8080 更换成实际远程服务器的地址。

注:如果你的服务器使用了shiro 框架,需要进行拦截配置,让apk文件不被拦截。

配置 如下:  /*.apk = anon

2、服务器端设置接口返回APP版本 的更新信息

将APP版本的更新信息保存到数据库的一个数据表中,服务器端获取数据库中APP 版本的更新信息,返回Json数据

返回的Json 如下

{"code":0,
"message":"{\"serverVersion\":142,\"upgradeInfo\":\"1、更新的信息\",\"appname\":\"test\",\"updateUrl\":\"http://服务器端地址/test.apk\",\"updateTime\":\"2021-10-14
15:18:10\",\"id\":2,\"softwareVersion\":\"2.5\"}",
"success":true}

四、Android端

1、权限

需要用到的权限应该有网络权限、本地文件写入权限,本地文件读取权限,请求安装包权限。使用网络权限去获取线上的版本号,然后下载保存到本地,安装的时候再去本地取来。Android 6.0后需要动态申请权限。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

2、获取本地版本号

 /*** 获取本地版本号* @return 版本号*/public static int getLocalVersionCode(Context context){PackageInfo pkg;int versionCode = 0;try {pkg = context.getPackageManager().getPackageInfo(BaseApplication.getInstance().getPackageName(), 0);versionCode = (int) pkg.getLongVersionCode();} catch (PackageManager.NameNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}return versionCode;}

3、获取服务器端版本信息

其中HttpHandle是封装后的API,实际使用OkHttp 进行请求。

 public static void getServerApkUpdateInfo(){HttpHandle.getInstance().getDataAsync(HttpUrl.upgradeUpdateInfo, null, new ProfileModel(), new HttpCallback<ProfileModel>() {@Overridepublic void onFailure(IOException e) {}@Overridepublic void onSuccess(int code, ProfileModel result) {if(result.getCode()==0) {if(result.getMessage()!=null){ApkUpdateModel  apkUpdateModel = JSONObject.parseObject(result.getMessage(),ApkUpdateModel.class);}}}});}

ApkUpdateModel如下

public class ApkUpdateModel {private int id;private String appname; //APP名称private int serverVersion; //服务器端APP版本号private String softwareVersion;  private String updateUrl; //更新APK的下载链接private String upgradeInfo; //更新信息private String updateTime; //更新时间
}

4、APK下载、安装

若本地版本号小于服务器端版号,则弹出对话框,用户选择是否更新。选择更新,则开始下载APK。

这里单独开启了一个后台服务来进行APP 的下载和安装。

(1)APK下载

 private void downApk(String downloadUrl){OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(downloadUrl).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {//下载失败mHandler.sendEmptyMessage(DOWNLOAD_FAIL);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (response.body() == null) {//下载失败mHandler.sendEmptyMessage(DOWNLOAD_FAIL);return;}InputStream is = null;FileOutputStream fos = null;byte[] buff = new byte[2048];int len;try {is = response.body().byteStream();createFile(true);fos = new FileOutputStream(updateFile);long total = response.body().contentLength();//  contentLength=total;long sum = 0;while ((len = is.read(buff)) != -1) {fos.write(buff,0,len);sum+=len;int progress = (int) (sum * 1.0f / total * 100);//下载中,更新下载进度//emitter.onNext(progress);String progressStr = getMsgSpeed(sum,total);Message msg = new Message();msg.what = DOWNLOAD_PROGRESS;msg.obj = progressStr;mHandler.sendMessage(msg);// downloadLength=sum;}fos.flush();//4.下载完成,安装apkmHandler.sendEmptyMessage(DOWNLOAD_COMPLETE);// installApk(TestActivity.this,file);} catch (Exception e) {e.printStackTrace();mHandler.sendEmptyMessage(DOWNLOAD_FAIL);} finally {try {if (is != null)is.close();if (fos != null)fos.close();} catch (Exception e) {e.printStackTrace();}}}});}/*** 创建file文件* @param sd_available    sdcard是否可用*/private void createFile(boolean sd_available) {if (sd_available) {updateDir = new File(Environment.getExternalStorageDirectory(),"app");} else {updateDir = getFilesDir();}updateFile = new File(updateDir.getPath(), appName + ".apk");if (!updateDir.exists()) {updateDir.mkdirs();}if (!updateFile.exists()) {try {updateFile.createNewFile();} catch (IOException e) {e.printStackTrace();}} else {updateFile.delete();try {updateFile.createNewFile();} catch (IOException e) {e.printStackTrace();}}}

(2)APK 安装

Android 如何通过代码安装 APK? - 碎岁语 - 博客园

Android 7.0后需要通过如下步骤安装APK

具体的步骤大致如下:

1、配置 AndroidManifest.xml 中的 ContentProvider 信息;

 <providerandroid:name="android.support.v4.content.FileProvider"android:authorities="com.xxx.xxx.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></provider>

android:name 表示FileProvider 类的完整名称。这个类可以填写两个值,一个是位于 support(android.support.v4.content.FileProvider) 包下的,另一个是位于 androidx(androidx.core.content.FileProvider) 包下的。这两种都可以填写,无区别,主要看你的工程引的是 androidx 支援包还是 support 支援包。

android:authorities表示授权者,这里的格式一般是[appId].fileprovider
android:exported只能为false
android:grantUriPermissions="true"表示授权Uri权限 ,且必须为true

meta-data里设置指定的文件目录,为引用某个xml文件这里引用file_paths

2、配置要开放的 paths 信息;

在工程 res 目录下新建一个 xml 目录,并在该 xml 目录下新建一个 xml 文件。文件的名称必须与第 1 步中 @xml/ 属性值中配置的一致。

xml格式如下,path:需要临时授权访问的路径(.代表所有路径) name:就是你给这个访问路径起个名字。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><root-path name="root" path="" /><files-path name="files" path="" /><cache-path name="cache" path="" /><external-path name="external" path="" /><external-files-path name="name" path="path" /><external-cache-path name="name" path="path" />
</paths>

<root-path/> 代表设备的根目录new File("/");
<files-path/> 代表context.getFilesDir()
<cache-path/> 代表context.getCacheDir()
<external-path/> 代表Environment.getExternalStorageDirectory() 
<external-files-path>代表context.getExternalFilesDirs()
<external-cache-path>代表getExternalCacheDirs()

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path path="app" name="app" /></paths>

上述配置对应路径为Environment.getExternalStorageDirectory() +"/app/"。这个配置的是APK下载后的路径。

3、在 Java 代码中通过 FileProvider 封装文件信息,安装APK

public void installApk(Context context, File file) {if (context == null) {return;}String authority = getApplicationContext().getPackageName() + ".fileprovider";Uri apkUri = FileProvider.getUriForFile(context, authority, file);Intent intent = new Intent(Intent.ACTION_VIEW);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//判读版本是否在7.0以上if (Build.VERSION.SDK_INT >= 24) {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);//弹出安装窗口把原程序关闭。//避免安装完毕点击打开时没反应/* Process.killProcess(android.os.Process.myPid());*/}

(3)服务

服务UpdateService.class

public class UpdateService extends Service {// BT字节参考量private static final float SIZE_BT = 1024L;// KB字节参考量private static final float SIZE_KB = SIZE_BT * 1024.0f;// MB字节参考量private static final float SIZE_MB = SIZE_KB * 1024.0f;private final static int DOWNLOAD_START = 0;// 完成private final static int DOWNLOAD_COMPLETE = 1;// 完成private final static int DOWNLOAD_NOMEMORY = -1;// 内存异常private final static int DOWNLOAD_FAIL = -2;// 失败private final static int DOWNLOAD_PROGRESS = 100;// 失败private final static int NotificationID = 2;// 失败private String appName = null;// 应用名字private String appUrl = null;// 应用升级地址private File updateDir = null;// 文件目录private File updateFile = null;// 升级文件// 通知栏private NotificationManager updateNotificationManager = null;private Notification updateNotification = null;private NotificationCompat.Builder builder;private Intent updateIntent = null;// 下载完成private PendingIntent updatePendingIntent = null;// 在下载的时候private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what){case DOWNLOAD_COMPLETE:builder.setContentText(appName +  getString(R.string.upgrade_download_finished));updateNotificationManager.notify(NotificationID,builder.build());if(updateFile!=null){installApk(getApplicationContext(),updateFile);updateNotificationManager.cancel(NotificationID);}stopSelf();Process.killProcess(android.os.Process.myPid());break;case DOWNLOAD_FAIL:builder.setContentText(appName +  getString(R.string.upgrade_download_failed));updateNotificationManager.notify(NotificationID,builder.build());stopSelf();break;case DOWNLOAD_NOMEMORY:stopSelf();break;case DOWNLOAD_PROGRESS:String progressShow =(String) msg.obj;builder.setContentTitle(appName+getString(R.string.upgrade_downloading));builder.setContentText(progressShow);updateNotificationManager.notify(NotificationID,builder.build());break;case DOWNLOAD_START:Toast.makeText(getApplicationContext(),getString(R.string.upgrade_download_start),Toast.LENGTH_SHORT).show();break;}}};public UpdateService() {}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.return null;// throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic void onCreate() {super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {appName = intent.getStringExtra("appname");appUrl = intent.getStringExtra("appurl");updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);String id = "my_channel_01";if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  //Android 8.0以上NotificationChannel mChannel = new NotificationChannel(id, appName, NotificationManager.IMPORTANCE_LOW);Log.i("DownAPKService", mChannel.toString());updateNotificationManager.createNotificationChannel(mChannel);builder = new NotificationCompat.Builder(getApplicationContext());builder.setSmallIcon(R.drawable.ic_launcher);builder.setTicker(getString(R.string.upgrade_downloading));builder.setContentTitle(appName);builder.setContentText("0MB (0%)");builder.setNumber(0);builder.setChannelId(id);builder.setAutoCancel(true);} else {    //Android 8.0以下builder = new NotificationCompat.Builder(getApplicationContext());builder.setSmallIcon(R.drawable.ic_launcher);builder.setTicker(getString(R.string.upgrade_downloading));builder.setContentTitle(appName);builder.setContentText("0MB (0%)");builder.setNumber(0);builder.setAutoCancel(true);}updateNotificationManager.notify(NotificationID, builder.build());//        new UpdateThread().execute();new DownloadThread().start();return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();stopSelf();}class DownloadThread extends Thread{@Overridepublic void run() {super.run();mHandler.sendEmptyMessage(DOWNLOAD_START);downApk(appUrl);}}private void downApk(String downloadUrl){OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(downloadUrl).build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {//下载失败mHandler.sendEmptyMessage(DOWNLOAD_FAIL);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (response.body() == null) {//下载失败mHandler.sendEmptyMessage(DOWNLOAD_FAIL);return;}InputStream is = null;FileOutputStream fos = null;byte[] buff = new byte[2048];int len;try {is = response.body().byteStream();createFile(true);fos = new FileOutputStream(updateFile);long total = response.body().contentLength();//  contentLength=total;long sum = 0;while ((len = is.read(buff)) != -1) {fos.write(buff,0,len);sum+=len;int progress = (int) (sum * 1.0f / total * 100);//下载中,更新下载进度//emitter.onNext(progress);String progressStr = getMsgSpeed(sum,total);Message msg = new Message();msg.what = DOWNLOAD_PROGRESS;msg.obj = progressStr;mHandler.sendMessage(msg);// downloadLength=sum;}fos.flush();//4.下载完成,安装apkmHandler.sendEmptyMessage(DOWNLOAD_COMPLETE);// installApk(TestActivity.this,file);} catch (Exception e) {e.printStackTrace();mHandler.sendEmptyMessage(DOWNLOAD_FAIL);} finally {try {if (is != null)is.close();if (fos != null)fos.close();} catch (Exception e) {e.printStackTrace();}}}});}public void installApk(Context context, File file) {if (context == null) {return;}String authority = getApplicationContext().getPackageName() + ".fileprovider";Uri apkUri = FileProvider.getUriForFile(context, authority, file);Intent intent = new Intent(Intent.ACTION_VIEW);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//判读版本是否在7.0以上if (Build.VERSION.SDK_INT >= 24) {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);//弹出安装窗口把原程序关闭。//避免安装完毕点击打开时没反应/* Process.killProcess(android.os.Process.myPid());*/}/*** 获取下载进度* @param downSize* @param allSize* @return*/public static String getMsgSpeed(long downSize, long allSize) {StringBuffer sBuf = new StringBuffer();sBuf.append(getSize(downSize));sBuf.append("/");sBuf.append(getSize(allSize));sBuf.append(" ");sBuf.append(getPercentSize(downSize, allSize));return sBuf.toString();}/*** 获取大小* @param size* @return*/public static String getSize(long size) {if (size >= 0 && size < SIZE_BT) {return (double) (Math.round(size * 10) / 10.0) + "B";} else if (size >= SIZE_BT && size < SIZE_KB) {return (double) (Math.round((size / SIZE_BT) * 10) / 10.0) + "KB";} else if (size >= SIZE_KB && size < SIZE_MB) {return (double) (Math.round((size / SIZE_KB) * 10) / 10.0) + "MB";}return "";}/*** 获取到当前的下载百分比* @param downSize   下载大小* @param allSize    总共大小* @return*/public static String getPercentSize(long downSize, long allSize) {String percent = (allSize == 0 ? "0.0" : new DecimalFormat("0.0").format((double) downSize / (double) allSize * 100));return "(" + percent + "%)";}private File createFile() {String root = Environment.getExternalStorageDirectory().getPath();File file = new File(root,appName+".apk");if (file.exists())file.delete();try {file.createNewFile();updateFile = file;return file;} catch (IOException e) {e.printStackTrace();}return null ;}/*** 创建file文件* @param sd_available    sdcard是否可用*/private void createFile(boolean sd_available) {if (sd_available) {updateDir = new File(Environment.getExternalStorageDirectory(),"app");} else {updateDir = getFilesDir();}updateFile = new File(updateDir.getPath(), appName + ".apk");if (!updateDir.exists()) {updateDir.mkdirs();}if (!updateFile.exists()) {try {updateFile.createNewFile();} catch (IOException e) {e.printStackTrace();}} else {updateFile.delete();try {updateFile.createNewFile();} catch (IOException e) {e.printStackTrace();}}}
}

开启服务

  private void startUpdateService(Context context,String appname,String appurl){Intent mIntent = new Intent(context, UpdateService.class);mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//传递数据mIntent.putExtra("appname", appname);mIntent.putExtra("appurl", appurl);context.startService(mIntent);enterSplashActivity();}

5、出现过的问题

(1) android.os.FileUriExposedException: file:///storage/emulated/0/app/test.apk exposed beyond app through Intent.getData()

由于代码中配置的FileProvider 与AndroidManifest.xml内配置的FileProvider 不一致

(2)java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/app/test.apk

由于file_paths.xml内的path 配置不正确导致。

(3)Apk覆盖安装的时候,出现安装失败,与旧版本部兼容的问题

​​​​​​Android 解决apk覆盖安装的时候,出现安装失败,与旧版本部兼容的问题_Afanbaby的博客-CSDN博客

解决方案:

1.你需要检查你的新旧apk所使用的签名文件是否是同一个。

2.检查你的签名文件是否是发布版本

安卓APP自动更新实现相关推荐

  1. 安卓APP自动更新功能实现

    安卓APP自动更新功能实现 前言 代码实现 前言 安卓App自动更新基本上是每个App都需要具备的功能,接下来介绍一下实现自动更新的步骤. 代码实现 App自动更新主要分为新版本检测.升级弹窗.下载升 ...

  2. Android如何实现APP自动更新

    先来看看要实现的效果图: 对于安卓用户来说,手机应用市场说满天飞可是一点都不夸张,比如小米,魅族,百度,360,机锋,应用宝等等,当我们想上线一款新版本APP时,先不说渠道打包的麻烦,单纯指上传APP ...

  3. web app升级—带进度条的App自动更新

    带进度条的App自动更新,效果如下图所示:   技术:vue.vant-ui.5+ 封装独立组件AppProgress.vue: <template><div><van- ...

  4. Android App自动更新解决方案(DownloadManager)

    Android App自动更新解决方案(DownloadManager) 参考文章: (1)Android App自动更新解决方案(DownloadManager) (2)https://www.cn ...

  5. android通知栏应用程序更新,Android App自动更新之通知栏下载

    本文实例为大家分享了Android App自动更新通知栏下载的具体代码,供大家参考,具体内容如下 版本更新说明 这里有调用UpdateService启动服务检查下载安装包等 1. 文件下载,下完后写入 ...

  6. flutter APP自动更新

    flutter APP自动更新 前言 在pubspec.yaml中安装依赖 在main.dart文件中,初始化FlutterDownLoader 配置网络 在AndroidManifest.xml新增 ...

  7. Android APP 自动更新实现(适用Android9.0)

    Android App自动更新基本上是每个App都需具备的功能,参考网上各种资料,自己整理了下,先来看看大致的界面: 一.实现思路: 1.发布Android App时,都会生成output-metad ...

  8. android app 自动更新,app升级项目,新增强制更新(可静默),支持热更新(wgt),可支持高版本安卓系统...

    pure-updater 一个可以用的自动更新方案 经测试可支持 Android 9.0 已支持热更新 已支持静默的强制更新 如果您觉得还可以的话那就点个五星吧!谢谢! 已测试 android 8.0 ...

  9. iphone微信美颜插件_iPhone、安卓微信自动更新,又有新功能?

    2019年的第一个工作日,要继续奋斗啦,我们都在努力奔跑,我们都是追梦人! 12月21日微信发布了全新的7.0版本,现在 iOS 和安卓都可以更新了. 有人夸"好看",有人吐槽&q ...

最新文章

  1. python mysqldb cursor_python中MySQLdb模块用法实例
  2. jfreechart 时序图 ,生成图表
  3. linux c 子线程sleep,linux c之sleep的多种实现
  4. 判断表达式值是否为空_如何在 Python 中判断列表是否为空
  5. torch.stack作用分析
  6. 如何制作一个塔防游戏 Cocos2d x 2 0 4
  7. python有道云笔记_你很需要的,一个一键导出「有道云笔记」所有笔记的功能
  8. php酒店管理论文,酒店管理毕业论文
  9. 安装mysql报curses的解决
  10. 《Android框架揭秘》——2.5节应用程序Framework源码级别调试
  11. pycharm专业版的破解
  12. 计算机配件仓库照片,配件仓库存管理技巧
  13. 如何用rose画出展示对象流的活动图
  14. 码出一个高颜值原生折线图(新增柱状图、环形图)
  15. R语言绘制等值线和等高线
  16. 攻击JavaWeb应用————2、CS交互安全
  17. 工具类APP的生存之道
  18. html span title属性,html – -Element:aria-label或title属性
  19. 不要迷恋哥本哈根达斯
  20. 直接高效:pycharm报错:cannot save setting :please specify a different sdk name

热门文章

  1. STM32标准库及的Keil软件包下载
  2. 分布式缓存 - memCached Voldemort
  3. 软破ps3安装linux,软破PS3安装大于4GB的PKG文件详细教程
  4. 使用showdown将markdown笔记插入到HTML网页
  5. 三星手机「我的文件」应用闪退问题的解决方法
  6. 微信公众号js接口安全域名的MP_verify_*.txt文件的放置路径
  7. python追加_python追加写
  8. 聪明人用方格笔记本-读书笔记
  9. 使用MIPS完成汇编程序——选择排序实现
  10. as it exceeds the max of 500KB._It#39;s a date的一语双关:它不仅仅表示“约会”