在Android 10中如何以编程方式安装任何Android应用程序

12

在Android 9和10中,我尝试使用apk文件的文件路径在Android Studio中以编程方式安装应用程序时遇到了问题。以下是我尝试过的方法...

Intent intent = new Intent("android.intent.action.VIEW");
intent.addCategory("android.intent.category.DEFAULT");
intent.setDataAndType(Uri.parse("content://"+Environment.getExternalStorageDirectory() + "/download/" + "app-release.apk"), "application/vnd.android.package-archive");
startActivity(intent);
我还在清单文件中添加了所需的权限。但是当我运行它时,就会出现“解析软件包时出现问题”的错误提示。

请编辑您的问题并发布完整的堆栈跟踪。 - Abra
2
在logcat中,我没有收到任何异常或错误。 - Ak23
3个回答

21

如果您想在Android 10中以编程方式安装应用程序,您需要为您的应用程序授予安装应用程序的权限。

步骤1:在Manifest中授予权限

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

步骤2:在Android清单文件中编写提供者

 <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths" />
    </provider>
步骤4:在XML文件夹中创建名为provider_paths.xml的文件并编写内容。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="files_root"
        path="Android/data/${applicationId}" />
    <external-path
        name="external_files"
        path="." />
</paths>

步骤4:授予apk安装和存储权限的运行时权限

    //installtion permission

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                if (!getPackageManager().canRequestPackageInstalls()) {
                    startActivityForResult(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", getPackageName()))), 1234);
                } else {
                }
    }

    //Storage Permission

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
            }

            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
    }

步骤5:调用安装APK函数

 void installAPK(){

    String PATH = Environment.getExternalStorageDirectory() + "/" + "apkname.apk";
    File file = new File(PATH);
    if(file.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(uriFromFile(getApplicationContext(), new File(PATH)), "application/vnd.android.package-archive");
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        try {
            getApplicationContext().startActivity(intent);
        } catch (ActivityNotFoundException e) {
            e.printStackTrace();
            Log.e("TAG", "Error in opening the file!");
        }
    }else{
        Toast.makeText(getApplicationContext(),"installing",Toast.LENGTH_LONG).show();
    }
}
Uri uriFromFile(Context context, File file) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
    } else {
        return Uri.fromFile(file);
    }
}

3
确切地说,我需要这个。 - Ak23
ACTION_VIEW在Android 10中已被弃用。 - Ashish Garg

8

首先,你的Uri在除了少数设备制造商为你破解的几个设备之外,在每个Android版本中都是无效的。

其次,默认情况下,无论是你还是包安装程序进程都无法访问Android 10上的外部存储。

第三,通过ACTION_VIEWACTION_INSTALL_PACKAGE在Android 10上安装应用程序已被弃用。

适用于Android 10及更高版本的解决方案-也适用于Android 5.0及更高版本-是使用使用PackageInstaller

此示例应用程序演示了如何使用它。关键部分在MainMotor中:

/*
  Copyright (c) 2019 CommonsWare, LLC

  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain   a copy
  of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS,   WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License.

  Covered in detail in the book _Elements of Android Q

  https://commonsware.com/AndroidQ
*/

package com.commonsware.q.appinstaller

import android.app.Application
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageInstaller
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private const val NAME = "mostly-unused"
private const val PI_INSTALL = 3439

class MainMotor(app: Application) : AndroidViewModel(app) {
  private val installer = app.packageManager.packageInstaller
  private val resolver = app.contentResolver

  fun install(apkUri: Uri) {
    viewModelScope.launch(Dispatchers.Main) {
      installCoroutine(apkUri)
    }
  }

  private suspend fun installCoroutine(apkUri: Uri) =
    withContext(Dispatchers.IO) {
      resolver.openInputStream(apkUri)?.use { apkStream ->
        val length =
          DocumentFile.fromSingleUri(getApplication(), apkUri)?.length() ?: -1
        val params =
          PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
        val sessionId = installer.createSession(params)
        val session = installer.openSession(sessionId)

        session.openWrite(NAME, 0, length).use { sessionStream ->
          apkStream.copyTo(sessionStream)
          session.fsync(sessionStream)
        }

        val intent = Intent(getApplication(), InstallReceiver::class.java)
        val pi = PendingIntent.getBroadcast(
          getApplication(),
          PI_INSTALL,
          intent,
          PendingIntent.FLAG_UPDATE_CURRENT
        )

        session.commit(pi.intentSender)
        session.close()
      }
    }
}

在这里,install() 接收从 ACTION_OPEN_DOCUMENT 获取的 Uri。在协程内部,我创建了一个 PackageInstaller 会话,打开该会话,将 APK 内容复制到会话提供的流中,然后提交并关闭该会话。


@Ak23:抱歉,我没有任何“PackageInstaller”样例Java代码可用。 - CommonsWare
@shakil.k 快速在谷歌上搜索“getLaunchIntentForPackage”会出现有关它的Android文档。这将需要您投入一些时间。 - Abandoned Cart
@Bungles:“指引我去一些地方” - 我在这本书中涵盖了那个例子。commit()需要一个IntentSender,通常我们从PendingIntent获取它。那将是我们发现安装成功、失败或需要用户批准的地方。InstallReceiver在我的示例应用程序中会在成功时哔哔作响,如果需要,则启动批准活动,否则记录一条消息到Logcat。 - CommonsWare
@CommonsWare 我已经尝试了您使用PackageInstaller更新apk的代码,它可以工作,但是在应用程序成功更新后,它会重新启动整个应用程序,用户必须转到最近的屏幕或启动器中点击应用程序才能将其带回前台。 - shakil.k
@shakil.k: "有没有办法在应用程序更新后重新启动我的应用程序" -- 据我所知没有。 - CommonsWare
显示剩余6条评论

-1
请在您的清单文件中添加以下权限。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接