可以从自定义Android应用程序中以编程方式安装动态下载的apk。


当前回答

试试这个 -在舱单上填写:

uses-permission android:name="android.permission.INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions"

编写代码:

File sdCard = Environment.getExternalStorageDirectory();
String fileStr = sdCard.getAbsolutePath() + "/Download";// + "app-release.apk";
File file = new File(fileStr, "app-release.apk");
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
                        "application/vnd.android.package-archive");

startActivity(promptInstall);

其他回答

是的,这是可能的。但要做到这一点,你需要在手机上安装未经验证的消息源。例如,slideMe就是这样做的。我认为你能做的最好的事情是检查应用程序是否存在,并向Android市场发送一个意图。你应该使用android市场的url方案。

market://details?id=package.name

我不知道如何启动这个活动但如果你用那种url启动一个活动。它应该打开android市场,给你安装应用程序的选择。

您可以轻松启动市场链接或安装提示:

Intent promptInstall = new Intent(Intent.ACTION_VIEW)
    .setDataAndType(Uri.parse("file:///path/to/your.apk"), 
                    "application/vnd.android.package-archive");
startActivity(promptInstall); 

Intent goToMarket = new Intent(Intent.ACTION_VIEW)
    .setData(Uri.parse("market://details?id=com.package.name"));
startActivity(goToMarket);

但是,没有用户的明确许可,你不能安装。apks;除非设备和您的程序已经扎根。

在Android Oreo及以上版本中,我们必须采用不同的方法以编程方式安装apk。

 private void installApkProgramatically() {


    try {
        File path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);

        File file = new File(path, filename);

        Uri uri;

        if (file.exists()) {

            Intent unKnownSourceIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", activity.getPackageName())));

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

                if (!activity.getPackageManager().canRequestPackageInstalls()) {
                    startActivityForResult(unKnownSourceIntent, Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE);
                } else {
                    Uri fileUri = FileProvider.getUriForFile(activity.getBaseContext(), activity.getApplicationContext().getPackageName() + ".provider", file);
                    Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
                    intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                    intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    startActivity(intent);
                    alertDialog.dismiss();
                }

            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

                Intent intent1 = new Intent(Intent.ACTION_INSTALL_PACKAGE);
                uri = FileProvider.getUriForFile(activity.getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file);
                activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                intent1.setDataAndType(uri,
                        "application/*");
                intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent1.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                startActivity(intent1);

            } else {
                Intent intent = new Intent(Intent.ACTION_VIEW);

                uri = Uri.fromFile(file);

                intent.setDataAndType(uri,
                        "application/vnd.android.package-archive");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        } else {

            Log.i(TAG, " file " + file.getPath() + " does not exist");
        }
    } catch (Exception e) {

        Log.i(TAG, "" + e.getMessage());

    }
}

在Oreo及以上版本中,我们需要未知资源安装权限。所以在活动结果中,你必须检查结果的权限

    @Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {

        case Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE:
            switch (resultCode) {
                case Activity.RESULT_OK:
                    installApkProgramatically();

                    break;
                case Activity.RESULT_CANCELED:
                    //unknown resouce installation cancelled

                    break;
            }
            break;
    }
}

为这个问题提供的解决方案都适用于23及以下的targetSdkVersion。然而,对于Android N,即API级别24及以上,它们不能工作并崩溃如下异常:

android.os.FileUriExposedException: file:///storage/emulated/0/... exposed beyond app through Intent.getData()

这是因为从Android 24开始,用于寻址下载文件的Uri已经改变。例如,一个名为appName.apk的安装文件存储在应用程序的主要外部文件系统上,包名为com.example.test

file:///storage/emulated/0/Android/data/com.example.test/files/appName.apk

适用于API 23及以下,而类似的

content://com.example.test.authorityStr/pathName/Android/data/com.example.test/files/appName.apk

适用于API 24或以上。

关于这方面的更多细节可以在这里找到,我不打算详细介绍。

要回答24及以上版本的targetSdkVersion的问题,必须遵循以下步骤: 将以下内容添加到AndroidManifest.xml:

<application
        android:allowBackup="true"
        android:label="@string/app_name">
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.authorityStr"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/paths"/>
        </provider>
</application>

2. 将下面的paths.xml文件添加到src, main中的res的xml文件夹中:

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

pathName是上面示例内容uri示例中显示的,pathValue是系统上的实际路径。 放个"。"是个好主意如果您不想添加任何额外的子目录,则为上面的pathValue(不带引号)。

Write the following code to install the apk with the name appName.apk on the primary external filesystem: File directory = context.getExternalFilesDir(null); File file = new File(directory, fileName); Uri fileUri = Uri.fromFile(file); if (Build.VERSION.SDK_INT >= 24) { fileUri = FileProvider.getUriForFile(context, context.getPackageName(), file); } Intent intent = new Intent(Intent.ACTION_VIEW, fileUri); intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); context.startActivity(intent); activity.finish();

在外部文件系统上写入自己应用程序的私有目录时,也不需要任何权限。

我在这里写了一个AutoUpdate库,我使用了上面的方法。

好吧,我深入挖掘,并从Android源代码中找到了PackageInstaller应用程序的源代码。

https://github.com/android/platform_packages_apps_packageinstaller

从manifest中我发现它需要许可:

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

而实际安装过程发生在确认之后

Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageName != null) {
   newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
}
startActivity(newIntent);