您的位置:首页 > 科技 > IT业 > 【Android】在App里面安装Apk文件

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

2024/9/21 21:43:33 来源:https://blog.csdn.net/qq_43358469/article/details/140033601  浏览:    关键词:【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-pathname="files"path="." />
</paths>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        <providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.fileprovider"android:exported="false"android:grantUriPermissions="true"tools:replace="android:authorities"><meta-dataandroid: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>() {@Overridepublic 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>() {@Overridepublic void call(Subscriber<? super Boolean> subscriber) {try {// 构建安装 APK 文件的路径File apkFile = new File(DigitalManager.getInstance().getImport_path(), Name);// 获取当前应用的包名String packageName = mContext.getPackageName();// 构建 APK 文件的 URIUri 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() {@Overridepublic 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() {@Overridepublic 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>>() {@Overridepublic Observable<Boolean> call(Boolean aBoolean) {if (aBoolean) {return AppFileUtil.appInstall(mContext).map(new Func1<Boolean, Boolean>() {@Overridepublic Boolean call(Boolean installSuccess) {return installSuccess;}}).onErrorReturn(new Func1<Throwable, Boolean>() {@Overridepublic Boolean call(Throwable throwable) {Log.e("AppInstaller", "安装过程中出现异常: " + throwable.getMessage());return false;}});} else {return Observable.just(false); // APK复制失败}}}).onErrorReturn(new Func1<Throwable, Boolean>() {@Overridepublic 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() {@Overridepublic void onClick(View v) {if (isInstalled) {AppFileUtil.startTestApp(this);} else {AppFileUtil.testAppInstall(this).subscribe(new Action1<Boolean>() {@Overridepublic 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文件已经被安装了,就直接启动,如果没有被安装,就开始安装。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com