Android下载apk并安装apk(用于软件版本升级用途)

软件版本更新是每个应用必不可少的功能,基本实现方案是请求服务器最新的版本号与本地的版本号对比,有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易,本文就不过多讲解,主要讲解下载apk到安装apk的内容。

一、所需权限

<!--请求安装APK的权限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!--写如外部存储的权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--读取外部存储的权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--网络权限-->
<uses-permission android:name="android.permission.INTERNET"/>

(1)读写外部存储的权限需要动态申请,详见:Android动态获取权限

(2)安装apk的权限从Android8.0开始需要每个应用独立开启

//跳转到开启apk安装权限开启的界面,让用户手动打开
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,Uri.parse("package:" +getPackageName()));
intentActivityResultLauncher.launch(intent);

二、代码实现

(1)注册provider

在AndroidManifest.xml中声明provider

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="你的包名">

    <!--省略属性。。。-->
    
    <application
        省略属性。。。>

        <activity
        省略属性。。。>

        <!--声明provider-->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="你的包名.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>

    </application>

</manifest>

在res的xml目录增加filepaths.xml
在这里插入图片描述
filepaths.xml中配置path路径

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

(2)动态申请权限基础BaseActivity

这个类在另外一篇文章中讲解,主要为了方便动态获取权限。

package com.soface.versioncontroll;

import android.content.pm.PackageManager;
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.util.ArrayList;
import java.util.List;

public class BaseActivity extends AppCompatActivity {

    public static final int REQUEST_CONDE =0xFFFF;

    /**
     * 动态请求权限(入口)
     * @param permissionNameList 需要授权的权限名称
     */
    public void requestPermission(List<String> permissionNameList){

        // TODO: 2023/2/22 第一步:排除(已经获得过授权的权限)=============================================================
        List<String> UnauthorizedPermissionNameList = new ArrayList<>();//用于存放未获得授权的权限
        for (String permission : permissionNameList){
            //检查每个权限是否已经获得授权
            int checkResult=ContextCompat.checkSelfPermission(this,permission);
            if (checkResult==PackageManager.PERMISSION_GRANTED){
                //已获得过授权,直接抛出结果true
                throwPermissionResults(permission,true);
            }else if (checkResult==PackageManager.PERMISSION_DENIED) {
                //未获得授权,把未获得授权的权限加入到thisPermissionNames中,待下一步请求
                UnauthorizedPermissionNameList.add(permission);
            }else {
                //按道理,这里永远不会发生,
                //因为checkSelfPermission方法说得很清楚,只会返回PERMISSION_GRANTED或者PERMISSION_DENIED
                //但是为了严谨,以防万一,还是给它抛出结果false
                throwPermissionResults("Unknown_result",false);
            }
        }
        if (UnauthorizedPermissionNameList.size()==0)return;//表示:全部已经拥有全选,不用往下执行

        // TODO: 2023/2/22 第二步:开始申请权限==========================================================================
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //由于请求权限的参数必须是String[],所以List转到String[]中
            String[] UnauthorizedPermissionNames=new String[UnauthorizedPermissionNameList.size()];
            for (int k=0;k<UnauthorizedPermissionNameList.size();k++){
                UnauthorizedPermissionNames[k]=UnauthorizedPermissionNameList.get(k);
            }
            //请求权限
            ActivityCompat.requestPermissions(this, UnauthorizedPermissionNames, REQUEST_CONDE);
        }else {
            //低版本的Android不需要动态获取权限,这里直接抛出结果true
            throwPermissionResults("Below_VERSION_M",true);
        }
    }

    /**
     * 抛出授权结果(出口)
     * @param permissionName 权限名称
     * @param isSuccess 该权限是否获得授权(true:获得授权  false:未获得授权)
     */
    public void throwPermissionResults(String permissionName, boolean isSuccess){
        // TODO: 2023/2/22 这里如果isSuccess=false,可以自定义一个弹窗,让用户选择
        //  到底是要直接退出应用,还是去设置中开启权限,本文主要是总结动态获取权限,所以弹窗笔者就不写了
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        //判断我们的请求码,避免别的事件调用onRequestPermissionsResult,导致我们拿到本不该属于我们的数据
        if (requestCode==REQUEST_CONDE){
            // 如果请求被取消,则结果数组为空。
            if (grantResults.length > 0) {
                //循环一个一个地去判断结果
                for (int k=0;k<permissions.length;k++){

                    if (grantResults[k] == PackageManager.PERMISSION_GRANTED){
                        // 权限请求成功,抛出结果true
                        throwPermissionResults(permissions[k],true);
                    }

                    if (grantResults[k] == PackageManager.PERMISSION_DENIED){
                        // 权限请求失败,抛出结果false
                        throwPermissionResults(permissions[k],false);
                    }
                }
            } else {
                //没有任何授权结果,直接抛出结果false
                throwPermissionResults("Unknown_result",false);
            }
        }
    }

}

(3)判断需不需要升级最新软件的MainActivity

package com.soface.versioncontroll;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends BaseActivity{


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //动态请求权限
        List<String> perList=new ArrayList<>();
        perList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        perList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
        perList.add(Manifest.permission.INTERNET);
        requestPermission(perList);

        //初始化结果返回接听
        initActivityResult();

        Button permission=(Button) findViewById(R.id.permission);
        permission.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            	//当判断需要升级最新软件,则调用这个方法,这里为了方便测试,放在点击事件中
                openSetting();
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stop();
    }

    @Override
    public void throwPermissionResults(String permissionName, boolean isSuccess) {
        super.throwPermissionResults(permissionName, isSuccess);
        //拿到相应的权限,以及授权结果
        switch (permissionName){
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                Log.d("fxHou","WRITE_EXTERNAL_STORAGE授权结果:"+isSuccess);
                break;
            case Manifest.permission.READ_EXTERNAL_STORAGE:
                Log.d("fxHou","READ_EXTERNAL_STORAGE授权结果:"+isSuccess);
                break;
            default:
                break;
        }
    }

    public void openSetting() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //Android 8.0以上
            if(!getPackageManager().canRequestPackageInstalls()){
                //权限没有打开,跳转界面,提示用户去手动打开
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,Uri.parse("package:" +getPackageName()));
                intentActivityResultLauncher.launch(intent);
            }else {
                //已经拥有权限,直接执行下载apk操作
                start();
            }
        }else {
            //开始下载安装
            start();
        }
    }
    private ActivityResultLauncher<Intent> intentActivityResultLauncher;
    private void initActivityResult() {
        intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == AppCompatActivity.RESULT_OK) {
                //开始下载安装
                start();
            }
        });
    }


    VersionControl versionControl;
    String downloadUrl="http://www.soface.top:8080/source/Public/ApkVersionControl/chart.apk";
    String titleStr="麦麦商家版V1.1.2";
    String contentStr="正在下载中,请耐心等待";
    //开始执行版本更新操作
    public void start(){
        //初始化版本控制
        versionControl=new VersionControl();
        versionControl.download(this,downloadUrl,titleStr,contentStr);
        versionControl.registerReceiver(this);
    }
    //停止执行版本更新操作
    public void stop(){
        //初始化版本控制
        versionControl.unRegisterReceiver(MainActivity.this);
        versionControl=null;
    }
}

(4)下载apk和安装apk的实现类

package com.soface.versioncontroll;

import static android.content.Context.DOWNLOAD_SERVICE;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import java.io.File;

public class VersionControl {

    //第一步: 下载APK
    private long downloadId=-1;
    private DownloadManager downloadManager;
    public void download(Context context,String url,String titleStr,String contentStr) {
        //创建下载任务
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        //在通知栏中显示,默认就是显示的
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setTitle(titleStr);
        request.setDescription(contentStr);
        //设置下载的路径
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "chart.apk");
        request.setDestinationUri(Uri.fromFile(file));
        file.getAbsolutePath();
        //获取DownloadManager
        downloadManager = (DownloadManager)context.getSystemService(DOWNLOAD_SERVICE);
        //将下载请求放入队列
        downloadId = downloadManager.enqueue(request);
    }

    //第二步: 监听下载结果
    private BroadcastReceiver broadcastReceiver;
    public void registerReceiver(Context context) {
        // 注册广播监听系统的下载完成事件。
        IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                long thisDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                if (thisDownloadId!=-1 && downloadId!=-1){
                    if (thisDownloadId == downloadId) {
                        //下载完成,检查下载状态
                        checkStatus(context);
                    }
                }
            }
        };
        context.registerReceiver(broadcastReceiver, intentFilter);
    }
    public void unRegisterReceiver(Context context){
        if (broadcastReceiver!=null) {
            context.unregisterReceiver(broadcastReceiver);
        }
    }

    //第三部: 检查下载状态,是否下载成功
    @SuppressLint("Range")
    private void checkStatus(Context context) {

        DownloadManager.Query query = new DownloadManager.Query();
        // 执行查询, 返回一个 Cursor (相当于查询数据库)
        Cursor cursor = downloadManager.query(query);
        if (!cursor.moveToFirst()) {
            cursor.close();
        }
        int id = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
        //通过下载的id查找
        query.setFilterById(id);

        // 获取下载好的 apk 路径
        String localFilename = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
        } else {
            localFilename = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
        }

        if (cursor.moveToFirst()) {
            int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
            switch (status) {
                case DownloadManager.STATUS_PAUSED:
                    //下载暂停
                    Log.d("fxHou","下载暂停");
                    break;
                case DownloadManager.STATUS_PENDING:
                    //下载延迟
                    Log.d("fxHou","下载延迟");
                    break;
                case DownloadManager.STATUS_RUNNING:
                    //正在下载
                    Log.d("fxHou","正在下载");
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    //下载完成安装APK
                    installApk(context,localFilename);
                    cursor.close();
                    break;
                case DownloadManager.STATUS_FAILED:
                    //下载失败
                    Log.d("fxHou","下载失败");
                    cursor.close();
                    break;
                default:
                    break;
            }
        }
    }

    //第四部: 安装apk
    private void installApk(Context context,String path) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        File file = new File(Uri.parse(path).getPath());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri uri = FileProvider.getUriForFile(context, "你的包名.fileprovider", file);
            intent.setDataAndType(uri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/8233.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Socket套接字编程(实现TCP和UDP的通信)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

(链表)移除链表元素(双指针法)

文章目录前言&#xff1a;问题描述&#xff1a;解题思路&#xff08;双指针法&#xff09;&#xff1a;代码实现&#xff1a;总结&#xff1a;前言&#xff1a; 此篇是针对链表的经典练习题。 问题描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请…

Js:apply/call/bind、作用域/闭包、this指向(普通,箭头,JS/Vue的this)

目录1、apply/call/bind2、作用域、作用域链和闭包核心1、预处理&#xff08;解析阶段&#xff09;——JS执行“代码段”之前2、生成执行上下文环境——对代码段(全局/函数体)进行处理3、执行上下文环境小结4、多个执行上下文环境5、作用域6、作用域和执行上下文7、从【自由变量…

小米万兆路由器里的 Docker 安装 Gitea

小米万兆路由器里的 Docker 安装 Gitea准备工作创建存储查看Docker Hub镜像信息拉取 gitea 镜像和运行容器配置通过 ssh 访问(Optional)其他小米2022年12月份发布了万兆路由器&#xff0c;里面可以使用Docker。 今天尝试在小米的万兆路由器里安装Gitea。 准备工作 先将一块US…

Java企业级开发学习笔记(2.1)MyBatis实现简单查询

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/zi0wB】 文章目录零、创建数据库与表一、基于配置文件方式使用MyBatis基本使用1.1 创建Maven项目 - MyBatisDemo1.2 在pom文件里添加相应的依赖1.3 创建与用户表对应的用户实体类 - User1.4 创建用…

没有他们,人工智能只能死翘翘

我过去写过一篇文章《很多所谓伟大的贡献&#xff0c;其实都是狗屎运》&#xff0c;今天我也写写人工智能。&#xff08;1&#xff09;人才深度神经网络如果不从明斯基和罗森布拉特说起&#xff0c;那就应该可以从1965年Ivakhnenko发明前馈神经网络说起。但关键里程碑是出自Rum…

SpringBoot2核心功能 --- 原理解析

一、Profile功能 为了方便多环境适配&#xff0c;springboot简化了profile功能。 1.1、application-profile功能 默认配置文件 application.yaml&#xff1b;任何时候都会加载指定环境配置文件 application-{env}.yaml激活指定环境配置文件激活 命令行激活&#xff1a;java -…

【快乐手撕LeetCode题解系列】—— 环形链表 II

【快乐手撕LeetCode题解系列】—— 环形链表 II&#x1f60e;前言&#x1f64c;环形链表 II&#x1f64c;画图分析&#xff1a;&#x1f60d;思路分析&#xff1a;&#x1f60d;源代码分享&#xff1a;&#x1f60d;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客…

STM32与Python上位机通过USB虚拟串口通信

文章目录前言1. 查看原理图2. 新建工程3.添加代码与烧录4. python代码编写总结问题解决思路前言 在详细阅读广大网友的教程之后&#xff0c;我对STM32和Python通过USB通信的流程烂熟于心。 尝试用ST公司的NUCLEO-L476RG板子进行简单的回环通信测试&#xff0c;发现还是存在网上…

Linux·异步IO编程框架

hi&#xff0c;大家好&#xff0c;今天分享一篇Linux异步IO编程框架文章&#xff0c;对比IO复用的epoll框架&#xff0c;到底性能提高多少&#xff1f;让我们看一看。 译者序 本文组合翻译了以下两篇文章的干货部分&#xff0c;作为 io_uring 相关的入门参考&#xff1a; Ho…

【RocketMQ】顺序消息实现原理

全局有序 在RocketMQ中&#xff0c;如果使消息全局有序&#xff0c;可以为Topic设置一个消息队列&#xff0c;使用一个生产者单线程发送数据&#xff0c;消费者端也使用单线程进行消费&#xff0c;从而保证消息的全局有序&#xff0c;但是这种方式效率低&#xff0c;一般不使用…

Web 攻防之业务安全:接口未授权访问/调用测试(敏感信息泄露)

Web 攻防之业务安全&#xff1a;接口未授权访问/调用测试 业务安全是指保护业务系统免受安全威胁的措施或手段。广义的业务安全应包括业务运行的软硬件平台&#xff08;操作系统、数据库&#xff0c;中间件等&#xff09;、业务系统自身&#xff08;软件或设备&#xff09;、业…

ViT/vit/VIT详解

参考&#xff1a; Vision Transformer详解: https://blog.csdn.net/qq_37541097/article/details/118242600 目录&#xff1a; x.1 (论文中)模型理解x.2 代码理解 建议阅读时间&#xff1a;10min x.1 模型理解 ViT是发表在ICLR2021上的一篇文章&#xff0c;通过将图片分割…

Java并发控制 学习笔记1

一、并发控制的方法 1、悲观锁&#xff1a;常用的互斥锁都属于悲观锁&#xff0c;一个线程访问共享资源时其他线程不能访问。 2、乐观锁&#xff1a;允许同时访问共享数据&#xff0c;只有在提交时利用如版本号检查是否有冲突&#xff0c;应用github。 3、什么时候用乐观锁、什…

携程平台增长部总经理王绩强:原生互联网企业正在经历一场数字升级丨数据猿专访...

‍数据智能产业创新服务媒体——聚焦数智 改变商业以大数据和人工智能为核心&#xff0c;众多新兴技术开始赋能数字营销。于是&#xff0c;智能营销已然从工具化走向了业务化。如今&#xff0c;数字化营销已经成为了企业数字化转型中的重要一环。相较于传统营销逻辑&#xff0…

新版新款影视直播粉红色UI的麻豆CMS源码/带教程/支付已接

基于苹果CMS v10影视系统框架开发的前端模板&#xff0c;带会员中心&#xff0c;可设置试看付费观看等功能。 经过测试及修复&#xff0c;这套源码功能还是很强大的&#xff0c;可以设置一键采集&#xff0c;并且支付我们给他接到了易支付&#xff0c;拓展性强&#xff0c;基本…

【压测】通过Jemeter进行压力测试(超详细)

文章目录背景一、前言二、关于JMeter三、准备工作四、创建测试4.1、创建线程组4.2、配置元件4.3、构造HTTP请求4.4、添加HTTP请求头4.5、添加断言4.6、添加察看结果树4.7、添加Summary Report4.8、测试计划创建完成五、执行测试计划总结背景 通过SpringCloudGateway整合Nacos进…

如何下载ChatGPT-ChatGPT如何写作

CHATGPT能否改一下文章 ChatGPT 作为一种自然语言处理技术&#xff0c;生成的文章可能存在表达不够准确或文风不符合要求等问题。在这种情况下&#xff0c;可以使用编辑和修改来改变输出的文章&#xff0c;使其符合特定的要求和期望。 具体来说&#xff0c;可以采用以下步骤对…

超越竞争对手:利用Facebook A/B测试优化广告效果!

随着社交媒体广告的普及&#xff0c;Facebook已经成为了许多公司推广业务的重要平台。但是&#xff0c;在Facebook上发布广告并不意味着成功&#xff0c;这也让许多公司开始关注如何优化广告效果。 在这篇文章中&#xff0c;我将介绍如何使用A/B测试来优化Facebook广告&#x…

纳米软件关于集成电路测试的分类介绍

集成电路测试可以按照测试目的、测试内容、按照器件开发和制造阶段分类。参照需要达到的测试目的对集成电路测试进行分类&#xff0c;可以分为:验证测试、制造测试、老化测试、入厂测试等。按照测试所涉及内容&#xff0c;集成电路测试可分为:参数测试、功能测试、结构测试等。…