Android:已掌握Assets打开PDF,需实现URL下载PDF至SD卡并按需展示按钮
Hey there! Since you already know how to load PDFs from the assets folder, let's build out the download-to-SD-card + dynamic button + in-app display feature step by step. Here's a complete, practical breakdown:
First, we need permissions to access the internet and write to external storage. Add these to your AndroidManifest.xml:
<!-- Internet access for downloading --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Storage permission for Android 9 and below --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- Optional for Android 10+ if using public directories (skip if using scoped storage) --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
Note: Storage permissions are "dangerous" permissions, so we'll need to request them dynamically at runtime.
We'll use OkHttp for reliable asynchronous downloads (you can add it to your build.gradle with implementation 'com.squareup.okhttp3:okhttp:4.10.0').
First, define where to save the PDF (e.g., the device's Downloads folder):
private val pdfSavePath = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/my_document.pdf"
Then, write the download function:
private fun downloadPdf(pdfUrl: String) { val client = OkHttpClient() val request = Request.Builder().url(pdfUrl).build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { runOnUiThread { Toast.makeText(this@MainActivity, "Download failed: ${e.message}", Toast.LENGTH_SHORT).show() } } override fun onResponse(call: Call, response: Response) { if (!response.isSuccessful) { runOnUiThread { Toast.makeText(this@MainActivity, "Server error: ${response.code}", Toast.LENGTH_SHORT).show() } return } // Save the file to SD card response.body?.byteStream()?.use { inputStream -> FileOutputStream(pdfSavePath).use { outputStream -> inputStream.copyTo(outputStream) } } runOnUiThread { Toast.makeText(this@MainActivity, "Download complete!", Toast.LENGTH_SHORT).show() toggleButtonState(isDownloaded = true) displayPdf(pdfSavePath) // Show the PDF right after download } } }) }
We need to check if the PDF exists on startup and update the button text accordingly. Add this logic to your onCreate method:
val actionButton = findViewById<Button>(R.id.action_btn) // Check if PDF is already downloaded on app launch if (isPdfDownloaded()) { toggleButtonState(isDownloaded = true) } else { toggleButtonState(isDownloaded = false) } // Handle button clicks actionButton.setOnClickListener { if (isPdfDownloaded()) { displayPdf(pdfSavePath) } else { checkPermissionsAndStartDownload("https://your-pdf-url.com/document.pdf") // Replace with your actual URL } } // Helper method to check file existence private fun isPdfDownloaded(): Boolean { val pdfFile = File(pdfSavePath) return pdfFile.exists() && pdfFile.length() > 0 } // Helper method to switch button text private fun toggleButtonState(isDownloaded: Boolean) { val actionButton = findViewById<Button>(R.id.action_btn) actionButton.text = if (isDownloaded) "View" else "Download" }
Since you already know how to load PDFs from assets, displaying a local file works similarly. Here are two options:
Option 1: Use Android's Built-in PdfRenderer
private fun displayPdf(filePath: String) { val pdfFile = File(filePath) if (!pdfFile.exists()) return try { val parcelFileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY) val pdfRenderer = PdfRenderer(parcelFileDescriptor) // Render the first page (extend this for multi-page support with ViewPager) val page = pdfRenderer.openPage(0) val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888) page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) // Show the bitmap in an ImageView val pdfImageView = findViewById<ImageView>(R.id.pdf_preview) pdfImageView.setImageBitmap(bitmap) // Clean up resources page.close() pdfRenderer.close() parcelFileDescriptor.close() } catch (e: IOException) { e.printStackTrace() Toast.makeText(this, "Failed to open PDF", Toast.LENGTH_SHORT).show() } }
Option 2: Use a Third-Party Library (AndroidPdfViewer)
For better multi-page support and gestures, add the library to your build.gradle:
implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1'
Add the PDFView to your layout:
<com.github.barteksc.pdfviewer.PDFView android:id="@+id/pdf_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
Then load the PDF:
private fun displayPdf(filePath: String) { val pdfView = findViewById<PDFView>(R.id.pdf_view) pdfView.fromFile(File(filePath)) .defaultPage(0) .enableSwipe(true) .swipeHorizontal(false) .onError { Toast.makeText(this, "Failed to load PDF", Toast.LENGTH_SHORT).show() } .load() }
Add this code to request storage permissions before downloading:
private fun checkPermissionsAndStartDownload(pdfUrl: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+: Check if we have full storage access if (Environment.isExternalStorageManager()) { downloadPdf(pdfUrl) } else { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:$packageName") startActivityForResult(intent, 1001) } } else { // Android 10 and below: Request WRITE_EXTERNAL_STORAGE if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1002) } else { downloadPdf(pdfUrl) } } } // Handle permission request results override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 1002) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { downloadPdf("https://your-pdf-url.com/document.pdf") } else { Toast.makeText(this, "Storage permission is required to download PDFs", Toast.LENGTH_SHORT).show() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 1001) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) { downloadPdf("https://your-pdf-url.com/document.pdf") } else { Toast.makeText(this, "Storage permission is required to download PDFs", Toast.LENGTH_SHORT).show() } } }
内容的提问来源于stack exchange,提问作者PRABHAT KUMAR MAHAPATRA

