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


当前回答

好吧,我深入挖掘,并从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);

其他回答

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);

另一种解决方案不需要硬编码接收应用程序,因此更安全:

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( Uri.fromFile(new File(pathToApk)) );
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;
    }
}

基于@ 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) { }

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

market://details?id=package.name

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