【Android】在App里面安装Apk文件

项目需求

在一个App里面内置一个第三方的APK文件,然后通过这个App可以安装这个APK文件。

需求实现
1.内置APK文件

在App里面创建一个assets文件夹,然后把想要安装的APK文件放到这里面。

在这里插入图片描述

2.定义文件路径访问权限

创建一个文件,命名【filepaths】,这个命名随意,只要记得名字就行,
文件里面内容

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

这段XML代码是用于在Android应用程序中定义文件路径访问的权限。在Android的应用沙箱环境中,应用程序只能访问其私有目录内的文件,默认情况下不能访问外部存储或其他应用程序的文件。为了让应用程序能够访问特定的文件路径,需要在应用的清单文件(AndroidManifest.xml)中声明相应的权限。

【paths】 元素指定了不同类型的路径访问规则。
【root-path】 元素定义了一个根路径,这个路径允许应用程序访问文件系统的根目录(root)。在这个例子中,name 属性为 “files” 表示这个路径的名称是 “files”,path 属性指定了实际的路径,. 表示当前目录,即根目录。

PS:name 属性为 “files” ,其实这个也是随便命名的,但建议选择能够清晰表达路径用途的名称,以便在应用程序中容易理解和识别。

这样做的目的是允许应用程序访问设备的文件系统中的根目录,以便读取或写入特定的文件。这种权限通常在需要从外部存储中读取或写入文件时使用,比如读取用户选择的文件或将文件保存到外部存储器中。

除了这个【root-path】,还有

【files-path】: 用于访问应用的私有文件目录。

<files-path
    name="name"
    path="path" />

name 属性定义路径的逻辑名称。
path 属性指定相对于应用的私有文件目录的子路径。

【cache-path】: 用于访问应用的私有缓存目录。

<cache-path
    name="name"
    path="path" />

name 属性定义路径的逻辑名称。
path 属性指定相对于应用的私有缓存目录的子路径。

【external-path】: 用于访问外部存储的顶级目录。

<external-path
    name="name"
    path="path" />

name 属性定义路径的逻辑名称。
path 属性指定相对于外部存储的根目录的子路径。

【external-files-path】: 用于访问应用在外部存储中的私有文件目录。

<external-files-path
    name="name"
    path="path" />

name 属性定义路径的逻辑名称。
path 属性指定相对于外部存储的应用私有文件目录的子路径。

【external-cache-path】: 用于访问应用在外部存储中的私有缓存目录。

<external-cache-path
    name="name"
    path="path" />

name 属性定义路径的逻辑名称。
path 属性指定相对于外部存储的应用私有缓存目录的子路径。

然后在AndroidManifest清单文件里面加入

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"
                tools:replace="android:resource" />
        </provider>

有时候还要加上这个权限

    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

还有注意的是需要App有读写权限的,这个需要自行申请,代码略。

3.读取文件信息

首先先定义几个常量

    /**
     * 要安装的apk文件的的包名
     */
    private static final String AppPackageName = "com.example.testapp";

    /**
     * 要安装的apk的启动项
     */
    private static final String LaunchName = "com.example.testapp.MainActivity";

    /**
     * 要安装的apk的名字
     */
    private static final String Name = "app-release.apk";

这些基本上都是固定的。这个【app-release.apk】就是放到【assets】文件夹下面的APK文件的名字

需要将这个apk文件从【assets】文件夹下面取出来放到一个指定的文件夹下面

这里使用了【Rxjava】的框架来进行异步操作。

    private static Observable<Boolean> appFileCopy(Context mContext) {
        return Observable.create(new Observable.OnSubscribe<Boolean>() {
            @Override
            public void call(Subscriber<? super Boolean> subscriber) {
            //获取应用的 AssetManager,用于访问应用的 assets 文件夹。
                AssetManager assetManager = mContext.getAssets();
                try {
                //从 assets 文件夹中打开一个名为 Name 的文件
                    InputStream inputStream = assetManager.open(Name);
                    //创建一个输出文件对象 outPutFile,保存在 DigitalManager.getInstance().getImport_path() 指定的路径下,文件名为 Name。这个DigitalManager.getInstance().getImport_path()是我自己指定的一个文件夹
                    File outPutFile = new File(DigitalManager.getInstance().getImport_path(), Name);
                    OutputStream outputStream = new FileOutputStream(outPutFile);
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = inputStream.read(buffer)) > 0) {
                        outputStream.write(buffer, 0, length);
                    }
                    outputStream.flush();
                    outputStream.close();
                    inputStream.close();
                    subscriber.onNext(true);
                    subscriber.onCompleted();
                } catch (Exception e) {
                    Log.e("TAG", "读取APK文件错误:" + e.getMessage());
                    e.printStackTrace();
                    subscriber.onNext(false);
                    subscriber.onError(e);
                }
            }
        }).subscribeOn(Schedulers.io());
    }

通过 RxJava 实现了在 IO 线程上从 assets 文件夹中读取文件,并将其复制到指定的输出路径中,同时处理可能出现的异常情况,并通过 Observable 发射结果给订阅者。

4.安装APK文件
private static Observable<Boolean> appInstall(Context mContext) {
    return Observable.create(new Observable.OnSubscribe<Boolean>() {
        @Override
        public void call(Subscriber<? super Boolean> subscriber) {
            try {
                // 构建安装 APK 文件的路径
                File apkFile = new File(DigitalManager.getInstance().getImport_path(), Name);

                // 获取当前应用的包名
                String packageName = mContext.getPackageName();

                // 构建 APK 文件的 URI
                Uri apkUri;
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.addCategory("android.intent.category.DEFAULT");
                
                // 根据 Android 版本选择不同的 URI 处理方式
                if (Build.VERSION.SDK_INT >= 24) {
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    apkUri = FileProvider.getUriForFile(mContext, packageName + ".fileprovider", apkFile);
                } else {
                    apkUri = Uri.fromFile(apkFile);
                }

                // 打印 APK 文件的 URI,方便调试
                Log.d("TAG", "apkUri:" + apkUri.getPath());

                // 设置 intent 的数据类型为 APK 文件
                intent.setDataAndType(apkUri, "application/vnd.android.package-archive");

                // 添加启动标志,确保安装界面是在新任务中启动的
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                // 创建广播接收器来监听安装结果
                BroadcastReceiver receiver = new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        String action = intent.getAction();
                        Log.d("TAG", "install action " + action);
                        if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                            // 如果安装成功,则发射 true 给订阅者
                            Log.d("TAG", "app installed");
                            String installedPackageName = intent.getDataString();
                            if (installedPackageName != null && installedPackageName.contains(packageName)) {
                                subscriber.onNext(true);
                                subscriber.onCompleted();
                                // 安装成功后取消注册广播接收器,避免内存泄漏
                                mContext.unregisterReceiver(this);
                            }
                        }
                    }
                };

                // 注册广播接收器,监听应用安装相关的广播
                IntentFilter filter = new IntentFilter();
                filter.addAction(Intent.ACTION_PACKAGE_ADDED);
                filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
                filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
                filter.addDataScheme("package");
                mContext.registerReceiver(receiver, filter);

                // 在主线程中启动安装过程
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mContext.startActivity(intent); // 启动安装界面
                        } catch (Exception e) {
                            // 捕获并处理启动安装界面的异常
                            Log.e("TAG", "APK文件安装错误:" + e.getMessage());
                            e.printStackTrace();
                            subscriber.onError(e); // 将异常传递给订阅者
                        }
                    }
                });
            } catch (Exception e) {
                // 捕获并处理安装过程中的其他异常
                Log.e("TAG", "APK文件安装错误:" + e.getMessage());
                e.printStackTrace();
                subscriber.onError(e); // 将异常传递给订阅者
            }
        }
    }).subscribeOn(Schedulers.io()); // 在 IO 线程上执行这个 Observable
}

通过 RxJava 实现了安装 APK 文件的过程,并且使用了广播接收器来监听安装结果,确保安装成功后通知订阅者。

然后把这两个方法放在一起

    public static Observable<Boolean> testAppInstall(Context mContext) {
        return AppFileUtil.appFileCopy(mContext)
                .flatMap(new Func1<Boolean, Observable<Boolean>>() {
                    @Override
                    public Observable<Boolean> call(Boolean aBoolean) {
                        if (aBoolean) {
                            return AppFileUtil.appInstall(mContext)
                                    .map(new Func1<Boolean, Boolean>() {
                                        @Override
                                        public Boolean call(Boolean installSuccess) {
                                            return installSuccess;
                                        }
                                    })
                                    .onErrorReturn(new Func1<Throwable, Boolean>() {
                                        @Override
                                        public Boolean call(Throwable throwable) {
                                            Log.e("AppInstaller", "安装过程中出现异常: " + throwable.getMessage());
                                            return false;
                                        }
                                    });
                        } else {
                            return Observable.just(false); // APK复制失败
                        }
                    }
                })
                .onErrorReturn(new Func1<Throwable, Boolean>() {
                    @Override
                    public Boolean call(Throwable throwable) {
                        Log.e("AppInstaller", "文件复制过程中出现异常: " + throwable.getMessage());
                        return false;
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

这样只要暴漏一个方法就好了。
同时还有做一下检查,检查系统有没有已经安装好目标apk文件,防止重复安装。

    public static boolean isInstallApp(Context mContext) {
        PackageManager packageManager = mContext.getPackageManager();
        try {
            packageManager.getPackageInfo(AppPackageName, PackageManager.GET_ACTIVITIES);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return false;
        }
    }

在安装完成后,可以启动这个Apk

    public static void startTestApp(Context mContext) {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setComponent(new ComponentName(AppPackageName, LaunchName));
        mContext.startActivity(intent);
    }

基本上代码就这些了,接下来需要进行使用了

            tv_install_service.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (isInstalled) {
                        AppFileUtil.startTestApp(this);
                    } else {
                        AppFileUtil.testAppInstall(this)
                                .subscribe(new Action1<Boolean>() {
                                    @Override
                                    public void call(Boolean aBoolean) {
                                        if (aBoolean) {
                                            Toast.makeText(this, "安装成功", Toast.LENGTH_SHORT).show();
                                            tv_install_service.setText("启动");
                                            isInstalled = true;
                                        } else {
                                            Toast.makeText(this, "安装失败", Toast.LENGTH_SHORT).show();
                                        }
                                    }
                                });
                    }
                }
            });

点击这个【tv_install_service】按钮,如果这个Apk文件已经被安装了,就直接启动,如果没有被安装,就开始安装。

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

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

相关文章

springcloud第4季 seata报could not find any implementation for class

一 问题说明 1.1 描述 在使用seata2.0alibaba-cloud 2022.0.0.0-RC2nacos 2.2.3 模拟下订单分布式事务场景&#xff0c;出现如下问题&#xff1a;java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0 查看服务端&#xff1a;java.util.ServiceCo…

鸿蒙开发Ability Kit(程序框架服务):【向用户申请授权】

向用户申请授权 当应用需要访问用户的隐私信息或使用系统能力时&#xff0c;例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等&#xff0c;应该向用户请求授权&#xff0c;这部分权限是user_grant权限。 当应用申请user_grant权限时&#xff0c;需要完成以下步骤&a…

鸿蒙开发设备管理:【@ohos.multimodalInput.inputDevice (输入设备)】

输入设备 输入设备管理模块&#xff0c;用于监听输入设备连接、断开和变化&#xff0c;并查看输入设备相关信息。比如监听鼠标插拔&#xff0c;并获取鼠标的id、name和指针移动速度等信息。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&…

11_电子设计教程基础篇(磁性元件)

文章目录 前言一、电感1、原理2、种类1、制作工艺2、用途 3、参数1、测试条件2、电感量L3、品质因素Q4、直流电阻&#xff08;DCR&#xff09;5、额定电流6、谐振频率SRF&#xff08;Self Resonant Frequency&#xff09;7、磁芯损耗 4、应用与选型 二、共模电感1、原理2、参数…

RocketMQ常用基本操作

文章中的rabbitmq使用的是rocketmq-all-5.1.3-bin-release版本&#xff0c;需要安装包的可自行下载 RockerMQ启动停止命令 启动命令 nohup sh bin/mqnamesrv & nohup sh bin/mqbroker -n localhost:9876 --enable-proxy & 查看日志 tail -f ~/logs/rocketmqlogs/…

对话贾扬清:我创业这一年所看到的 AI

引言 在这次对话中&#xff0c;前阿里巴巴人工智能专家、现LIBRINAI创始人贾扬清分享了他在AI领域创业一年的见解和经历。作为一位从科学家转型为CEO的创业者&#xff0c;他探讨了AI计算、异构计算和云原生软件的结合带来的革命性变化&#xff0c;并讨论了LIBRINAI如何在激烈的…

EasyExcel数据导入

前言&#xff1a; 我先讲一种网上信息的获取方式把&#xff0c;虽然我感觉和后面的EasyExcel没有什么关系&#xff0c;可能是因为这个项目这个操作很难实现&#xff0c;不过也可以在此记录一下&#xff0c;如果需要再拆出来也行。 看上了网页信息&#xff0c;怎么抓到&#x…

浅谈区块链

区块链是一种分布式数据库技术&#xff0c;也被称为分布式账本技术。它的本质是一个去中心化的数据库&#xff0c;使用密码学相关联产生的数据块串连而成&#xff0c;用于验证其信息的有效性&#xff08;防伪&#xff09;和生成下一个区块。区块链具有“不可伪造”“全程留痕”…

【ajax实战02】数据管理网站—验证码登录

一&#xff1a;数据提交&#xff08;提交手机验证码&#xff09; 核心思路整理 利用form-serialize插件&#xff0c;收集对象形式的表单数据后&#xff0c;一并提交给服务器。后得到返回值&#xff0c;进一步操作 基地址&#xff1a; axios.defaults.baseURL http://geek.…

【简易版tinySTL】 哈希表与移动语义

基本概念 哈希表&#xff08;HashTable&#xff09;是一个重要的底层数据结构, 无序关联容器包括unordered_set, unordered_map内部都是基于哈希表实现。 哈希表是一种通过哈希函数将键映射到索引的数据结构&#xff0c;存储在内存空间中。哈希函数负责将任意大小的输入映射到…

垃圾回收与算法

目录 一、判断对象已经 "死亡" 1、引用计数法 2、可达性分析 二、垃圾收集算法 1、标记清楚算法 2、复制算法 3、标记整理算法 4、分代收集算法 4.1、新生代与复制算法 4.2老年代与标记复制算法 一、判断对象已经 "死亡" 1、引用计数法 在 Java 中&#…

3.ROS串口实例

#include <iostream> #include <ros/ros.h> #include <serial/serial.h> #include<geometry_msgs/Twist.h> using namespace std;//运行打开速度控制插件&#xff1a; rosrun rqt_robot_steering rqt_robot_steering //若串口访问权限不够&#xff1a…

PTA:7-12 斐波那契数列

斐波那契数列 (FibonacciSequence)&#xff0c;又称黄金分割数列&#xff0c;因数学家莱昂纳多斐波那契 (LeonardoFibonacci) 以兔子繁殖为例子而引入&#xff0c;故又称为“兔子数列”&#xff0c;指的是这样一个数列&#xff1a;1,1,2,3,5,8,13,21,⋯ 在数学上&#xff0c;斐…

常用字符串方法<python>

导言 在python中内置了许多的字符串方法&#xff0c;使用字符串方法可以方便快捷解决很多问题&#xff0c;所以本文将要介绍一些常用的字符串方法。 目录 导言 string.center(width[,fillchar]) string.capitalize() string.count(sub[,start[,end]]) string.join(iterabl…

2.linux操作系统CPU使用率和平均负载区别

目录 概述cpu使用率区别 结束 概述 linux操作系统CPU 使用率 和 平均负载 区别 负载高并不一定使用率高&#xff0c;有可能 cpu 被占用&#xff0c;但不干活。 cpu使用率 cpu使用率&#xff1a;cpu非空闲态运行的时间占比&#xff0c;反映cpu的繁忙程度&#xff0c;和平均负载…

大模型上下文长度扩展中的检索增强技术简述

基于Transformer的语言模型在众多自然语言处理任务上都取得了十分优异的成绩&#xff0c;在一些任务上已经达到SOTA的效果。但是&#xff0c;经过预训练后&#xff0c;模型能够较好处理的序列长度就固定下来。而当前的众多场景往往需要处理很长的上下文&#xff08;如&#xff…

如何安装多版本CUDA?

在这篇文章中&#xff0c;我们不仅要安装好CUDA&#xff0c;还有安装多版本的CUDA 首先聊一个题外话&#xff1a;前几天在csdn上看到的一个话题”安装pytorch一定要去nvidia官网下载安装cuda和cudnn吗&#xff1f;“ 我相信任何一个刚开始接触或者从事深度学习的炼丹者都会从安…

java中break和continue的标签使用

break标签的使用 break label是退出label对应的循环 //BreakDetail.java //2024.06.29 public class BreakDetail{public static void main(String[] args) {label1:for(int j 0; j < 4; j){label2:for(int i 0; i < 10; i){if(i 2){//break; //情况1//break label2…

五、Pentium 微处理器保护模式存储管理,《微机系统》第一版,赵宏伟

一、分段存储管理 Pentium支持分段存储管理、分页存储管理和段页式存储管理。 1.1 分段存储管理的基本思想 一个程序由多个模块组成。 每一个模块都是一个特定功能的独立的程序段。 段式管理&#xff1a;把主存按段分配的存储管理方式。 程序模块→段→段描述符→段描述符…

热题系列章节7

剑指 Offer 04. 二维数组中的查找 题目描述&#xff1a; 在一个二维数组中&#xff08;每个一维数组的长度相同&#xff09;&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个函数&#xff0c;输入这样的一个二维数…