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内应用更新功能~ 如果有不当之处,可以在评论区指出,一起进步。


目录
相关文章
|
19天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
25天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
25天前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
29天前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
1月前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
45 2
|
2月前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
55 5
|
2月前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。
|
11天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
16天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
2天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。