• 【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文件已经被安装了,就直接启动,如果没有被安装,就开始安装。

  • 相关阅读:
    2385. 感染二叉树需要的总时间将-树转化为图+邻接表+广度优先遍历
    SpringBoot:返回响应,统一封装
    数据库专辑--WITH CHECK OPTION的用法
    NeuralRecon拜读:单目视频实时连贯三维重建
    8086 汇编小程序
    【老生谈算法】matlab实现模糊数学模型源码——模糊数学模型
    uniapp实现扫一扫功能,扫码成功后跳转页面
    使用拦截器来检测用户的登录状态
    使用mybatis实现CRUD(超详细)
    【POJ No. 3984】 迷宫问题
  • 原文地址:https://blog.csdn.net/qq_43358469/article/details/140033601