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

news2024/10/6 1:50:47
项目需求

在一个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.coloradmin.cn/o/1872301.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot系列七: Lombok注解,Spring Initializr,yaml语法

老韩学生 LombokLombok介绍Lombok常用注解Lombok应用实例代码实现idea安装lombok插件 Spring InitializrSpring Initializr介绍Spring Initializr使用演示需求说明方式1: IDEA创建方式2: start.spring.io创建 注意事项和说明 yaml语法yaml介绍使用文档yaml基本语法数据类型字面…

酣客的“FFC模式”|白酒商业模式|分润制度顶层架构设计

酣客公社摒弃传统商业模式&#xff0c;提出“心联网”及“FFC模式”的商业模式。 坐标&#xff1a;厦门&#xff0c;我是肖琳 深耕社交新零售行业10年&#xff0c;主要提供新零售系统工具及顶层商业模式设计、全案策划运营陪跑等。 今天和大家分享“酣客”的营销模式&#xff…

leetcode 动态规划(基础版)单词拆分

题目&#xff1a; 题解&#xff1a; 一种可行的dp做法是基于完全背包问题&#xff0c;将s看成是一个背包&#xff0c;wordDict看作是物品&#xff0c;然后往s中放入物品判断最终是否可以变为给定的s即可。这道题和上一题都用到了在dp如何枚举连续子串和状态表示&#xff1a;枚…

JavaWeb——MySQL

目录 2. 数据库设计 3. 表的关系 4. 表关系的实现 5. 多表查询 5.1 内连接 &#xff08;1&#xff09;隐式内连接 &#xff08;2&#xff09;显式内连接 ​5.2 外连接 &#xff08;1&#xff09;左外连接 &#xff08;2&#xff09;右外连接 2. 数据库设计 数据库设…

Eureka 服务注册与发现

目录 前言 注册中心 CAP 理论 常⻅的注册中心 CAP理论对比 Eureka 搭建 Eureka Server 引⼊ eureka-server 依赖 完善启动类 编写配置⽂件 启动服务 服务注册 引⼊ eureka-client 依赖 完善配置⽂件 启动服务 服务发现 引⼊依赖 完善配置⽂件 远程调⽤ 启动…

光明致优尊耀呈现“柏林爱乐在上海”音乐会正式开幕,奏响盛夏狂热乐章

2024年6月26日&#xff0c;由光明致优尊耀呈现的中国上海国际艺术节特别项目“柏林爱乐在上海”音乐会正式开幕。暌违七年&#xff0c;世界顶级交响乐团——柏林爱乐乐团再度访沪&#xff0c;在首席指挥基里尔别特连科率领下&#xff0c;正式在中国上海国际艺术节登台演出&…

Zynq MPSoC / RFSoC 动态配置 DIMM DDR

目录 名词释义硬软件版本Zynq MPSoC / RFSoC 动态 DDR 配置简介具体操作方法PCW GUI 配置启用动态 DDR 配置功能I2C 硬件要求根据硬件修改 FSBL初始化 I2C 控制器选择 I2C Mux 的 Slave读取 EEPROM 的第一页读取 EEPROM 的第二页 调试参考文档 本文首发于 Josh Gao 的博客&…

自动化测试小技巧之Airtest-Selenium和Excel的无缝协作

一、前言 之前在问卷以及Q群上有同学有提出过能否将网页上的一些数据通过Airtest去导出生成一份Excel&#xff0c;那么我们今天一起讨论一下&#xff0c;我们应该如何去实现&#xff0c;以及当我们获取的数据类型不同的时候&#xff0c;获取的方式该怎么随之调整&#xff1f; …

实现在父盒子中点击生成子盒子并识别父盒子边界不溢出

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</…

高级运维工程师讲述银河麒麟V10SP1服务器加固删除snmp服务引起keeplived莫名连带删除引起生产事故实战

高级运维工程师讲述银河麒麟V10SP1服务器加固删除snmp服务引起keeplived莫名连带删除引起生产事故实战 一、核实操作系统信息 uname -a Linux localhost.localdomain 4.19.90-23.8.v2101.ky10.x86_64 #1 SMP Mon May 17 17:08:34 CST 2021 x86_64 x86_64 x86_64 GNU/Linuxcat…

【AMBA】APB总线的个人学习记录(一):理论知识

精通APB (Advanced Peripheral Bus) 总线设计通常意味着你不仅理解其基础概念&#xff0c;而且能够在实际的硬件设计中灵活运用APB总线&#xff0c;解决复杂问题&#xff0c;并优化设计。以下是一些关键点&#xff0c;当你掌握这些方面时&#xff0c;可以说你对APB总线设计有了…

004-GeoGebra基础篇-GeoGebra的布局

注意&#xff0c;为保证大家的GeoGebra的学习顺利从基础、中级到高级的过度&#xff0c;本教程笔记有此要求&#xff1a; 必须使用英文版GeoGebra&#xff08;虽说GeoGebra也支持汉字写算式&#xff0c;但强烈不建议&#xff0c;因为一个英文翻译过来的中文真是五花八门&#…

网店管家婆与金蝶云星空对接集成往来单位查询接口打通客户新增

网店管家婆与金蝶云星空对接集成往来单位查询接口打通客户新增 对接系统网店管家婆 目前网上管家婆已服务超十万家海内外企业级用户&#xff0c;覆盖了服装配饰、食品酒水、数码3C、美妆日护、医疗保健、母婴用品、五金工具等等行业。 对接系统&#xff1a;金蝶云星空 金蝶K/3C…

如何高效管理TikTok账号?TK矩阵防关联测评养号引流系统揭秘

TK矩阵防关联测评自养号系统是为TikTok&#xff08;TK&#xff09;平台设计的&#xff0c;旨在帮助卖家实现多账号管理、防关联以及自动化测评和养号的功能。该系统通过一系列的技术手段和创新功能&#xff0c;为跨境电商运营者提供了强大的支持。 系统核心优势 1. 全球真实环…

5000字深入讲解:企业数字化转型优先从哪个板块开始?

很多企业都知道数字化转型重要&#xff0c;但不知道应该怎样入手&#xff0c;分哪些阶段。以下引用国内领先数字化服务商 织信Informat 的数字化转型方法论材料&#xff0c;且看看他们是如何看待数字化转型的&#xff1f;数字化转型应该从哪先开始&#xff1f;如何做&#xff1…

一个好玩的 AI 产品,一起来玩儿梗

欢迎贡献你的扎心梗图&#xff1a; - 万物皆可meme介绍&#xff1a;https://hqexj12b0g.feishu.cn/wiki/space/7385353047184375810 - 任何人都可以编辑的玩梗集&#xff1a;https://hqexj12b0g.feishu.cn/wiki/

Redis-Bitmap位图及其常用命令详解

1.Redis概述 2.Bitmap Bitmap 是 Redis 中的一种数据结构&#xff0c;用于表示位图&#xff08;bit array&#xff09;。 它通常用于处理大规模数据集中每个元素的状态&#xff0c;比如用户的在线/离线状态&#xff08;每个用户对应一个位&#xff0c;表示在线&#xff08;1&a…

【课程总结】Day12:YOLO的深入了解

前言 在【课程总结】Day11&#xff08;下&#xff09;&#xff1a;YOLO的入门使用一节中&#xff0c;我们已经了解YOLO的使用方法&#xff0c;使用过程非常简单&#xff0c;训练时只需要三行代码&#xff1a;引入YOLO&#xff0c;构建模型&#xff0c;训练模型&#xff1b;预测…

分类预测 | PSO-PNN基于粒子群算法优化概率神经网络的数据分类预测(Matlab)

分类预测 | ZOA-PCNN-AT-SVM斑马优化并行卷积-支持向量机融合注意力机制的故障识别 目录 分类预测 | ZOA-PCNN-AT-SVM斑马优化并行卷积-支持向量机融合注意力机制的故障识别分类效果基本描述程序设计参考资料 分类效果 基本描述 1.PSO-PNN基于粒子群算法优化概率神经网络的数据…

SQLite:一个极简使用教程

SQLite是一个轻量级的、文件系统基础的数据库&#xff0c;它被设计为配置简单、易于部署。SQLite数据库存储在一个单一的磁盘文件中&#xff0c;这意味着数据库的创建和维护都非常简单。 1. SQLite特点 轻量级&#xff1a;SQLite不需要一个独立的服务器进程。它是一个嵌入式SQ…