可以从自定义Android应用程序中以编程方式安装动态下载的apk。
当前回答
为这个问题提供的解决方案都适用于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库,我使用了上面的方法。
其他回答
基于@ 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) { }
只是一个扩展,如果有人需要一个库,那么这可能会有所帮助。感谢Raghav
试试这个
String filePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
String title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
MainActivity.this.startActivity(intent);
您可以轻松启动市场链接或安装提示:
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;除非设备和您的程序已经扎根。
是的,这是可能的。但要做到这一点,你需要在手机上安装未经验证的消息源。例如,slideMe就是这样做的。我认为你能做的最好的事情是检查应用程序是否存在,并向Android市场发送一个意图。你应该使用android市场的url方案。
market://details?id=package.name
我不知道如何启动这个活动但如果你用那种url启动一个活动。它应该打开android市场,给你安装应用程序的选择。
推荐文章
- getDefaultSharedPreferences和getSharedPreferences的区别
- 如何模拟按钮点击使用代码?
- Android Webview给出net::ERR_CACHE_MISS消息
- Parcelable遇到IOException写入序列化对象getactivity()
- 如何在Android中动态更改菜单项文本
- 如何将Base64字符串转换为位图图像,以显示在一个ImageView?
- 新版本的Android模拟器问题-模拟器进程已终止
- 没有与请求版本匹配的NDK版本
- 如何将一个颜色整数转换为十六进制字符串在Android?
- 格式浮动到小数点后n位
- 移除一个onclick监听器
- 如何圆形图像与滑翔图书馆?
- 测试者从哪里下载谷歌Play Android应用?
- Android Studio在创建新项目时卡住了Gradle下载
- 我如何让拨号打开与电话号码显示?