AndroidでWebViewからPDFを表示させたい【Kotlin,Java】

皆さんこんにちは!

最近の開発でWebViewからPDFを表示させたいという要望があり

かなり苦戦したのでメモとしてこの記事に残しておこうと思います。

最初に書いたコード

最初に書いたコードは以下の通りです。

   /*ファイルが.pdfだった場合にOpenPdfが動きます*/
 fun openApp(url:String, mContext: Context): Boolean{
        val urlSp = url.split(".")
        if (urlSp.isNotEmpty()) {
            if (urlSp[urlSp.size - 1].equals("pdf")) {
                openPdf(url, mContext)
                return true;
            }
        }
        return false
    }
/* WebViewのPDFがタップされたらこのメソッドが動きます*/private fun openPdf(url:String, mContext: Context){  val intent = Intent()  intent.action = Intent.ACTION_VIEW   intent.data = Uri.parse(url) intent.type = "application/pdf"  mContext.startActivity(Intent.createChooser(intent, null)) }

ですがどうやってもうまくいきません。

色々調べてみて出た結論はダウンロードして

PDFを開かないと開くことができないということです。

というわけでダウンロードしてそのファイルパスから

PDFを表示させようということになりました。

作り替えたコードがこちらです。

private fun openPdf(url: String, mContext: Context){
    val urlSp = url.split("/")
    val pdfName = urlSp[urlSp.size - 1]
    val file = File(Environment.DIRECTORY_DOWNLOADS, pdfName)
    lateinit var request :DownloadManager.Request
        request = DownloadManager.Request(
                Uri.parse(url))
                .setMimeType("application/pdf")
                //Android 9.0(APIレベル28)以降を対象とし、使用するアプリフォアグラウンドサービスは、
                //ManifestでFOREGROUND_SERVICE権限をリクエストする必要があるかもしれない
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
                //trueに設定すると、モバイルネットワークが接続されても、ダウンロードします
                .setAllowedOverMetered(true)
                .setDestinationUri(Uri.fromFile(file))
                //実際に保存されるファイル名 とあるwebサイトによればEnvironment.DIRECTORY_DOWNLOADS.toString()にしていた
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, pdfName)

    //メディアスキャンを許可する(ギャラリーアプリとかで参照可能になる)
    request.allowScanningByMediaScanner()
    //ダウンロード中・ダウンロード完了時にも通知を表示する
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
    val dm = mContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    mydownloadid = dm.enqueue(request)

    //このonReceiveはダウンロードが完了した時に呼ばれます
    val onDownloadComplete = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == intent.action) {
                //インテントは複数のIDに対する応答が来ることができるので、downloadIdをチェックする必要があります
                if (mydownloadid == id) {
                    val query: DownloadManager.Query = DownloadManager.Query()
                    query.setFilterById(id)
                    //cursorはDownloadMangerのqueryからダウンロード状態を取得
                    val cursor = dm.query(query)
                    if (!cursor.moveToFirst()) {
                        return
                    }

                    val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
                    val status = cursor.getInt(columnIndex)
                    //ダウンロードが成功
                    if (status == DownloadManager.STATUS_SUCCESSFUL) {
                            Toast.makeText(context, R.string.msg_pdf_download_completed, Toast.LENGTH_SHORT).show()
                            //ここでDownloadManager.COLUMN_URIにしたらできなかった
                            val path = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
                            val uri = Uri.parse(path)
                            //メインスレッドから I/O アクセスなどを行った場合に、違反 (violation) 通知を受けれる
                            val builder = VmPolicy.Builder()
                            StrictMode.setVmPolicy(builder.build())
                            //最初に書いたコード ダウンロードが完了したので表示されるようになった
                            val intentDownloaded = Intent()
                            intentDownloaded.action = Intent.ACTION_VIEW
                            intentDownloaded.setDataAndType(uri, "application/pdf")
                            intentDownloaded.flags = Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_GRANT_READ_URI_PERMISSION
                            mContext.startActivity(Intent.createChooser(intentDownloaded, "Open file with"))
                    } else if (status == DownloadManager.STATUS_FAILED) {
                        Toast.makeText(context, R.string.msg_pdf_download_failed, Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }
    val intentFilter = IntentFilter()
    // ダウンロード要求が完了(成功と失敗)でonDownloadCompleteが呼ばれるようにするため
    intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
    mContext.registerReceiver(onDownloadComplete, intentFilter)
}

↓Manifestに以下を追加しておきます。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />

ですが、ここでもいくつかの問題が発生しました。

第一の問題

まずはwebViewがログインしているWebページからpdfをダウンロードするので

アクセスできずpdfをダウンロードできないという問題です。

この問題はクッキーを追加することで直りました。

val cookies: String = CookieManager.getInstance().getCookie(url)
//requestの.setDestinationInExternalPublicDirの下に追加
.addRequestHeader("cookie", cookies)

そして次は端末による問題です。

Android10より前までは動きますが、

Android10以降はダウンロードは完了しますがPDFを開くまでされません。

またアプリによって開く開かないが見られました。

(google drive pdf viewerは動かない,adobeは動く)

この理由としてAndroid10から導入されたScoped Storageによるものらしいです。

これについてはSDKバージョンによって処理を変えることで対応しました。

最終的なコード

private fun openPdf(url: String, mContext: Context){
    val urlSp = url.split("/")
    val pdfName = urlSp[urlSp.size - 1]
    val file = File(Environment.DIRECTORY_DOWNLOADS, pdfName)
    val cookies: String = CookieManager.getInstance().getCookie(url)
    lateinit var request :DownloadManager.Request
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        request = DownloadManager.Request(
                Uri.parse(url))
                .setMimeType("application/pdf")
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
                .setAllowedOverMetered(true)
                .setDestinationUri(Uri.fromFile(file))
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS.toString(), pdfName)
                .addRequestHeader("cookie", cookies)
    }else{
        request = DownloadManager.Request(
                Uri.parse(url))
                .setMimeType("application/pdf")
                .addRequestHeader("cookie", cookies)
    }

    request.allowScanningByMediaScanner()
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
    val dm = mContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    mydownloadid = dm.enqueue(request)

    val onDownloadComplete = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == intent.action) {
                if (mydownloadid == id) {
                    val query: DownloadManager.Query = DownloadManager.Query()
                    query.setFilterById(id)
                    val cursor = dm.query(query)
                    if (!cursor.moveToFirst()) {
                        return
                    }
                    val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
                    val status = cursor.getInt(columnIndex)
                    if (status == DownloadManager.STATUS_SUCCESSFUL) {
                            Toast.makeText(context, R.string.msg_pdf_download_completed, Toast.LENGTH_SHORT).show()

                            val path = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
                            val uri = Uri.parse(path)
                            val builder = VmPolicy.Builder()
                            StrictMode.setVmPolicy(builder.build())

                            val intentDownloaded = Intent()
                            intentDownloaded.action = Intent.ACTION_VIEW
                            intentDownloaded.setDataAndType(uri, "application/pdf")
                            intentDownloaded.flags = Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_GRANT_READ_URI_PERMISSION
                            mContext.startActivity(Intent.createChooser(intentDownloaded, "Open file with"))
                    } else if (status == DownloadManager.STATUS_FAILED) {
                        Toast.makeText(context, R.string.msg_pdf_download_failed, Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }
    val intentFilter = IntentFilter()
    intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
    mContext.registerReceiver(onDownloadComplete, intentFilter)

}

Android10より前は正常に動くが,Android10以降は

端末によってダウンロードはされるがPDFの表示がされなかったりする。

PDFのアプリによっては開いてくれるアプリもある

(google drive pdf viewerは動かない,adobeは動く)

結局この状態でリリースとなりました。。

一応PDFの表示ができなくてもダウンロード自体はされるので

通知バーから「ダウンロードが完了しました」の通知を

タップすればどの端末でも開くことができます。

 

全ての端末で正常に動くやり方があるなら教えてほしいです。

コメント欄からお願いします。

参考にした記事

https://cloud6.net/so/android/4173171

https://setohide.blogspot.com/2020/09/intent.html

https://teratail.com/questions/115988

https://codechacha.com/ja/android-downloadmanager/

https://stackoverflow.com/questions/62444823/drive-doesnt-opens-pdf

コメントを残す

メールアドレスが公開されることはありません。