有奖捉虫:办公协同&微信生态&物联网文档专题 HOT
Android 和 iOS 都推荐使用 webview 嵌入 H5 的方式来集成。具体可以参考《集成在线客服Web端》
Android 系统的 WebView 初始功能并不具备完备的浏览器能力。在线客服网站使用了 Cookie、LocalStoreage、文件上传、文件下载等需要浏览器交互的功能。因此需要将以上功能在 WebView 中实现,可以参考以下代码:
MainActivity.java
DownloadBlobFileJSInterface.java
package com.example.myapplication;

import android.app.Activity;
import android.app.DownloadManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.webkit.*;

import androidx.appcompat.app.AppCompatActivity;

import java.net.URL;

public class MainActivity extends AppCompatActivity {
private WebView webView;
private ValueCallback<Uri[]> filePathCallback;
private final int REQUEST_SELECT_FILE = 100;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);

webView.setWebViewClient(new WebViewClient());
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
// 这里我们使用了原生组件的Title,因此需要在View Title变化时,同步更新原生组件的Title
super.onReceivedTitle(view, title);
setTitle(title);
}

@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> callback, FileChooserParams fileChooserParams) {
// 网页中有文件上传控件时,会调用这个方法
filePathCallback = callback;
if (fileChooserParams.getAcceptTypes().length > 0 && fileChooserParams.getAcceptTypes()[0].startsWith("image/")) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/png", "image/jpeg", "image/gif", "image/webp", "image/bmp"});
Intent chooserIntent = Intent.createChooser(intent, "选择图片");
startActivityForResult(chooserIntent, REQUEST_SELECT_FILE);
} else {
Intent intent = fileChooserParams.createIntent();
Intent chooserIntent = Intent.createChooser(intent, "选择文件");
startActivityForResult(chooserIntent, REQUEST_SELECT_FILE);
}
return true;
}
});

WebSettings webSettings = webView.getSettings();
DownloadBlobFileJSInterface mDownloadBlobFileJSInterface = new DownloadBlobFileJSInterface(this);
// DownloadManager只支持http和https协议,因此需要在这里使用mDownloadBlobFileJSInterface处理blob协议
webView.addJavascriptInterface(mDownloadBlobFileJSInterface, "Android");
webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
if (url.startsWith("http://") || url.startsWith("https://")) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "download");
DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
dm.enqueue(request);
} else if (url.startsWith("blob:")) {
String filename = url.split("\\\\?filename=")[1];
webView.loadUrl(mDownloadBlobFileJSInterface.getBase64StringFromBlobUrl(url, filename));
}
});

// 打开LocalStorage
webSettings.setDomStorageEnabled(true);
// 允许Js支持
webSettings.setJavaScriptEnabled(true);
// Cookie支持
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.setAcceptThirdPartyCookies(webView, true);
// 允许访问文件
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setAllowContentAccess(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
// 加载网站(隐藏标题栏使用参数 hideHeader=true)
webView.loadUrl("https://tccc.qcloud.com/web/im/chat/?webAppId=9903496b6be2e57ad291dd2333f3ad9f&hideHeader=true");
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_SELECT_FILE) {
if (filePathCallback == null) {
return;
}
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
filePathCallback.onReceiveValue(new Uri[]{});
} else {
filePathCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
}
} else {
filePathCallback.onReceiveValue(null);
}
filePathCallback = null;
}
}
}

package com.example.myapplication;


import static androidx.core.app.ActivityCompat.requestPermissions;
import static androidx.core.content.PermissionChecker.checkSelfPermission;


import android.Manifest;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Base64;
import android.webkit.JavascriptInterface;
import android.webkit.URLUtil;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.core.content.PermissionChecker;


import java.io.File;
import java.io.FileOutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DownloadBlobFileJSInterface {


private Context mContext;
private static final int REQUEST_CODE = 200;
private static String mFileName;
private DownloadFileSuccessListener mDownloadFileSuccessListener;
private Pattern pattern = Pattern.compile("^data:(.*);base64,");


public DownloadBlobFileJSInterface(Context context) {
this.mContext = context;
}


public void setDownloadFileSuccessListener(DownloadFileSuccessListener listener) {
mDownloadFileSuccessListener = listener;
}


@RequiresApi(api = Build.VERSION_CODES.N)
@JavascriptInterface
public void getBase64FromBlobData(String base64Data, String blobUrl) {
convertToFileAndProcess(base64Data, blobUrl);
}


public static String getBase64StringFromBlobUrl(String blobUrl, String filename) {
mFileName = filename;
if (blobUrl.startsWith("blob")) {
return "javascript: var xhr = new XMLHttpRequest();" +
"xhr.open('GET', '" + blobUrl + "', true);" +
"xhr.setRequestHeader('Content-type','*');" +
"xhr.responseType = 'blob';" +
"xhr.onload = function(e) {" +
" if (this.status == 200) {" +
" var blobFile = this.response;" +
" var reader = new FileReader();" +
" reader.readAsDataURL(blobFile);" +
" reader.onloadend = function() {" +
" base64data = reader.result;" +
" Android.getBase64FromBlobData(base64data, '" + blobUrl + "');" +
" }" +
" }" +
"};" +
"xhr.send();";
}
return "javascript: console.log('It is not a Blob URL');";
}


@RequiresApi(api = Build.VERSION_CODES.N)
private void convertToFileAndProcess(String base64Str, String blobUrl) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
requestPermissions((MainActivity) mContext, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
return;
}
}
Matcher matcher = pattern.matcher(base64Str);
if(matcher.find()){
String mimeType = matcher.group(1);
String base64String = matcher.replaceFirst("");
if(mFileName.isEmpty()) {
mFileName = URLUtil.guessFileName(blobUrl,null, mimeType);
}
File stlFile = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) , mFileName);
saveFileToPath(base64String, stlFile);
Toast.makeText(mContext, "文件" + mFileName + "已下载成功", Toast.LENGTH_SHORT).show();
if (mDownloadFileSuccessListener != null) {
mDownloadFileSuccessListener.downloadFileSuccess(stlFile.getAbsolutePath());
}
}
}


private void saveFileToPath(String base64, File filePath) {
try {

byte[] fileBytes = Base64.decode(base64,0);
FileOutputStream os = new FileOutputStream(filePath, false);
os.write(fileBytes);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}


public interface DownloadFileSuccessListener {
void downloadFileSuccess(String absolutePath);
}
}