Загрузить файл Blob с веб-сайта внутри Android WebViewClient
У меня есть веб-страница HTML с кнопкой, которая запускает запрос POST, когда пользователь нажимает на. Когда запрос выполнен, запускается следующий код:
window.open(fileUrl);
В браузере все прекрасно работает, но при реализации этого внутри компонента Webview новая вкладка не открывается.
К вашему сведению: в моем приложении для Android я установил следующее:
webview.getSettings().setJavaScriptEnabled(true);
webview.getSettings().setSupportMultipleWindows(true);
webview.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
На AndroidManifest.xml
У меня есть следующие разрешения:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
Я тоже пытаюсь с setDownloadListener
чтобы поймать загрузку. Другой подход был заменен WebViewClient()
за WebChromeClient()
но поведение было таким же.
3 ответа
Хорошо, у меня была такая же проблема при работе с веб-представлениями, я понял, что WebViewClient не может загружать "блоб-URL", как это делает клиент Chrome Desktop, я решил это с помощью интерфейсов Javascript. Вы можете сделать это, выполнив следующие действия. В этом приложении все работает нормально с помощью minSdkVersion: 17. Сначала преобразуйте данные URL-адреса BLOB-объекта в строку Base64 через JS. Во-вторых, отправьте эту строку в класс Java и, наконец, преобразуйте ее в доступный формат, в этом случае я преобразовал ее в файл ".pdf".
Обо всем по порядку. Вы должны настроить свое веб-представление, в моем случае я загружаю веб-страницы фрагментом:
public class WebviewFragment extends Fragment {
WebView browser;
...
// invoke this method after set your WebViewClient and ChromeClient
private void browserSettings() {
browser.getSettings().setJavaScriptEnabled(true);
browser.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
browser.loadUrl(JavaScriptInterface.getBase64StringFromBlobUrl(url));
}
});
browser.getSettings().setAppCachePath(getActivity().getApplicationContext().getCacheDir().getAbsolutePath());
browser.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
browser.getSettings().setDatabaseEnabled(true);
browser.getSettings().setDomStorageEnabled(true);
browser.getSettings().setUseWideViewPort(true);
browser.getSettings().setLoadWithOverviewMode(true);
browser.addJavascriptInterface(new JavaScriptInterface(getContext()), "Android");
browser.getSettings().setPluginState(PluginState.ON);
}
}
Имея это, давайте создадим JavaScriptInterface.class, у этого класса будет наш скрипт, который будет выполняться на нашей веб-странице.
public class JavaScriptInterface {
private Context context;
private NotificationManager nm;
public JavaScriptInterface(Context context) {
this.context = context;
}
@JavascriptInterface
public void getBase64FromBlobData(String base64Data) throws IOException {
convertBase64StringToPdfAndStoreIt(base64Data);
}
public static String getBase64StringFromBlobUrl(String blobUrl){
if(blobUrl.startsWith("blob")){
return "javascript: var xhr = new XMLHttpRequest();" +
"xhr.open('GET', 'YOUR BLOB URL GOES HERE', true);" +
"xhr.setRequestHeader('Content-type','application/pdf');" +
"xhr.responseType = 'blob';" +
"xhr.onload = function(e) {" +
" if (this.status == 200) {" +
" var blobPdf = this.response;" +
" var reader = new FileReader();" +
" reader.readAsDataURL(blobPdf);" +
" reader.onloadend = function() {" +
" base64data = reader.result;" +
" Android.getBase64FromBlobData(base64data);" +
" }" +
" }" +
"};" +
"xhr.send();";
}
return "javascript: console.log('It is not a Blob URL');";
}
private void convertBase64StringToPdfAndStoreIt(String base64PDf) throws IOException {
final int notificationId = 1;
String currentDateTime = DateFormat.getDateTimeInstance().format(new Date());
final File dwldsPath = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) + "/YourFileName_" + currentDateTime + "_.pdf");
byte[] pdfAsBytes = Base64.decode(base64PDf.replaceFirst("^data:application/pdf;base64,", ""), 0);
FileOutputStream os;
os = new FileOutputStream(dwldsPath, false);
os.write(pdfAsBytes);
os.flush();
if(dwldsPath.exists()) {
NotificationCompat.Builder b = new NotificationCompat.Builder(context, "MY_DL");
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_file_download_24dp)
.setContentTitle("MY TITLE")
.setContentText("MY TEXT CONTENT");
nm = (NotificationManager) this.context.getSystemService(Context.NOTIFICATION_SERVICE);
if(nm != null) {
nm.notify(notificationId, b.build());
Handler h = new Handler();
long delayInMilliseconds = 5000;
h.postDelayed(new Runnable() {
public void run() {
nm.cancel(notificationId);
}
}, delayInMilliseconds);
}
}
}
}
Источники:
/questions/42692936/kak-chitat-dannyie-v-blob-obekte-v-webview-na-android/42692941#42692941
/questions/19658422/kak-poluchit-fajl-ili-blob-iz-obekta-url/19658431#19658431
/questions/39250856/kak-konvertirovat-base64-v-pdf/39250861#39250861
Недавно я столкнулся с подобными проблемами на Android. Я смог найти обходной путь благодаря этой теме!
Я повторно использовал и реорганизовал приведенный выше фрагмент кода в Kotlin .
Объяснение : WebViewClient не может загрузить URL-адрес BLOB-объекта. Обходным решением может быть преобразование URL-адреса BLOB-объекта в объект BLOB-объекта, а затем в данные Base64 на веб-стороне. Нативная сторона загрузит вложение в данных Base64 в соответствии с типом mime, указанным в префиксе данных Base64.
JavascriptInterface.kt
import android.content.Context
import android.os.Environment
import android.util.Base64
import android.util.Log
import android.webkit.JavascriptInterface
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
class JavascriptInterface {
var context: Context;
constructor(context: Context) {
this.context = context;
}
/**
* Method to process Base64 data then save it locally.
*
* 1. Strip Base64 prefix from Base64 data
* 2. Decode Base64 data
* 3. Write Base64 data to file based on mime type located in prefix
* 4. Save file locally
*/
@JavascriptInterface
fun processBase64Data(base64Data: String) {
Log.i("JavascriptInterface/processBase64Data", "Processing base64Data ...")
var fileName = "";
var bytes = "";
if (base64Data.startsWith("data:image/png;base64,")) {
fileName = "foo.png"
bytes = base64Data.replaceFirst("data:image/png;base64,","")
}
if (fileName.isNotEmpty() && bytes.isNotEmpty()) {
val downloadPath = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
fileName
)
Log.i("JavascriptInterface/processBase64Data", "Download Path: ${downloadPath.absolutePath}")
val decodedString = Base64.decode(bytes, Base64.DEFAULT)
val os = FileOutputStream(downloadPath, false)
os.write(decodedString)
os.flush()
}
}
/**
* Method to convert blobUrl to Blob, then process Base64 data on native side
*
* 1. Download Blob URL as Blob object
* 2. Convert Blob object to Base64 data
* 3. Pass Base64 data to Android layer for processing
*/
fun getBase64StringFromBlobUrl(blobUrl: String): String {
Log.i("JavascriptInterface/getBase64StringFromBlobUrl", "Downloading $blobUrl ...")
// Script to convert blob URL to Base64 data in Web layer, then process it in Android layer
val script = "javascript: (() => {" +
"async function getBase64StringFromBlobUrl() {" +
"const xhr = new XMLHttpRequest();" +
"xhr.open('GET', '${blobUrl}', true);" +
"xhr.setRequestHeader('Content-type', 'image/png');" +
"xhr.responseType = 'blob';" +
"xhr.onload = () => {" +
"if (xhr.status === 200) {" +
"const blobResponse = xhr.response;" +
"const fileReaderInstance = new FileReader();" +
"fileReaderInstance.readAsDataURL(blobResponse);" +
"fileReaderInstance.onloadend = () => {" +
"console.log('Downloaded' + ' ' + '${blobUrl}' + ' ' + 'successfully!');" +
"const base64data = fileReaderInstance.result;" +
"Android.processBase64Data(base64data);" +
"}" + // file reader on load end
"}" + // if
"};" + // xhr on load
"xhr.send();" +
"}" + // async function
"getBase64StringFromBlobUrl();" +
"}) ()"
return script
}
}
MainActivity.kt
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v7.app.AppCompatActivity
import android.webkit.DownloadListener
import android.webkit.WebView
import java.net.URL
class MainActivity : AppCompatActivity() {
var debug = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Request permissions
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), PackageManager.PERMISSION_GRANTED);
val wv = findViewById<WebView>(R.id.web_view)
wv.settings.javaScriptEnabled = true
wv.settings.domStorageEnabled = true
// Load local .html with baseUrl set to production domain since attachment downloads does not work cross-origin
val queryParams = "foo=bar"
var url = URL(OmnichannelConfig.config["src"])
val baseUrl = "${url.protocol}://${url.host}?${queryParams}"
val data = application.assets.open("index.html").bufferedReader().use {
it.readText()
};
wv.loadDataWithBaseURL(baseUrl, data, "text/html", null, baseUrl)
// Expose Android methods to Javascript layer
val javascriptInterface = JavascriptInterface(applicationContext)
wv.addJavascriptInterface(javascriptInterface, "Android")
// Subscribe to notification when a file from Web content needs to be downloaded in Android layer
wv.setDownloadListener(DownloadListener { url, _, _, _, _ ->
if (url.startsWith("blob:")) {
wv.evaluateJavascript(javascriptInterface.getBase64StringFromBlobUrl(url), null)
}
})
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demo">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Примечание . Внутри метода processBase64Data JavascriptInterface.kt обрабатывается only . Требуется дополнительная реализация для обработки данных с различными типами mime (
data:application/pdf;base64,
,
data:image/gif;base64,
,
data:image/png;base64,
, так далее)
Недавно я столкнулся с этой проблемой и обнаружил, что решение состоит в том, чтобы манипулировать URL-адресом со строкой в моем setDownloadListener.
Например:
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url.replace("blob:","").trim()));
Я использую менеджер загрузок и манипулирую строкой URL и обрезаю ее, надеюсь, это поможет