You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:


Step 1: Add Required Permissions

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.

Step 2: Implement PDF Download Logic

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
            }
        }
    })
}
Step 3: Toggle Button State (Download/View)

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"
}
Step 4: Display the Downloaded PDF

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()
}
Step 5: Handle Runtime Permissions

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:11:05