非Play商店APK应用内无浏览器直接更新实现方案咨询
Hey there! I’ve tackled this exact problem before when building a non-Play Store app, so let’s break down the reliable ways to implement in-app APK downloads without bouncing the user to a browser—just like Google Play does.
Solution 1: Use Android’s Built-in DownloadManager (Recommended)
This is the most stable approach because it leverages Android’s system-level download service, which keeps running even if your app is closed (just like Play Store updates). Here’s how to set it up:
Step 1: Add Required Permissions & FileProvider
First, update your AndroidManifest.xml to include necessary permissions and register a FileProvider (required for Android 7.0+ to avoid file exposure errors):
<!-- Basic internet access --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Required for installing APKs on Android 8.0+ --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Only needed for Android 9 and below --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <application ...> <!-- Register FileProvider to safely share APK files --> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
Next, create res/xml/file_paths.xml to define where the APK will be stored:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Stores APK in your app's external files directory (no extra permissions needed for Android 10+) --> <external-files-path name="apk_files" path="." /> </paths>
Step 2: Implement the Download & Install Logic
Add this code to your Activity/Fragment to start the download and handle installation once it’s done:
private fun startInAppApkDownload(apkDownloadUrl: String) { // Get system DownloadManager service val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager // Configure download request val downloadRequest = DownloadManager.Request(Uri.parse(apkDownloadUrl)) .setTitle("My App Update") .setDescription("Downloading the latest version...") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) // Save APK to your app's private external storage .setDestinationInExternalFilesDir(this, null, "MyApp_Latest.apk") // Enqueue the download and save its ID to track progress/completion val downloadId = downloadManager.enqueue(downloadRequest) // Register a broadcast receiver to listen for download completion val downloadCompleteReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val completedDownloadId = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) if (completedDownloadId == downloadId) { // Download finished—trigger installation installDownloadedApk(context) // Unregister the receiver to avoid memory leaks context?.unregisterReceiver(this) } } } registerReceiver(downloadCompleteReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) } private fun installDownloadedApk(context: Context?) { context ?: return val apkFile = File(getExternalFilesDir(null), "MyApp_Latest.apk") // Get a safe URI for the APK (handles Android 7.0+ file restrictions) val apkUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile(context, "${packageName}.fileprovider", apkFile) } else { Uri.fromFile(apkFile) } // Create install intent val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply { setDataAndType(apkUri, "application/vnd.android.package-archive") addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } // Check for unknown app installation permission (Android 8.0+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (packageManager.canRequestPackageInstalls()) { startActivity(installIntent) } else { // Redirect user to settings to enable permission val permissionIntent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:$packageName")) startActivity(permissionIntent) } } else { startActivity(installIntent) } }
Step 3: Trigger the Download from Your Update Button
Just call startInAppApkDownload("YOUR_APK_DOWNLOAD_URL") when the user taps your update button.
Solution 2: Custom Downloader (For Advanced Control)
If you need more control over the download process (like custom progress bars or retry logic), you can use OkHttp or HttpURLConnection with a background coroutine:
// Using Kotlin Coroutines for background download private fun startCustomDownload(apkUrl: String) { lifecycleScope.launch(Dispatchers.IO) { val apkFile = File(getExternalFilesDir(null), "MyApp_Latest.apk") val client = OkHttpClient() try { val request = Request.Builder().url(apkUrl).build() val response = client.newCall(request).execute() if (response.isSuccessful) { response.body?.byteStream()?.use { inputStream -> apkFile.outputStream().use { outputStream -> inputStream.copyTo(outputStream) } } // Switch to main thread to trigger installation withContext(Dispatchers.Main) { installDownloadedApk(this@YourActivity) } } } catch (e: IOException) { withContext(Dispatchers.Main) { Toast.makeText(this@YourActivity, "Download failed: ${e.message}", Toast.LENGTH_SHORT).show() } } } }
Key Notes to Remember
- HTTPS Only: Android P (API 28+) blocks HTTP requests by default, so make sure your APK download URL uses HTTPS. If you must use HTTP, add a network security config to your Manifest.
- Download Failure Handling: For
DownloadManager, you can query the download status usingdownloadManager.query(DownloadManager.Query().setFilterById(downloadId))to check for failures and notify the user. - Scoped Storage: On Android 10+, saving to
getExternalFilesDir()doesn’t requireWRITE_EXTERNAL_STORAGEpermission, which simplifies things.
That should give you a seamless in-app update experience just like Google Play. Let me know if you hit any snags!
内容的提问来源于stack exchange,提问作者Amit Chauhan

