Мой веб-сайт использует html2pdf (javascript) для создания PDF-файла из html, но возникают проблемы с загрузкой в ​​веб-просмотре Android: kotlin

На моем веб-сайте используется html2pdf (javascript) для создания PDF-файла из html, но возникают проблемы с загрузкой на Android webview: kotlin. Когда я хочу нажать «Загрузить» на кнопке webview, которая отображает кнопку моего веб-сайта, она просто вылетает. Я надеялся на вас, ребята, которые знают, почему это произошло и как это преодолеть? Я все еще новичок в разработке студии Android.

На сайте (javascript):


function generatePDF(){
    var station_element = document.getElementById('station_content');
    var inspect_element = document.getElementById('inspect_content');
    window.scrollTo(0, 0);
    // Delay execution for 500 milliseconds (adjust as needed)
    setTimeout(function() {
        if (station_element) {
            var opt = {
                margin: 50,
                filename: 'myfile.pdf',
                image: { type: 'jpeg', quality: 1 },
                html2canvas: { scale: 5, width: 1300 },
                jsPDF: { unit: 'pt', format: 'a4', orientation: 'portrait' }
            };
            html2pdf().set(opt).from(station_element).save();
        } else if (inspect_element) {
            var opt = {
                margin: 60,
                filename: 'myfile.pdf',
                image: { type: 'jpeg', quality: 1 },
                html2canvas: { scale: 2 },
                jsPDF: { unit: 'pt', format: 'a4', orientation: 'portrait' }
            };
            html2pdf().set(opt).from(inspect_element).save();
        }
    }, 500); // 500 milliseconds delay
}

Android-студия: MainActivity.kt

package com.coding.meet.webviewtoapp


class MainActivity : AppCompatActivity() {
    private var webUrl = "https://smartappx.site"
    private val multiplePermissionId = 14
    private val multiplePermissionNameList =
        if (Build.VERSION.SDK_INT >= 33) {
            arrayListOf()
        } else {
            arrayListOf(
                android.Manifest.permission.READ_EXTERNAL_STORAGE,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
            )
        }

    private var isLoaded = false
    private var doubleBackToExitPressedOnce = false

    private val networkConnectivityObserver: NetworkConnectivityObserver by lazy {
        NetworkConnectivityObserver(this)
    }

    private val loadingDialog: Dialog by lazy { Dialog(this) }

    private val mainBinding: ActivityMainBinding by lazy {
        DataBindingUtil.setContentView(this, R.layout.activity_main)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        loadingDialog.setContentView(R.layout.loading_layout)
        loadingDialog.window!!.setLayout(
            LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
        loadingDialog.setCancelable(false)
        loadingDialog.show()

        val setting = mainBinding.webView.settings
        setting.javaScriptEnabled = true
        setting.allowFileAccess = true
        setting.domStorageEnabled = true
        setting.javaScriptCanOpenWindowsAutomatically = true
        setting.supportMultipleWindows()

        val snackbar =
            Snackbar.make(mainBinding.root, "No Internet Connection", Snackbar.LENGTH_INDEFINITE)
                .setAction("Wifi") { startActivity(Intent(Settings.ACTION_WIFI_SETTINGS)) }

        networkConnectivityObserver.observe(this) {
            when (it) {
                Status.Available -> {
                    if (snackbar.isShown) {
                        snackbar.dismiss()
                    }
                    mainBinding.swipeRefresh.isEnabled = true
                    if (!isLoaded) loadWebView()
                }
                else -> {
                    showNoInternet()
                    snackbar.show()
                    mainBinding.swipeRefresh.isRefreshing = false
                }
            }
        }

        mainBinding.swipeRefresh.setOnRefreshListener {
            if (!isLoaded) {
                loadWebView()
            } else {
                setProgressDialogVisibility(false)
            }
        }
    }

    private fun setProgressDialogVisibility(visible: Boolean) {
        if (visible) {
            loadingDialog.show()
        } else {
            loadingDialog.dismiss()
            mainBinding.swipeRefresh.isRefreshing = false
        }
    }

    private fun showNoInternet() {
        isLoaded = false
        setProgressDialogVisibility(false)
        gone(mainBinding.webView)
        visible(mainBinding.noInternet.noInternetRL)
    }

    private fun loadWebView() {
        gone(mainBinding.noInternet.noInternetRL)
        visible(mainBinding.webView)
        mainBinding.webView.loadUrl(webUrl)
        mainBinding.webView.setDownloadListener {
            url,
            userAgent,
            contentDisposition,
            mimeType,
            contentLength ->
            Log.d("Url", url.trim())
            Log.d("userAgent", userAgent)
            Log.d("contentDisposition", contentDisposition)
            Log.d("mimeType", mimeType)
            Log.d("contentLength", contentLength.toString())
            if (checkMultiplePermission()) {
                download(url.trim(), userAgent, contentDisposition, mimeType, contentLength)
            }
        }
        mainBinding.webView.webViewClient =
            object : WebViewClient() {
                override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                    setProgressDialogVisibility(true)
                    super.onPageStarted(view, url, favicon)
                }

                override fun shouldOverrideUrlLoading(
                    view: WebView?,
                    request: WebResourceRequest?,
                ): Boolean {
                    val url = request?.url.toString()
                    view?.loadUrl(url)
                    return super.shouldOverrideUrlLoading(view, request)
                }

                override fun onPageFinished(view: WebView?, url: String?) {
                    isLoaded = true
                    webUrl = url!!
                    setProgressDialogVisibility(false)
                    super.onPageFinished(view, url)
                }

                override fun onReceivedError(
                    view: WebView?,
                    request: WebResourceRequest?,
                    error: WebResourceError?,
                ) {
                    isLoaded = false
                    setProgressDialogVisibility(false)
                    super.onReceivedError(view, request, error)
                }
            }
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        if (event.action == KeyEvent.ACTION_DOWN) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                if (mainBinding.webView.canGoBack()) {
                    mainBinding.webView.goBack()
                } else {
                    showToastExit()
                }
                return true
            }
        }

        return super.onKeyDown(keyCode, event)
    }

    private fun showToastExit() {
        when {
            doubleBackToExitPressedOnce -> {
                finish()
            }
            else -> {
                doubleBackToExitPressedOnce = true
                Toast.makeText(this, "Please Click Back Again to Exit", Toast.LENGTH_LONG).show()
                Handler(Looper.getMainLooper())
                    .postDelayed({ doubleBackToExitPressedOnce = false }, 2000)
            }
        }
    }

    private fun download(
        url: String,
        userAgent: String,
        contentDisposition: String,
        mimeType: String,
        contentLength: Long
    ) {
        val folder = File(Environment.getExternalStorageDirectory().toString() + "/Download/Image")
        if (!folder.exists()) {
            folder.mkdirs()
        }
        Toast.makeText(this, "Download Started", Toast.LENGTH_SHORT).show()

        val request = DownloadManager.Request(Uri.parse(url))
        request.setMimeType(mimeType)
        val cookie = CookieManager.getInstance().getCookie(url)
        request.addRequestHeader("cookie", cookie)
        request.addRequestHeader("User-Agent", userAgent)
        request.setAllowedNetworkTypes(
            DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
        )
        val fileName = URLUtil.guessFileName(url, contentDisposition, mimeType)
        request.setTitle(fileName)
        request.setNotificationVisibility(
            DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
        )
        request.setDestinationInExternalPublicDir(
            Environment.DIRECTORY_DOWNLOADS,
            "Image/$fileName"
        )
        val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
        downloadManager.enqueue(request)
    }

    private fun checkMultiplePermission(): Boolean {
        val listPermissionNeeded = arrayListOf<String>()
        for (permission in multiplePermissionNameList) {
            if (
                ContextCompat.checkSelfPermission(this, permission) !=
                    PackageManager.PERMISSION_GRANTED
            ) {
                listPermissionNeeded.add(permission)
            }
        }
        if (listPermissionNeeded.isNotEmpty()) {
            ActivityCompat.requestPermissions(
                this,
                listPermissionNeeded.toTypedArray(),
                multiplePermissionId
            )
            return false
        }
        return true
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray,
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == multiplePermissionId) {
            if (grantResults.isNotEmpty()) {
                var isGrant = true
                for (element in grantResults) {
                    if (element == PackageManager.PERMISSION_DENIED) {
                        isGrant = false
                    }
                }
                if (isGrant) {
                    // here all permission granted successfully
                    Toast.makeText(this, "all permission granted successfully", Toast.LENGTH_LONG)
                        .show()
                } else {
                    var someDenied = false
                    for (permission in permissions) {
                        if (
                            !ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
                        ) {
                            if (
                                ActivityCompat.checkSelfPermission(this, permission) ==
                                    PackageManager.PERMISSION_DENIED
                            ) {
                                someDenied = true
                            }
                        }
                    }
                    if (someDenied) {
                        // here app Setting open because all permission is not granted
                        // and permanent denied
                        appSettingOpen(this)
                    } else {
                        // here warning permission show
                        warningPermissionDialog(this) { _: DialogInterface, which: Int ->
                            when (which) {
                                DialogInterface.BUTTON_POSITIVE -> checkMultiplePermission()
                            }
                        }
                    }
                }
            }
        }
    }
}

Этот код не мой, я просто тестирую его на своем демонстрационном веб-сайте.

Я пытался найти решение на платформе Android, но не нашел.

Это логарифм Android

Logcat IllegalArgumentException:

024-06-03 17:28:30.668  1264-2123  AlarmManager            com.google.android.gms.persistent    W  alarm window unlikely to be respected [CONTEXT service_id=231 ] (Ask Gemini)
    java.lang.IllegalArgumentException: alarm "NetworkLocationLocator" has short window length
        at bngl.b(:com.google.android.gms@[email protected] (190800-633713831):52)
        at efni.f(:com.google.android.gms@[email protected] (190800-633713831):119)
        at efjs.r(:com.google.android.gms@[email protected] (190800-633713831):107)
        at efjs.f(:com.google.android.gms@[email protected] (190800-633713831):27)
        at efjp.apply(:com.google.android.gms@[email protected] (190800-633713831):19)
        at esdc.d(:com.google.android.gms@[email protected] (190800-633713831):3)
        at esdd.run(:com.google.android.gms@[email protected] (190800-633713831):42)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at bomx.nL(:com.google.android.gms@[email protected] (190800-633713831):1)
        at bomx.dispatchMessage(:com.google.android.gms@[email protected] (190800-633713831):138)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.os.HandlerThread.run(HandlerThread.java:67)
--------- beginning of crash
2024-06-03 17:36:43.011  1639-1639  AndroidRuntime          com.coding.meet.webviewtoapp         E  FATAL EXCEPTION: main (Ask Gemini)
    Process: com.coding.meet.webviewtoapp, PID: 1639
    java.lang.IllegalArgumentException: Can only download HTTP/HTTPS URIs: blob:https://smartappx.site/ac776f6d-9ba9-4d15-ab60-2249b485853b
        at android.app.DownloadManager$Request.<init>(DownloadManager.java:468)
        at com.coding.meet.webviewtoapp.MainActivity.download(MainActivity.kt:236)
        at com.coding.meet.webviewtoapp.MainActivity.loadWebView$lambda$2(MainActivity.kt:148)
        at com.coding.meet.webviewtoapp.MainActivity.$r8$lambda$17rajA2X3B1OvSyO9osdba9FNsQ(Unknown Source:0)
        at com.coding.meet.webviewtoapp.MainActivity$$ExternalSyntheticLambda1.onDownloadStart(Unknown Source:7)
        at g9.handleMessage(chromium-TrichromeWebViewGoogle6432.apk-stable-447211487:145)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7839)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
2024-06-03 17:39:19.433  1264-2123  AlarmManager            com.google.android.gms.persistent    W  alarm window unlikely to be respected [CONTEXT service_id=231 ] (Ask Gemini)
    java.lang.IllegalArgumentException: alarm "NetworkLocationLocator" has short window length
        at bngl.b(:com.google.android.gms@[email protected] (190800-633713831):52)
        at efni.f(:com.google.android.gms@[email protected] (190800-633713831):119)
        at efjs.r(:com.google.android.gms@[email protected] (190800-633713831):107)
        at efjs.f(:com.google.android.gms@[email protected] (190800-633713831):27)
        at efjp.apply(:com.google.android.gms@[email protected] (190800-633713831):19)
        at esdc.d(:com.google.android.gms@[email protected] (190800-633713831):3)
        at esdd.run(:com.google.android.gms@[email protected] (190800-633713831):42)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at bomx.nL(:com.google.android.gms@[email protected] (190800-633713831):1)
        at bomx.dispatchMessage(:com.google.android.gms@[email protected] (190800-633713831):138)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.os.HandlerThread.run(HandlerThread.java:67)

Можете ли вы опубликовать logcat в виде текста с полным журналом ошибок IllegalArgumentException?

Bob 03.06.2024 09:24

@BobSmith Я использую решение 1, которое оно скачало, поэтому я придерживаюсь этого решения больше, чем решения 2, которому я плохо следую: c РЕШЕНИЕ 1 РАБОТАЕТ. Он начал загружать файл PDF, однако в середине загрузки произошел сбой. в уведомлении моего эмулятора в файле указано, что загрузка не удалась.

Tengku Muhammad Muzaffar 03.06.2024 13:11

Проверьте это на реальном устройстве и поделитесь журналом ошибок неудачной загрузки.

Bob 03.06.2024 13:29
Пожалуйста, идите сюда, сэр - сказал я логкот. я постоянно получаю отчеты как спам :c
Tengku Muhammad Muzaffar 03.06.2024 17:28

Пожалуйста, оставляйте комментарии к ответам на ответ, а не на вопрос.

Kissaki 03.06.2024 17:53
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
5
100
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У меня была похожая проблема: к URL-адресу параметра onDownloadStart был добавлен blob. Это было просто в URL, поэтому я удалил его, и все работает нормально.

Решение 1:

private fun download(
    url: String,
    userAgent: String,
    contentDisposition: String,
    mimeType: String,
    contentLength: Long
) {
    val folder = File(Environment.getExternalStorageDirectory().toString() + "/Download/Image")
    if (!folder.exists()) {
        folder.mkdirs()
    }
    Toast.makeText(this, "Download Started", Toast.LENGTH_SHORT).show()

    // Remove the 'blob:' prefix if it exists
    val cleanUrl = url.replace("blob:", "")

    val request = DownloadManager.Request(Uri.parse(cleanUrl))
    request.setMimeType(mimeType)
    val cookie = CookieManager.getInstance().getCookie(cleanUrl)
    request.addRequestHeader("cookie", cookie)
    request.addRequestHeader("User-Agent", userAgent)
    request.setAllowedNetworkTypes(
        DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE
    )
    val fileName = URLUtil.guessFileName(cleanUrl, contentDisposition, mimeType)
    request.setTitle(fileName)
    request.setNotificationVisibility(
        DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
    )
    request.setDestinationInExternalPublicDir(
        Environment.DIRECTORY_DOWNLOADS,
        "Image/$fileName"
    )
    val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
    downloadManager.enqueue(request)
}

Примечание. Это простой способ сделать это.


Решение 2:

Другим решением было бы:

1. Получение данных Blob

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        view.loadUrl("javascript:(function() {" +
            "document.querySelector('a').addEventListener('click', function() {" +
            "   var xhr = new XMLHttpRequest();" +
            "   xhr.open('GET', this.href, true);" +
            "   xhr.responseType = 'blob';" +
            "   xhr.onload = function() {" +
            "       if (xhr.status === 200) {" +
            "           var reader = new FileReader();" +
            "           reader.onload = function() {" +
            "               var base64Data = reader.result.split(',')[1];" +
            "               Android.downloadBlob(base64Data, 'image/jpeg', 'myImage.jpg');" +
            "           };" +
            "           reader.readAsDataURL(xhr.response);" +
            "       }" +
            "   };" +
            "   xhr.send();" +
            "});" +
        "})();");
    }
});

2. Преобразование Blob в файл с помощью JavaScript

webView.addJavascriptInterface(new Object() {
    @JavascriptInterface
    public void downloadBlob(String base64Data, String mimeType, String fileName) {
        byte[] data = Base64.decode(base64Data, Base64.DEFAULT);
        saveFile(data, mimeType, fileName);
    }
}, "Android");

3. Загрузите файл.

private void saveFile(byte[] data, String mimeType, String fileName) {
    File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/Image");
    if (!folder.exists()) {
        folder.mkdirs();
    }
    File file = new File(folder, fileName);
    try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(data);
        Toast.makeText(this, "Download Complete", Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Notify the download manager of the new file
    DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
    downloadManager.addCompletedDownload(fileName, fileName, true, mimeType, file.getAbsolutePath(), data.length, true);
}

Используйте это, если мое первое решение вам не подходит, поскольку меньше кода всегда лучше, чем больше кода, тем больше вероятность ошибок и сбоев.

Другие вопросы по теме