Android 实现APP内应用更新功能(支持Android7.0以上)

简介: Android 实现APP内应用更新功能(支持Android7.0以上)

前言:

实现APP内应用更新功能思路,这里我给大家具体说明以下。

思路:

  1. 通过API接口获取服务器端版本号,与本地应用版本号作比较。
  2. 如果本地版本号小于服务器端版本号,则弹出一个对话框,提示用户更新APP,用户可以点击更新按钮,在APP内直接更新,安装。用户也可以选择去应用商店更新。
  3. 如果用户点击更新按钮,通过给这个按钮设置监听事件,开启Service服务,在后台服务中下载apk。
  4. 在Service服务中,通过上一个Activity传来的值,获取到最新APK的下载地址,通过IO流的知识,把最新APK下载到手机SD卡的指定路径。
  5. 最后通过调用系统安装界面,实现APK的安装。

接下来我们通过代码实现以上这些步骤

版本号对比,设置对话框,开启服务

//获取本地版本号
            String vername = APKVersionCodeUtils.getVerName(this);
            //获取服务端版本号大小
            final AppUpadeLog newapplog = new Gson().fromJson(resp, AppUpadeLog.class);
            //判断版本号的大小,大于网络上的版本号不提示更新
            int verflag = compareVersion(vername, newapplog.AppVer);
            if (verflag == -1) {
                //对话框弹出
                //弹出提醒对话框,提示用户登录成功
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("更新提示");
                builder.setMessage("应用已有新版本发布,请前往应用商城进行更新,或者直接点击更新按钮,进行更新!");
                builder.setPositiveButton("更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //启动后台服务,下载APK
                        Intent intent = new Intent(MainActivity.this, UpdateService.class);
                        intent.putExtra("FileSrc", newapplog.FileSrc);
                        intent.putExtra("AppName", newapplog.AppName);
                        startService(intent);
                    }
                });
                builder.setNeutralButton("下次提醒", null);
                AlertDialog alert = builder.create();
                alert.show();

下载APK,设置通知栏,安装APK

public class UpdateService extends Service {
    private static final String TAG = "UpdateService";
    private String appUrl; //应用升级地址
    private String appName; //应用名称
    private String updateFile; //最新APK的路径
    private NotificationManager notificationManager; //声明系统的通知管理器
    //定义notification实用的ID
    private static final String MESSAGES_CHANNEL = "messages";
    private HttpURLConnection connection;
    private NotificationCompat.Builder builder;
    private final int NEW_MESSAGE_ID = 0;
    private static int HANDLER_LABEL = 0X00; //设置一个常量值来标记信息
    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            if (msg.what == HANDLER_LABEL) {
                if (!TextUtils.isEmpty(updateFile)) {
                    installAPK();
                }
            }
            return false;
        }
    });
    //安装
    private void installAPK() {
        try {
            File cacheDir = new File(getApplicationContext().getExternalFilesDir(null).getPath());
            File file = new File(cacheDir, appName + ".apk");
            //使用隐式意图开启安装APK的Activity
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //Android7.0 API24之后获取uri要用contentProvider
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                Uri apkUri = FileProvider.getUriForFile(this,
                        "com.run.xiao.FileProvider", file);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
                Log.e(TAG, "installAPK: " + apkUri);
            } else {
                Uri apkUri = Uri.parse("file://" + updateFile);
                intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
            }
            startActivity(intent);
            notificationManager.cancel(0); //出现安装APK页面,取消通知
            //结束服务
            stopSelf();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //获取上一个页面传来的包裹
        Bundle bundle = intent.getExtras();
        appUrl = bundle.getString("FileSrc");
        appName = bundle.getString("AppName");
        downloadUpdateFile(appUrl);
        return super.onStartCommand(intent, flags, startId);
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    /**
     * 下载更新APK文件
     *
     * @param appUrl
     * @return
     */
    private int downloadUpdateFile(final String appUrl) {
        try {
            //创建通知渠道
            createMessageNotificationChannel();
            final int NEW_MESSAGE_ID = 0;
            builder = new NotificationCompat.Builder(this, MESSAGES_CHANNEL);
            builder.setSmallIcon(R.mipmap.lisen_lancher)  //小图标
                    .setContentTitle("正在下载")         //标题
                    .setContentText("正在更新APP")           //描述性文本
                    .setAutoCancel(true)            //点击通知后关闭通知
                    .setOnlyAlertOnce(true);         //设置提示音只响一次
            //StrictMode修改默认的策略
            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
            //设置进度条操作
            URL url = new URL(appUrl);
            //打开和URL之间的连接
            connection = (HttpURLConnection) url.openConnection();
            //设置网络请求
            connection.setRequestMethod("GET");
            //开始读取服务器端数据,到了指定时间还没有读到数据,则报超时异常
            connection.setReadTimeout(50000);
            //要取得长度,要求http请求不要gzip压缩
            connection.setRequestProperty("Accept-Encoding", "identity"); // 添加这行代码
            //建立实际的连接
            connection.connect();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int total_length = 0;
                    BufferedOutputStream bos = null;
                    BufferedInputStream bis = null;
                    try {
                        if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                            InputStream is = connection.getInputStream();
                            bis = new BufferedInputStream(is);
//                            File cacheDir = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
                            //创建文件路径
                            File cacheDir = new File(getApplicationContext().getExternalFilesDir(null).getPath());
                            if (!cacheDir.exists()) {
                                cacheDir.mkdirs();
                            }
                            //创建文件夹
                            //通过输出流,下载到指定的本地文件目录
                            File file = new File(cacheDir, appName + ".apk");
                            if (!file.exists()) {
                                file.createNewFile();
                            }
                            //检查SD卡的状态是否可用
                            if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                                Toast.makeText(UpdateService.this, "SD卡不可用~", Toast.LENGTH_SHORT).show();
                            }
                            FileOutputStream fos = new FileOutputStream(file);
                            bos = new BufferedOutputStream(fos);
                            //获取文件流大小,更新进度
                            byte[] buffer = new byte[1024 * 8];
                            int len;
                            int pro1 = 0;
                            long file_length = connection.getContentLength();
                            while ((len = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, len);
                                total_length += len;
                                if (file_length > 0) {
                                    pro1 = (int) ((total_length / (float) file_length) * 100);//进度条传递进度
                                    builder.setProgress(100, pro1, false);
                                    builder.setContentText("下载" + pro1 + "%");
                                    notificationManager.notify(NEW_MESSAGE_ID, builder.build());
                                }
                            }
                            builder.setStyle(new NotificationCompat.BigTextStyle().bigText("下载完成,点击安装")); //显示多行文本
                            notificationManager.notify(NEW_MESSAGE_ID, builder.build());
                            updateFile = file.getAbsolutePath();
                            Log.e(TAG, "下载路径:" + updateFile);
                            handler.sendEmptyMessage(HANDLER_LABEL);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //关闭资源,先关闭外层流,在关闭内层流
                        try {
                            if (bis != null) {
                                bis.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        try {
                            if (bos != null) {
                                bos.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return NEW_MESSAGE_ID;
    }
    //创建通知渠道
    private void createMessageNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = this.getString(R.string.app_name);
            NotificationChannel channel = new NotificationChannel(
                    MESSAGES_CHANNEL,
                    name,
                    NotificationManager.IMPORTANCE_HIGH
            );
            notificationManager = this.getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

下载完成之后,不需要用户点击通知栏安装,因为当下载完成后,会发送一条消息给handler,再由handler,调用系统安装界面,进行最新APK的安装。

在清单文件AndroidManifest.xml中注册服务,解决一个应用提供自身文件给其它应用使用时,如果给出一个file://格式的URI的话,应用会抛出FileUriExposedException

<service android:name=".service.UpdateService"
            android:enabled="true"/>
        <!-- 解决Android 7.0 以上报错FileUriExposedException -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.soundproject.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

android:resource="@xml/file_paths" 这个资源是在res目录下,创建xml包,之后创建file_paths.xml

file_paths.xml代码如下:

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

这样就完成了APP内应用更新功能~ 如果有不当之处,可以在评论区指出,一起进步。


目录
相关文章
|
8月前
|
移动开发 小程序 Android开发
基于 uni-app 开发的废品回收类多端应用功能与界面说明
本文将对一款基于 uni-app 开发的废品回收类多端应用,从多端支持范围、核心功能模块及部分界面展示进行客观说明,相关资源信息也将一并呈现。
263 0
|
存储 Android开发
如何查看Flutter应用在Android设备上已被撤销的权限?
如何查看Flutter应用在Android设备上已被撤销的权限?
686 64
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
544 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
12月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
238 0
|
10月前
|
Java Shell Maven
【Azure Container App】构建Java应用镜像时候遇无法编译错误:ERROR [build 10/10] RUN ./mvnw.cmd dependency:go-offline -B -Dproduction package
在部署Java应用到Azure Container App时,构建镜像过程中出现错误:“./mvnw.cmd: No such file or directory”。尽管项目根目录包含mvnw和mvnw.cmd文件,但依然报错。问题出现在Dockerfile构建阶段执行`./mvnw dependency:go-offline`命令时,系统提示找不到可执行文件。经过排查,确认是mvnw文件内容异常所致。最终通过重新生成mvnw文件解决该问题,镜像成功构建。
556 1
|
10月前
|
存储 Android开发 数据安全/隐私保护
Thanox安卓系统增加工具下载,管理、阻止、限制后台每个APP运行情况
Thanox是一款Android系统管理工具,专注于权限、后台启动及运行管理。支持应用冻结、系统优化、UI自定义和模块管理,基于Xposed框架开发,安全可靠且开源免费,兼容Android 6.0及以上版本。
1214 4
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
559 11
移动端数据抓取:Android App的TLS流量解密方案
|
12月前
|
搜索推荐 API UED
淘宝/天猫获得淘宝app商品详情原数据 API 返回值的应用
该API专注于商品信息整合与展示,提供基础信息抓取、多媒体内容整合等功能,助力实时同步商品数据,构建丰富的详情页。同时支持数据分析与市场洞察,包括销售趋势分析和竞品对比,优化库存与定价策略。此外,动态促销管理和个性化推荐系统可提升营销效果,而实时库存预警和评价数据可视化则显著增强用户体验,为用户决策提供透明依据,全面提升平台竞争力与用户满意度。
|
10月前
|
存储 移动开发 监控
App Trace功能实战:一键拉起、快速安装与免提写邀请码的应用实践
App Trace系统通过一键拉起、快速安装和免提写邀请码三大功能,显著提升用户转化率、安装成功率和邀请注册率。结合深度技术实现与优化,助力公司用户增长,成为移动端核心基础设施。
|
小程序
【04】微信支付商户申请下户到配置完整流程-微信开放平台移动APP应用通过-微信商户继续申请-微信开户函-视频声明-以及对公打款验证-申请+配置完整流程-优雅草卓伊凡
【04】微信支付商户申请下户到配置完整流程-微信开放平台移动APP应用通过-微信商户继续申请-微信开户函-视频声明-以及对公打款验证-申请+配置完整流程-优雅草卓伊凡
967 1
【04】微信支付商户申请下户到配置完整流程-微信开放平台移动APP应用通过-微信商户继续申请-微信开户函-视频声明-以及对公打款验证-申请+配置完整流程-优雅草卓伊凡