功能演示

Android App自动更新基本上是每个App都需具备的功能。网上有各种更新方式,但基本都是往年的了,最近刚查资料写完一个app更新功能,发现了许多app更新的博客内没有详细说明的小问题,这里整理了一下。先看看演示图片。

实现步骤

具体分为两个方面,一个是对项目app进行打包生成.apk文件,二是代码实现对应用版本的判断以及对新版本的.apk文件下载。

1、对于项目打包网络上已经有很全面的文章了,这里就不复述了。具体需要注意一下签名文件要统一,现在使用的app版本和未来将要发布的版本一定得使用同一个签名文件进行打包,否则下载新的版本将不能覆盖掉旧版本,导致更新失败。还有打包的项目中build.gradle配置要写正确,特别是versionCode记得每次增加。

2、 对于代码实现方面网络上也有,但这里主要讲一个可靠的流程以及其中含有的其他教程包含不全面的小问题,主要有对于sdk>29情况下存储路径的获取、安装软件权限等。

具体方法

1、声明权限

由于自动更新需要访问网络,下载更新包,操作安装包操作,所以需要申明以下权限:

    <!-- 网络权限 --><uses-permission android:name="android.permission.INTERNET" />
<!--    读写设计存储权限--><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!--添加REQUEST_INSTALL_PACKAGES权限-->
<!--    必须加这个权限,不然会导致读条结束后安装包不启动--><uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

这个案例并没有用到危险权限,所以权限全部在AndroidManifest.xml声明静态权限即可。

需要注意

(1)对于安装包在https和http网站下时需要进行不同的配置时app允许访问

(2)在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException,所以需要用到FileProvider。

具体操作

在AndroidManifest.xml中的application标签内声明

<!--        FileProvider的使用--><providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/filepaths"/></provider>

在res目录下创建xml/filepaths.xml

<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-path name="external" path="."/>
</paths>

(3)因为android9以后对外部权限的改动,不能使用getExternalStorageDirectory()去获取路径,否则会提示not found,所以需要用到getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS),即需要在AndroidManifest.xml中的application标签中进行配置

android:requestLegacyExternalStorage="true"

2、更新界面制作

感谢界面一般有一个进度条即可,在layout目录下创建progress.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><ProgressBarandroid:id="@+id/progress"style="?android:attr/progressBarStyleHorizontal"android:layout_width="fill_parent"android:layout_height="wrap_content" /></LinearLayout>

3、开始编写核心代码

编写一个工具类UpdateManager.java

这里工具类只是单纯的实现了我们需要的功能,一些突发状况这里并没有进行处理,小伙伴们学会了基础后可以自己添砖加瓦。代码参考自这个大佬


//软件升级模块import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;import androidx.core.content.FileProvider;import com.huang.uifinaldesign.register.R;import org.json.JSONException;
import org.json.JSONObject;import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;public class UpdateManager {// 应用程序Contextprivate Context mContext;// 是否是最新的应用,默认为falseprivate boolean isNew = false;private boolean intercept = false;// 下载安装包的网络路径private String apkUrl = "https://***/***/update/"+ "GenerateAPK_release_v1.0.apk";// 保存APK的文件夹private static String savePath;//apk文件的绝对路径private static String saveFileName ;// 下载线程private Thread downLoadThread;private int progress;// 当前进度TextView text;// 进度条与通知UI刷新的handler和msg常量private ProgressBar mProgress;private static final int DOWN_UPDATE = 1;private static final int DOWN_OVER = 2;public UpdateManager(Context context) {mContext = context;//获取存储路径savePath = String.valueOf(mContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS))+"/";
//这里是一个易出问题点,android9以后对外部权限的改动,不能使用getExternalStorageDirectory()去获取路径,否则会提示not found //要在AndroidManifest.xml中的application标签中声明android:requestLegacyExternalStorage="true"}/*** 检查是否更新的内容*///获取应用的版本号versionCode//对于versionCode新版本和老版本一定是不一样的,这个详细见打包项目成.apk部分public static int getVersionCode(Context context) {PackageManager manager = context.getPackageManager();int code = 0;try {PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);code = info.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return code;}public void checkVersion() {try {//下载网站的json数据包,根据数据包的版本来判断当前版本是否需要被替换//这里的StreamUtil().getjson()是我自己写的一个工具类,里面的getjson()方法能获取给定uri的json文件,小伙伴们可以自行搜索或者自力更生写一个工具类String json = new StreamUtil().getjson("https://***/***/update/"+ "output-metadata.json");JSONObject jo = new JSONObject(json);int versionCode = jo.getInt("versionCode");//获取版本代号Log.i("本软件版本号", Integer.toString(UpdateManager.getVersionCode(mContext)));Log.i("服务器软件版本号", Integer.toString(versionCode));if (UpdateManager.getVersionCode(mContext) < versionCode) {//本地版本小于服务器版本,存在新版本isNew = false;} else {isNew = true;}checkUpdateInfo();//判断isNew,检测是否需要更新操作} catch (JSONException e) {e.printStackTrace();}}public void checkUpdateInfo() {if (isNew) {return;} else {showUpdateDialog();}}/*** 显示更新程序对话框,供主程序调用*/private void showUpdateDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(mContext);builder.setTitle("软件版本更新");builder.setMessage("有最新的软件包,是否下载!");builder.setPositiveButton("下载", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {showDownloadDialog();}});builder.setNegativeButton("以后再说",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();//弹出的对话框销毁}});builder.create().show();}/*** 显示下载进度的对话框*/private void showDownloadDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(mContext);builder.setTitle("软件版本更新");LayoutInflater inflater = LayoutInflater.from(mContext);View v = inflater.inflate(R.layout.progress, null);mProgress = (ProgressBar) v.findViewById(R.id.progress);builder.setView(v);builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {intercept = true;//取消下载的标志位}});builder.show();downloadApk();}/*** 从服务器下载APK安装包*/private void downloadApk() {downLoadThread = new Thread(mdownApkRunnable);downLoadThread.start();}//下载安装包的进程,同步更新安装包的下载进度,以及下载完成后的通知安装private Runnable mdownApkRunnable = new Runnable() {@Overridepublic void run() {URL url;try {//对sd卡进行状态的判断,如果相等的话表示当前的sdcard挂载在手机上并且是可用的if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {url = new URL(apkUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.connect();int length = conn.getContentLength();InputStream ins = conn.getInputStream();//创建安装包所在的前置文件夹File file = new File(savePath);if (!file.exists()) {file.mkdir();}//创建安装包的文件,安装包将下载入这个文件夹中long time = System.currentTimeMillis();//当前时间的毫秒数saveFileName = savePath+time+"_ARKUpdate.apk";File apkFile = new File(saveFileName);//创建该文件夹的输入流FileOutputStream fos = new FileOutputStream(apkFile);int count = 0;byte[] buf = new byte[1024];while (!intercept) {//对取消下载的标志位进行判断,如果一直没有被打断即没有点击取消下载按钮则继续下载int numread = ins.read(buf);//返回读入的字节个数并将读到的字节内容放入buf中count += numread;progress = (int) (((float) count / length) * 100);//当前进度,用来更新progressBar的进度// 通知主线程更新下载进度mHandler.sendEmptyMessage(DOWN_UPDATE);if (numread <= 0) {// 下载完成通知安装mHandler.sendEmptyMessage(DOWN_OVER);break;}//已经全部读入,不需要再读入字节为-1的内容fos.write(buf, 0, numread);//从fos中写入读出的字节个数到buf中}fos.close();ins.close();}elsereturn;} catch (Exception e) {e.printStackTrace();}}};/*** 安装APK内容,这些代码主要是对文件进行操作,需要理解文件的读写,但是不了解也没关系* 一般安装apk以及下载apk部分短时间内不会有什么变化,需要用的时候改改路径复制粘贴就行*///在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException。所以这里用到了FIleProvider,具体步骤上面已经给出private void installAPK() {//这里给出了一个不使用FIleProvider即更新失败的//        File apkFile = new File(saveFileName);
//        if (!apkFile.exists()) {//不存在即没有下载则返回
//            return;
//        }
//        Intent intent = new Intent(Intent.ACTION_VIEW);
//        intent.setDataAndType(Uri.parse("file://" + apkFile.toString()),
//                "application/vnd.android.package-archive");//根据apk路径自动安装apk
//        mContext.startActivity(intent);//        打开apk文件的格式
//        Intent intent = new Intent();
//        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//        intent.setAction(android.content.Intent.ACTION_VIEW);
//        intent.setDataAndType(uri,"application/vnd.android.package-archive");Intent intent = new Intent(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//设置启动模式,四种之一File apkFile = new File(saveFileName);Uri uri;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// 前面代表的是当前操作系统的版本号,后面是给定的版本号,Ctrl鼠标放置显示版本号intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//记得修改com.xxx.fileprovider与androidmanifest相同// 获取的是应用唯一区分的id即applicationIduri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".fileprovider", apkFile);intent.setDataAndType(uri, "application/vnd.android.package-archive");// 打开apk文件} else {uri = Uri.parse("file://" + apkFile.toString());}intent.setDataAndType(uri, "application/vnd.android.package-archive");mContext.startActivity(intent);};//消息通知部分  private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {switch (msg.what) {case DOWN_UPDATE:mProgress.setProgress(progress);break;case DOWN_OVER:installAPK();break;default:break;}}};}

写完工具类后就只需要在需要使用的地方进行方法的调用了,一般都是在app启动界面进行调用。调用的是工具类中的checkVersion()方法,即可实现更新功能了

这里还要对工具类中可能出现的问题再单独进行复试一次

1、在对版本号进行比对判断时可能出错,但错误大概都是打包安装包时在build.gradle中没有对新版本进行versionCode的修改,导致新旧版本的versionCode一样

2、对于安装包保存在手机中的目录其实自己写的案例或者学习的话安装包放哪都差不多,但是如果需要放在看起来比较正规(我觉得)那就要用到获取内部存储的方法了,android9以后对外部权限的改动,不能使用getExternalStorageDirectory()去获取存储路径,否则会提示not found。需要用到getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)获取,这个方法根据参数不同获取的路径也会不同,可以自行搜索教程。注意获取的路径最后没有'/',记得后面接安装包名称时补上。

3、对于权限方面还一个易错点就是下载安装包进度条结束后不弹出安装界面,这样一般都是没有添加安装软件的权限(虽然我上面已经写了要添加这个权限了)

其他比较容易出问题的就是签名文件和打包方面build.gradle中配置问题了。

如果有什么问题可以评论留言

android app实现更新功能相关推荐

  1. Android 程序自动更新功能模块实现

    2019独角兽企业重金招聘Python工程师标准>>> Android 程序自动更新功能模块实现 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程 ...

  2. android app 自动更新,AndroidUpdateDemo

    Android课程-App更新策略 @(Android) 第一节 课程介绍 概述 App更新是应用当中很常见的一个功能,基本上联网的app都应该具备这样的功能,对于更新迭代比较快速的产品,应用更新升级 ...

  3. android自动更新demo,Android程序自动更新功能模块的实现方法【附完整demo源码下载】...

    本文实例讲述了Android程序自动更新功能模块的实现方法.分享给大家供大家参考,具体如下: 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程序启动的时候首先调用更 ...

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

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

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

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

  6. Android应用自动更新功能实现使用AsyncTask!

    为什么80%的码农都做不了架构师?>>>    我所开发应用不是面向大众的应用,所以无法放到应用市场去让大家下载,然后通过应用市场更新.所以我必要做一个应用自动更新功能.但是不难,T ...

  7. android n 支持机型,Android N无缝更新功能不适于现有机型

    原标题:Android N无缝更新功能不适于现有机型 [天极网手机频道]谷歌在I/O大会中宣布AndroidN引入了Chrome OS中的"无缝更新"功能,该功能允许用户下载系统更 ...

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

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

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

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

最新文章

  1. Word 2007中隐藏段落和格式标记
  2. jquery对json 键值对或数组的增加、删除、遍历操作
  3. 很高兴加入 英文_XR车载公司Holoride加入高通XR计划
  4. Android脚本打包
  5. redis的zset的底层实现_Redis中hash、set、zset有多牛?从底层告诉你数据结构原理...
  6. maven如何实现创建带源代码的jar包
  7. Web API-DOM-滚动事件、加载事件和元素大小和位置
  8. C#+OpenGL+FreeType显示3D文字(3) - 用PointSprite绘制文字
  9. 上海行政区划经纬度地图_全国行政区划2017省市区完整版附经纬度及拼音
  10. Qt调用谷歌翻译API
  11. 【转】中华吸血鬼分析
  12. mesh和wifi中继的区别_科普:路由器的无线中继和Mesh的区别是什么?
  13. 字节跳动测开发实习面试
  14. 关于英语写作和阅读的学习——施一公教授的两篇博文
  15. 中继 并发量_潮流UCM6102 IP PBX 支持500门分机 双外线 30个并发数
  16. Java学习day08--方法引用和Stream流
  17. PaMIR: Parametric Model-Conditioned Implicit Representation for Image-based Human Reconstruction
  18. JSONObject转对象
  19. Java 基础 | Java 中引用与指针的关系
  20. 《十二》CSS3 Grid 网格布局

热门文章

  1. 彻底解决快播不可点播方法
  2. ssm Java试题库管理系统 maven
  3. php 处理大数组,如何在PHP中处理非常大的数组?
  4. kicad 输出Gerber文件
  5. 如何遍历一棵二叉树?
  6. IE浏览器打印预览只打印第一页
  7. 对话系统最新进展-17篇EMNLP 2021论文
  8. GEOTRANS 3.7 用户使用手册 ------ 文件处理(坐标文件格式)
  9. Python教程(十)
  10. Python爬虫入门教程:博客园首页推荐博客排行的秘密