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


当前回答

It's worth noting that if you use the DownloadManager to kick off your download, be sure to save it to an external location e.g. setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. The intent with a package-archive type doesn't appear to like the content: scheme used with downloads to an internal location, but does like file:. (Trying to wrap the internal path into a File object and then getting the path doesn't work either, even though it results in a file: url, as the app won't parse the apk; looks like it must be external.)

例子:

int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String downloadedPackageUriString = cursor.getString(uriIndex);
File mFile = new File(Uri.parse(downloadedPackageUriString).getPath());
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
        .setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive")
        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(promptInstall);

其他回答

这可以帮助别人很多!

第一:

private static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyAppFolderInStorage/";

private void install() {
    File file = new File(APP_DIR + fileName);

    if (file.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        String type = "application/vnd.android.package-archive";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Uri downloadedApk = FileProvider.getUriForFile(getContext(), "ir.greencode", file);
            intent.setDataAndType(downloadedApk, type);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else {
            intent.setDataAndType(Uri.fromFile(file), type);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        getContext().startActivity(intent);
    } else {
        Toast.makeText(getContext(), "ّFile not found!", Toast.LENGTH_SHORT).show();
    }
}

第二:对于android 7及以上,你应该在manifest中定义一个提供商,如下所示!

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="ir.greencode"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
    </provider>

第三:在res/xml文件夹中定义path.xml,如下所示! 我使用这个路径内部存储,如果你想改变它的东西有一些方法!你可以进入这个链接: FileProvider

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

第四:您应该在manifest中添加此权限:

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

允许应用程序请求安装包。针对api大于25的应用程序必须拥有此权限才能使用Intent.ACTION_INSTALL_PACKAGE。 请确保提供者权限相同!


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

market://details?id=package.name

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

基于@ uroovic podkrinik的回答。

通过APK安装应用程序对于不同版本的android可能有所不同(API级别21-30):

private var uri: Uri? = null
private var manager: DownloadManager? = null
private var file: File? = null
private var request: DownloadManager.Request? = null

private val REQUEST_WRITE_PERMISSION = 786
private val REQUEST_INSTALL_PACKAGE = 1234

private var receiver: BroadcastReceiver? = null
private var installIntent: Intent? = null

...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val externalStorageDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
    } else {
        @Suppress("DEPRECATION")
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    }
    val destination = "$externalStorageDir/Application.apk"
    uri = Uri.parse("file://$destination")

    file = File(destination)
    file?.let { if (it.exists()) it.delete() }
    
    request = DownloadManager.Request(Uri.parse("https://path_to_file/application.apk"))
    request?.let {
        it.setDescription("Update App")
        it.setTitle("Application")
        it.setDestinationUri(uri)
    }
    manager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager

    // for level android api >= 23 needs permission to write to external storage
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(context!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            // here you can display the loading diagram
            registerReceiver()
        } else {
            // request for permission to write to external storage
            requestPermissions(
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                REQUEST_WRITE_PERMISSION
            )
        }
    } else {
        // here you can display the loading diagram
        registerReceiver()
    }
}

创建并注册接收者:

private val onDownloadComplete = object : BroadcastReceiver() {
    // install app when apk is loaded
    override fun onReceive(ctxt: Context, intent: Intent) {
        val mimeType = "application/vnd.android.package-archive"
        receiver = this
        try {
            installIntent = Intent(Intent.ACTION_VIEW)
            installIntent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP

            // for android api >= 24 requires FileProvider
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                installIntent?.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

                val fileProviderURI = FileProvider.getUriForFile(
                    context!!,
                    context!!.applicationContext.packageName + ".provider",
                    file!!)

                installIntent?.setDataAndType(fileProviderURI, mimeType)

                // for android api >= 26 requires permission to install from APK in settings
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
                        installFromAPK()
                    } else goToSecuritySettings()
                } else installFromAPK()
            } else {
                // for android api < 24 used file:// instead content://
                // (no need to use FileProvider)
                installIntent?.setDataAndType(uri, mimeType)
                installFromAPK()
            }
        } catch (e: Exception) {
            // view error message
        }
    }
}

private fun registerReceiver() {
    manager!!.enqueue(request)
    context?.registerReceiver(
        onDownloadComplete,
        IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
    )
}

private fun installFromAPK() {
    try {
        startActivity(installIntent)
        context?.unregisterReceiver(receiver)
        activity?.finish()
    } catch (e: Exception) {
        // view error message
    }
}

// go to settings for get permission install from APK
private fun goToSecuritySettings() {
    val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
        Uri.parse(String.format(
            "package:%s",
            context!!.applicationContext.packageName
        ))
    )
    try {
        startActivityForResult(intent, REQUEST_INSTALL_PACKAGE)
    } catch (e: Exception) {
        // view error message
    }
}

拦截权限请求WRITE_EXTERNAL_STORAGE的结果:

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_WRITE_PERMISSION
            && grantResults.isNotEmpty()
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        try {
            // here you can display the loading diagram
            registerReceiver()
        } catch (e: Exception) {
            // view error message
        }
    }
}

在安全设置中拦截用户选择的结果:

@RequiresApi(api = Build.VERSION_CODES.O)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_INSTALL_PACKAGE
            && resultCode == AppCompatActivity.RESULT_OK) {
        if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
            installFromAPK()
        }
    } else {
        // view error message
    }
}

添加到您的清单:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application...>
    ...
    <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>
    ...
</application>

将provider_paths.xml文件添加到res/xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

对于android API级别= 30,从安全设置返回不工作, 所以使用浏览器安装:

try {
    val intent = Intent(Intent.ACTION_VIEW)
    intent.data = Uri.parse("https://path_to_file/application.apk")
    startActivity(intent)
    activity?.finish()
} catch (e: ActivityNotFoundException) { }
File file = new File(dir, "App.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);

我也遇到过同样的问题,在尝试了几次之后,结果是这样的。我不知道为什么,但是分别设置数据和类型破坏了我的意图。

在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;
    }
}