项目说明:最近在开发Android原生嵌套H5实现混合开发,刚开始采用原生的WebView各种兼容性问题,之后决定采用腾讯的x5浏览器来开发,遇到的一些问题列一下:

  • x5Webview与H5的交互问题
  • x5同步cookie问题
  • WebView加载进度条问题处理
  • H5调用Android摄像头进行录制视频、H5调用Android相机进行拍照
  • x5WebView-WebChromeClient的方法onShowFileChooser只执行一次的问题
  • X5WebView的Setting需要配置那些东西

先看效果图


x5webview集成传送门
x5webview同步cookie问题传送门

1、Android集成X5内核

 public class MyApplication extends BaseApplication {@Overridepublic void onCreate() {super.onCreate();preInitX5Core();}private void preInitX5Core() {//预加载x5内核Intent intent = new Intent(this, X5NetService.class);startService(intent);}
}
public class X5NetService extends IntentService {public static final String TAG = LogTAG.x5webview;public X5NetService(){super(TAG);}public X5NetService(String name) {super(TAG);}@Overridepublic void onHandleIntent(@Nullable Intent intent) {initX5Web();//初始化}public void initX5Web() {if (!QbSdk.isTbsCoreInited()) {// 设置X5初始化完成的回调接口QbSdk.preInit(getApplicationContext(), null);}QbSdk.initX5Environment(getApplicationContext(), cb);}QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {@Overridepublic void onViewInitFinished(boolean arg0) {// TODO Auto-generated method stub}@Overridepublic void onCoreInitFinished() {// TODO Auto-generated method stub}};
}
//自定义x5WebView
public class X5WebView extends WebView {private static final String TAG = "x5webview";int progressColor = 0xFFFF4081;ProgressView mProgressview; //自定义WebView加载进度条TextView title;Context context;@SuppressLint("SetJavaScriptEnabled")public X5WebView(Context arg0, AttributeSet arg1) {super(arg0, arg1);this.context = arg0;initWebViewSettings();//初始化setting配置this.setWebViewClient(client) ;this.setWebChromeClient(chromeClient);this.setDownloadListener(downloadListener);initProgressBar();//初始化进度条this.getView().setClickable(true);}//setting配置private void initWebViewSettings() {WebSettings webSetting = this.getSettings();webSetting.setJavaScriptEnabled(true);//允许js调用webSetting.setJavaScriptCanOpenWindowsAutomatically(true);//支持通过JS打开新窗口 webSetting.setAllowFileAccess(true);//在File域下,能够执行任意的JavaScript代码,同源策略跨域访问能够对私有目录文件进行访问等webSetting.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);//控制页面的布局(使所有列的宽度不超过屏幕宽度)webSetting.setSupportZoom(true);//支持页面缩放webSetting.setBuiltInZoomControls(true);//进行控制缩放webSetting.setAllowContentAccess(true);//是否允许在WebView中访问内容URL(Content Url),默认允许webSetting.setUseWideViewPort(true);//设置缩放密度webSetting.setSupportMultipleWindows(false);//设置WebView是否支持多窗口,如果为true需要实现onCreateWindow(WebView, boolean, boolean, Message)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//两者都可以webSetting.setMixedContentMode(webSetting.getMixedContentMode());//设置安全的来源}webSetting.setAppCacheEnabled(true);//设置应用缓存webSetting.setDomStorageEnabled(true);//DOM存储API是否可用webSetting.setGeolocationEnabled(true);//定位是否可用webSetting.setLoadWithOverviewMode(true);//是否允许WebView度超出以概览的方式载入页面,webSetting.setAppCacheMaxSize(Long.MAX_VALUE);//设置应用缓存内容的最大值webSetting.setPluginState(WebSettings.PluginState.ON_DEMAND);//设置是否支持插件webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE);//重写使用缓存的方式webSetting.setAllowUniversalAccessFromFileURLs(true);//是否允许运行在一个file schema URL环境下的JavaScript访问来自其他任何来源的内容webSetting.setAllowFileAccessFromFileURLs(true);//是否允许运行在一个URL环境}//进度条private void initProgressBar() {mProgressview = new ProgressView(context);mProgressview.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 6));mProgressview.setDefaultColor(progressColor);addView(mProgressview);}//客户端配置private WebViewClient client = new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(com.tencent.smtt.sdk.WebView view, String url) {//这里直接加载urlview.loadUrl(url);return true;}@Overridepublic void onPageStarted(WebView webView, String s, Bitmap bitmap) {super.onPageStarted(webView, s, bitmap);}@Overridepublic void onPageFinished(com.tencent.smtt.sdk.WebView view, String url) {//处理客户端与WebView同步,具体细节问题请看最上面传送门CookieManager cookieManager = CookieManager.getInstance();cookieManager.setAcceptCookie(true);String endCookie = cookieManager.getCookie(url);Log.i(TAG, "onPageFinished: endCookie : " + endCookie);if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {CookieSyncManager.getInstance().sync();//同步cookie } else {CookieManager.getInstance().flush();}super.onPageFinished(view, url);}@Overridepublic void onReceivedError(WebView webView, int i, String s, String s1) {super.onReceivedError(webView, i, s, s1);//网页问题报错的时候执行webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);webView.setVisibility(View.VISIBLE);}@Overridepublic void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {super.onReceivedSslError(webView, sslErrorHandler, sslError);if(sslError.getPrimaryError() == android.net.http.SslError.SSL_INVALID ){// 校验过程遇到了bug//这里直接忽略ssl证书的检测出错问题,选择继续执行页面sslErrorHandler.proceed();}else{//不是证书问题时候则停止执行加载页面sslErrorHandler.cancel();}}};//x5浏览器配置可视频播放、文件下载private WebChromeClient chromeClient = new WebChromeClient() {@Overridepublic boolean onJsConfirm(com.tencent.smtt.sdk.WebView arg0, String arg1, String arg2,com.tencent.smtt.export.external.interfaces.JsResult arg3) {return super.onJsConfirm(arg0, arg1, arg2, (com.tencent.smtt.export.external.interfaces.JsResult) arg3);}View myVideoView;View myNormalView;IX5WebChromeClient.CustomViewCallback callback;/*** 全屏播放配置*/@Overridepublic void onShowCustomView(View view,IX5WebChromeClient.CustomViewCallback customViewCallback) {FrameLayout normalView = null;ViewGroup viewGroup = (ViewGroup) normalView.getParent();viewGroup.removeView(normalView);viewGroup.addView(view);myVideoView = view;myNormalView = normalView;callback = customViewCallback;}@Overridepublic void onHideCustomView() {if (callback != null) {callback.onCustomViewHidden();callback = null;}if (myVideoView != null) {ViewGroup viewGroup = (ViewGroup) myVideoView.getParent();viewGroup.removeView(myVideoView);viewGroup.addView(myNormalView);}}@Overridepublic void onProgressChanged(com.tencent.smtt.sdk.WebView webView, int i) {super.onProgressChanged(webView, i);mProgressview.setProgress(i);}@Overridepublic boolean onJsAlert(com.tencent.smtt.sdk.WebView arg0, String arg1, String arg2,com.tencent.smtt.export.external.interfaces.JsResult arg3) {/*** 这里写入你自定义的window alert*/return super.onJsAlert(null, arg1, arg2, arg3);}};//下载监听器DownloadListener downloadListener = new DownloadListener() {@Overridepublic void onDownloadStart(String arg0, String arg1, String arg2,String arg3, long arg4) {new AlertDialog.Builder(context).setTitle("allow to download?").setPositiveButton("yes",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {Toast.makeText(context,"fake message: i'll download...",Toast.LENGTH_LONG).show();}}).setNegativeButton("no",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {// TODO Auto-generated method stubToast.makeText(context,"fake message: refuse download...",Toast.LENGTH_SHORT).show();}}).setOnCancelListener(new DialogInterface.OnCancelListener() {@Overridepublic void onCancel(DialogInterface dialog) {// TODO Auto-generated method stubToast.makeText(context,"fake message: refuse download...",Toast.LENGTH_SHORT).show();}}).show();}};@Overrideprotected boolean drawChild(Canvas canvas, View child, long drawingTime) {boolean ret = super.drawChild(canvas, child, drawingTime);canvas.save();Paint paint = new Paint();paint.setColor(0x7fff0000);paint.setTextSize(24.f);paint.setAntiAlias(true);canvas.restore();return ret;}public X5WebView(Context arg0) {super(arg0);setBackgroundColor(85621);}
}
//进度条
public class ProgressView extends View {int defaultColor = 0xFFFF4081;Paint progressPaint = null;Paint progressCircle = null;int currentProgress = 0;int totalProgress = 0;boolean isHide = false;public ProgressView(Context context) {this(context, null);}public ProgressView(Context context, AttributeSet attrs) {this(context, attrs, -1);}public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {setLayerType(LAYER_TYPE_SOFTWARE, null);progressPaint = new Paint();progressPaint.setColor(defaultColor);progressCircle = new Paint();progressCircle.setColor(defaultColor);progressCircle.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));}int viewWidth = 0;int viewHeight = 0;@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);viewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();viewHeight = getMeasuredHeight();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if(currentProgress<=100&&isHide){isHide = false;this.setAlpha(1);}canvas.drawRect(0, 0, (float) (viewWidth * (currentProgress / 100.0)), viewHeight, progressPaint);canvas.drawCircle((float) (viewWidth * (currentProgress / 100.0)) - viewHeight / 2, viewHeight / 2, viewHeight, progressCircle);if (currentProgress >= 100) {hideSelf();}}private void hideSelf() {this.postDelayed(new Runnable() {@Overridepublic void run() {ViewCompat.animate(ProgressView.this).alpha(0);isHide=true;ProgressView.this.currentProgress = 0;}}, 100);}public int getDefaultColor() {return defaultColor;}public void setDefaultColor(int defaultColor) {this.defaultColor = defaultColor;}ValueAnimator animator;public void setProgress(int progress) {totalProgress = progress;if (animator != null) {if (animator.isRunning()) {animator.cancel();}}animator = ValueAnimator.ofInt(currentProgress, totalProgress);animator.setDuration(300);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {currentProgress = (int) animation.getAnimatedValue();invalidate();}});animator.start();}}

2、Android同步cookie

//这里直接采用okhttp做同步cookie操作,具体可看上面cookie同步传送门
public class OkHttpRequestUtil {private volatile static OkHttpRequestUtil netRequest;private static OkHttpClient okHttpClient; // OKHttp网络请求private Handler mHandler;final String TAG = LogTAG.okhttp;private boolean checkNet;private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();private OkHttpRequestUtil() {okHttpClient = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)).addInterceptor(new AddCookiesInterceptor())//这里是关键!!!.addInterceptor(new SaveCookiesInterceptor())//这里是关键!!!.cookieJar(new SaCookieManger(MyApplication.context()))//这里是关键!!!.build();mHandler = new Handler(Looper.getMainLooper());}private static OkHttpRequestUtil getInstance() {if (netRequest == null) {netRequest = new OkHttpRequestUtil();}return netRequest;}/*** 异步get请求(Form),内部实现方法* @param url    url* @param params key value*/public void inner_GetFormAsync(String url, Map<String, String> params, final DataCallBack callBack) {if (params == null) {params = new HashMap<>();}final String doUrl = urlJoint(url, params);final Request request = new Request.Builder().url(doUrl).build();Call call = okHttpClient.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {deliverDataFailure(request, e, callBack);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (response != null && response.isSuccessful()) {String result = response.body().string();deliverDataSuccess(result, callBack);} else {throw new IOException(response + "");}}});}/*** get请求  没有请求体** @param url* @param callBack*/private void getMethod(String url, final DataCallBack callBack) {final Request req = new Request.Builder().url(url).build();okHttpClient.newCall(req).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {deliverDataFailure(req, e, callBack);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (response != null && response.isSuccessful()) {String result = response.body().string();deliverDataSuccess(result, callBack);} else {deliverDataSuccess("请求异常", callBack);}}});}/*** 异步post请求(Form),内部实现方法* @param url      url* @param params   params* @param callBack callBack*/private void inner_PostFormAsync(String url, Map<String, String> params, final DataCallBack callBack) {RequestBody requestBody;if (params == null) {params = new HashMap<>();}FormBody.Builder builder = new FormBody.Builder();/*** 在这对添加的参数进行遍历*/for (Map.Entry<String, String> map : params.entrySet()) {String key = map.getKey();String value;/*** 判断值是否是空的*/if (map.getValue() == null) {value = "";} else {value = map.getValue();}/*** 把key和value添加到formbody中*/builder.add(key, value);}requestBody = builder.build();final Request request = new Request.Builder().url(url).post(requestBody).build();okHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {deliverDataFailure(request, e, callBack);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (response.isSuccessful()) { // 请求成功Headers newHead = response.networkResponse().request().headers();Log.i(TAG, "new headers :: " + newHead);//执行请求成功的操作String result = response.body().string();deliverDataSuccess(result, callBack);} else {throw new IOException(response + "");}}});}private void inner_PostJsonAsync(String url, Map<String, String> params, final DataCallBack callBack) {// 将map转换成json,需要引入Gson包String mapToJson = new Gson().toJson(params);final Request request = buildJsonPostRequest(url, mapToJson);okHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {deliverDataFailure(request, e, callBack);}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (response.isSuccessful()) { // 请求成功//执行请求成功的操作String result = response.body().string();deliverDataSuccess(result, callBack);} else {throw new IOException(response + "");}}});}private Request buildJsonPostRequest(String url, String json) {RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);return new Request.Builder().url(url).post(requestBody).build();}/*** 分发失败的时候调用* 客户端没有网络 还是 服务器异常* @param request  request* @param e        e* @param callBack callBack*/private void deliverDataFailure(final Request request, final IOException e, final DataCallBack callBack) {/*** 在这里使用异步处理*/checkNet = CheckNetUtil.checkNet(NRApplication.context());mHandler.post(new Runnable() {@Overridepublic void run() {if (callBack != null) {try {if (checkNet) {callBack.requestFailure(request, e);} else {callBack.requestNoNet(ProjectDataDescribe.NET_NO_LINKING,ProjectDataDescribe.NET_NO_LINKING);}} catch (Exception e) {}}}});}/*** 分发成功的时候调用** @param result   result* @param callBack callBack*/private void deliverDataSuccess(final String result, final DataCallBack callBack) {/*** 在这里使用异步线程处理*/mHandler.post(new Runnable() {@Overridepublic void run() {if (callBack != null) {try {callBack.requestSuccess(result);} catch (Exception e) {e.printStackTrace();}}}});}/*** 数据回调接口*/public interface DataCallBack {//       请求成功 响应成功void requestSuccess(String result) throws Exception;
//      请求失败  响应失败void requestFailure(Request request, IOException e);
//      客户端没有网络连接void requestNoNet(String msg, String data);}/*** 拼接url和请求参数** @param url    url* @param params key value* @return String url*/private static String urlJoint(String url, Map<String, String> params) {StringBuilder endUrl = new StringBuilder(url);boolean isFirst = true;Set<Map.Entry<String, String>> entrySet = params.entrySet();for (Map.Entry<String, String> entry : entrySet) {if (isFirst && !url.contains("?")) {isFirst = false;endUrl.append("?");} else {endUrl.append("&");}endUrl.append(entry.getKey());endUrl.append("=");endUrl.append(entry.getValue());}return endUrl.toString();}//-------------对外提供的方法Start--------------------------------/*** 建立网络框架,获取网络数据,异步get请求(Form)** @param url      url* @param params   key value* @param callBack data*/public static void okGetFormRequest(String url, Map<String, String> params, DataCallBack callBack) {getInstance().inner_GetFormAsync(url, params, callBack);}/*** 建立网络框架,获取网络数据,异步post请求(Form)* @param url      url* @param params   key value* @param callBack data*/public static void okPostFormRequest(String url, Map<String, String> params, DataCallBack callBack) {getInstance().inner_PostFormAsync(url, params, callBack);}/*** get 请求* 没有请求体*/public static void okGetRequest(String url, DataCallBack callBack) {getInstance().getMethod(url, callBack);}}
public class SaCookieManger implements CookieJar {private static final String TAG = LogTAG.cookie;private static Context mContext;public SaCookieManger(Context context) {mContext = context;}@Overridepublic void saveFromResponse(HttpUrl url, List<Cookie> cookies) {SaasCookieManager.loadCookie(cookies,url.host());}@Overridepublic List<Cookie> loadForRequest(HttpUrl url) {return new ArrayList<>();}
}
public class AddCookiesInterceptor implements Interceptor {private static final String COOKIE_PREF = "cookies_prefs";@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Request.Builder builder = request.newBuilder();String cookie = getCookie(request.url().toString(), request.url().host());if (!TextUtils.isEmpty(cookie)) {builder.addHeader("Cookie", cookie);Log.i(LogTAG.cookie, "interceptor addHeader Cookie: "+cookie);}return chain.proceed(builder.build());}private String getCookie(String url, String domain) {SharedPreferences sp = MyApplication.context().getSharedPreferences(COOKIE_PREF,Context.MODE_PRIVATE);String cookie = sp.getString(domain, "");Log.i(LogTAG.cookie, "interceptor getCookie: "+cookie);if (!TextUtils.isEmpty(domain) && sp.contains(domain) && !TextUtils.isEmpty(sp.getString(domain, ""))) {return sp.getString(domain, "");}return null;}}
/*** 存储cookie拦截器*/
public class SaveCookiesInterceptor implements Interceptor {private static final String COOKIE_PREF = "cookies_prefs";@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Response response = chain.proceed(request);if (!response.headers("set-cookie").isEmpty()) {List<String> cookies = response.headers("set-cookie");String cookie = encodeCookie(cookies);saveCookie(request.url().toString(), request.url().host(), cookie);}return response;}/*** 整合cookie为唯一字符串* @param cookies* @return*/private String encodeCookie(List<String> cookies) {StringBuilder sb = new StringBuilder();List<String> set = new ArrayList<>();for (String cookie : cookies) {String[] arr = cookie.split(";");for (String s : arr) {if (set.contains(s)) {continue;}set.add(s);}}Iterator<String> ite = set.iterator();while (ite.hasNext()) {String cookie = ite.next();sb.append(cookie).append(";");}int last = sb.lastIndexOf(";");if (sb.length() - 1 == last) {sb.deleteCharAt(last);}return sb.toString();}/*** 持久化cookie*/private void saveCookie(String url, String domain, String cookies) {SharedPreferences sp = MyApplication.context().getSharedPreferences(COOKIE_PREF,Context.MODE_PRIVATE);SharedPreferences.Editor editor = sp.edit();if (TextUtils.isEmpty(url)) {throw new NullPointerException("url is null.");}else{editor.putString(url, cookies);}if (!TextUtils.isEmpty(domain)) {editor.putString(domain, cookies);}editor.apply();}}
//登陆请求
public class SimpleLogin {//TODO 账号密码String userName = bean.getUserName();String passWord = bean.getPassWord();HashMap<String,String> map = new HashMap<>();map.put("username",userName);map.put("password",passWord);//TODO 网络请求OkHttpRequestUtil.okPostFormRequest(HttpUrlConstance.APP_LOGIN, map, new OkHttpRequestUtil.DataCallBack() {@Overridepublic void requestSuccess(String result) throws Exception {//TODO 成功JSONObject j = new JSONObject(result);//TODO 具体业务处理}@Overridepublic void requestFailure(Request request, IOException e) {//TODO 失败}@Overridepublic void requestNoNet(String msg, String data) {//TODO 网络问题}});
}

3、X5WebView调用Android摄像头、相机进行录像、拍照

/*** WebView渲染Activity** @author wenhua.qin*/
public class BrowserActivity extends BaseActivity {private ValueCallback<Uri> uploadFile;private ValueCallback<Uri[]> uploadFiles;public String TAG = "AABBCC";private int mResultCode = Activity.RESULT_CANCELED;private X5WebView mWebView;private String mHomeUrl = "";@BindView(R.id.webView1)ViewGroup mViewParent;@Overrideprotected int getContentView() {return R.layout.activity_browser;}@Overrideprotected void initData() {super.initData();Intent intent = getIntent();if (intent != null) {mHomeUrl = (String) intent.getExtras().get("url"); //传入的网页}try {if (Integer.parseInt(android.os.Build.VERSION.SDK) >= 11) {getWindow().setFlags(android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);}} catch (Exception e) {}init();}@Overrideprotected boolean getSwipeBack() {return true;}private void init() {mWebView = new X5WebView(this, null);WebSettings webSetting = mWebView.getSettings();webSetting.setAppCachePath(this.getDir("appcache", 0).getPath());  //设置应用缓存目录webSetting.setDatabasePath(this.getDir("databases", 0).getPath()); //设置数据库缓存路径webSetting.setGeolocationDatabasePath(this.getDir("geolocation", 0).getPath());//设置定位的数据库路径mViewParent.addView(mWebView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT,FrameLayout.LayoutParams.FILL_PARENT));mWebView.addJavascriptInterface(new WebContrl(this, mWebView), "webCtrl");//与js进行交互mWebView.setWebChromeClient(chromeClient);mWebView.loadUrl(mHomeUrl);}@Overridepublic void onDestroy() { //销毁时候需要处理Webview移除if (mWebView != null && mWebView.getParent() != null) {((ViewGroup) mWebView.getParent()).removeView(mWebView);mWebView.destroy();mWebView = null;ViewGroup view = (ViewGroup) getWindow().getDecorView();view.removeAllViews();}super.onDestroy();}//处于onPause、onStop状态需要重写onNewIntent方法@Overrideprotected void onNewIntent(Intent intent) {if (intent == null || mWebView == null || intent.getData() == null)return;mWebView.loadUrl(intent.getExtras().getString("url"));}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK) {//返回键监听 回滚H5页面if (mWebView != null && mWebView.canGoBack()) {mWebView.goBack();if (Integer.parseInt(android.os.Build.VERSION.SDK) >= 16)changGoForwardButton(mWebView);return true;} elsereturn super.onKeyDown(keyCode, event);}return super.onKeyDown(keyCode, event);}private void changGoForwardButton(com.tencent.smtt.sdk.WebView view) {}//当前涉及调用拍照、摄像功能,需要重新设置WebChromeClientprivate WebChromeClient chromeClient = new WebChromeClient() {public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {}public void openFileChooser(ValueCallback<Uri> uploadMsgs) {}// For Android  > 4.1.1public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {}// For Android  >= 5.0 该项目需求是在5.0之上开发、所以5.0以下不作处理public boolean onShowFileChooser(com.tencent.smtt.sdk.WebView webView,ValueCallback<Uri[]> filePathCallback,final WebChromeClient.FileChooserParams fileChooserParams) {uploadFiles = filePathCallback;new ActionSheetDialog(BrowserActivity.this).builder(uploadFile,uploadFiles)//这里是重点!!!,需要传入uploadFile,uploadFiles进行判断处理.setCancelable(true) //取消键.setCanceledOnTouchOutside(true)//空白地方取消dialog.addSheetItem("上传照片",ActionSheetDialog.SheetItemColor.Blue,new ActionSheetDialog.OnSheetItemClickListener() {@Overridepublic void onClick(int which) {take();}}).addSheetItem("上传视频",ActionSheetDialog.SheetItemColor.Blue,new ActionSheetDialog.OnSheetItemClickListener() {@Overridepublic void onClick(int which) {Toast.makeText(BrowserActivity.this, "调用视频", Toast.LENGTH_SHORT).show();Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//限制时长intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);//开启摄像机startActivityForResult(intent, 101);}})//                    .addSheetItem("调用相册",
//                            ActionSheetDialog.SheetItemColor.Blue,
//                            new ActionSheetDialog.OnSheetItemClickListener() {
//                                @Override
//                                public void onClick(int which) {
//                                    Toast.makeText(BrowserActivity.this, "调用相册", Toast.LENGTH_SHORT).show();
//                                    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
//                                    i.addCategory(Intent.CATEGORY_OPENABLE);
//                                    i.setType("image/*");
//                                    startActivityForResult(Intent.createChooser(i, "选择相册"), 102);
//                                }
//                    }).show();return true;}};public boolean flag = true;@SuppressWarnings("null")@TargetApi(Build.VERSION_CODES.LOLLIPOP)private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {if (requestCode != 100|| uploadFiles == null) {return;}Uri[] results = null;if (resultCode == Activity.RESULT_OK) {if (data == null) {results = new Uri[]{imageUri};} else {String dataString = data.getDataString();ClipData clipData = data.getClipData();if (clipData != null) {results = new Uri[clipData.getItemCount()];for (int i = 0; i < clipData.getItemCount(); i++) {ClipData.Item item = clipData.getItemAt(i);results[i] = item.getUri();}}if (dataString != null)results = new Uri[]{Uri.parse(dataString)};}}if(results!=null){uploadFiles.onReceiveValue(results);uploadFiles = null;}else{results = new Uri[]{imageUri};uploadFiles.onReceiveValue(results);uploadFiles = null;}return;}private void take(){File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyApp");// Create the storage directory if it does not existif (! imageStorageDir.exists()){imageStorageDir.mkdirs();}File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");imageUri = Uri.fromFile(file);final List<Intent> cameraIntents = new ArrayList<Intent>();final Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);final PackageManager packageManager = getPackageManager();final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);for(ResolveInfo res : listCam) {final String packageName = res.activityInfo.packageName;final Intent i = new Intent(captureIntent);i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));i.setPackage(packageName);i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);cameraIntents.add(i);}Intent i = new Intent(Intent.ACTION_GET_CONTENT);i.addCategory(Intent.CATEGORY_OPENABLE);i.setType("image/*");Intent chooserIntent = Intent.createChooser(i,"请选择相册或者拍照");chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));BrowserActivity.this.startActivityForResult(chooserIntent,  100);}Uri result;@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK) {switch (requestCode) {case 100://相片 拍照片if (null == uploadFile && null == uploadFiles) return;Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();if (uploadFiles != null) {onActivityResultAboveL(requestCode, resultCode, data);}else  if (uploadFile != null) {Log.e("result",result+"");if(result==null){uploadFile.onReceiveValue(imageUri);uploadFile = null;Log.e("imageUri",imageUri+"");}else {uploadFile.onReceiveValue(result);uploadFile = null;}}flag =  true;break;case 101://相机 拍摄视频if (null == uploadFile && null == uploadFiles) {Log.d(TAG, "onActivityResult null");return;}result = data == null || resultCode != RESULT_OK ? null : data.getData();Log.d(TAG, "onActivityResult path=" + result.getPath());if (uploadFiles != null) {if (resultCode == RESULT_OK) {Log.d(TAG, "onActivityResult 1");uploadFiles.onReceiveValue(new Uri[]{result});} else {Log.d(TAG, "onActivityResult 2");uploadFiles.onReceiveValue(new Uri[]{});uploadFiles = null;}} else if (uploadFile != null) {if (resultCode == RESULT_OK) {uploadFile.onReceiveValue(result);uploadFile = null;} else {Log.d(TAG, "onActivityResult 4");uploadFile.onReceiveValue(Uri.EMPTY);uploadFile = null;}}break;case 102:if (null != uploadFile) {result = data == null || resultCode != RESULT_OK ? null: data.getData();uploadFile.onReceiveValue(result);uploadFile = null;}if (null != uploadFiles) {result = data == null || resultCode != RESULT_OK ? null: data.getData();uploadFiles.onReceiveValue(new Uri[]{result});uploadFiles = null;}break;default:break;}} else if (resultCode == RESULT_CANCELED) {if (null != uploadFile) {uploadFile.onReceiveValue(null);uploadFile = null;}}}private Uri imageUri;@Overrideprotected void onResume() {super.onResume();// 取消选择时需要回调onReceiveValue,否则网页会挂住,不会再响应点击事件if (mResultCode == Activity.RESULT_CANCELED) {try {if (uploadFiles != null) {uploadFiles.onReceiveValue(null);}if (uploadFile != null) {uploadFile.onReceiveValue(null);}} catch (Exception e) {e.printStackTrace();}}}
}
//采用ios风格弹出框,进行多项选择(调用相机、调用摄像)
public class ActionSheetDialog {private Context context;private Dialog dialog;private TextView txt_title;private TextView txt_cancel;private LinearLayout lLayout_content;private ScrollView sLayout_content;private boolean showTitle = false;private List<SheetItem> sheetItemList;private Display display;public ActionSheetDialog(Context context) {this.context = context;WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);display = windowManager.getDefaultDisplay();}public ActionSheetDialog builder(final  ValueCallback<Uri> uploadFile,final ValueCallback<Uri[]>  uploadFiles) {// 获取Dialog布局View view = LayoutInflater.from(context).inflate(R.layout.view_actionsheet, null);// 设置Dialog最小宽度为屏幕宽度view.setMinimumWidth(display.getWidth());// 获取自定义Dialog布局中的控件sLayout_content = (ScrollView) view.findViewById(R.id.sLayout_content);lLayout_content = (LinearLayout) view.findViewById(R.id.lLayout_content);txt_title = (TextView) view.findViewById(R.id.txt_title);txt_cancel = (TextView) view.findViewById(R.id.txt_cancel);txt_cancel.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {  //这里如果不做处理,onShowFileChooser只会执行一次
//                  Toast.makeText(context,"按钮取消",100).show();if (uploadFiles != null) {uploadFiles.onReceiveValue(null);}if (uploadFile != null) {uploadFile.onReceiveValue(null);}dialog.dismiss();}});// 定义Dialog布局和参数dialog = new Dialog(context, R.style.ActionSheetDialogStyle);dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {@Overridepublic void onCancel(DialogInterface dialogInterface) {//这里如果不做处理,onShowFileChooser只会执行一次
//                  Toast.makeText(context,"点击屏幕取消",100).show();if (uploadFiles != null) {uploadFiles.onReceiveValue(null);}if (uploadFile != null) {uploadFile.onReceiveValue(null);}}});dialog.setContentView(view);Window dialogWindow = dialog.getWindow();dialogWindow.setGravity(Gravity.LEFT | Gravity.BOTTOM);WindowManager.LayoutParams lp = dialogWindow.getAttributes();lp.x = 0;lp.y = 0;dialogWindow.setAttributes(lp);return this;}public ActionSheetDialog setTitle(String title) {showTitle = true;txt_title.setVisibility(View.VISIBLE);txt_title.setText(title);return this;}public ActionSheetDialog setCancelable(boolean cancel) {dialog.setCancelable(cancel);return this;}public ActionSheetDialog setCanceledOnTouchOutside(boolean cancel) {dialog.setCanceledOnTouchOutside(cancel);return this;}/**** @param strItem*            条目名称* @param color*            条目字体颜色,设置null则默认蓝色* @param listener* @return*/public ActionSheetDialog addSheetItem(String strItem, SheetItemColor color,OnSheetItemClickListener listener) {if (sheetItemList == null) {sheetItemList = new ArrayList<SheetItem>();}sheetItemList.add(new SheetItem(strItem, color, listener));return this;}/** 设置条目布局 */private void setSheetItems() {if (sheetItemList == null || sheetItemList.size() <= 0) {return;}int size = sheetItemList.size();// TODO 高度控制,非最佳解决办法// 添加条目过多的时候控制高度if (size >= 7) {LinearLayout.LayoutParams params = (LayoutParams) sLayout_content.getLayoutParams();params.height = display.getHeight() / 2;sLayout_content.setLayoutParams(params);}// 循环添加条目for (int i = 1; i <= size; i++) {final int index = i;SheetItem sheetItem = sheetItemList.get(i - 1);String strItem = sheetItem.name;SheetItemColor color = sheetItem.color;final OnSheetItemClickListener listener = (OnSheetItemClickListener) sheetItem.itemClickListener;TextView textView = new TextView(context);textView.setText(strItem);textView.setTextSize(18);textView.setGravity(Gravity.CENTER);// 背景图片if (size == 1) {if (showTitle) {textView.setBackgroundResource(R.drawable.actionsheet_bottom_selector);} else {textView.setBackgroundResource(R.drawable.actionsheet_single_selector);}} else {if (showTitle) {if (i >= 1 && i < size) {textView.setBackgroundResource(R.drawable.actionsheet_middle_selector);} else {textView.setBackgroundResource(R.drawable.actionsheet_bottom_selector);}} else {if (i == 1) {textView.setBackgroundResource(R.drawable.actionsheet_top_selector);} else if (i < size) {textView.setBackgroundResource(R.drawable.actionsheet_middle_selector);} else {textView.setBackgroundResource(R.drawable.actionsheet_bottom_selector);}}}// 字体颜色if (color == null) {textView.setTextColor(Color.parseColor(SheetItemColor.Blue.getName()));} else {textView.setTextColor(Color.parseColor(color.getName()));}// 高度float scale = context.getResources().getDisplayMetrics().density;int height = (int) (45 * scale + 0.5f);textView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, height));// 点击事件textView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {listener.onClick(index);dialog.dismiss();}});lLayout_content.addView(textView);}}public void show() {setSheetItems();dialog.show();}public interface OnSheetItemClickListener {void onClick(int which);}public class SheetItem {String name;OnSheetItemClickListener itemClickListener;SheetItemColor color;public SheetItem(String name, SheetItemColor color,OnSheetItemClickListener itemClickListener) {this.name = name;this.color = color;this.itemClickListener = itemClickListener;}}public enum SheetItemColor {Blue("#037BFF"), Red("#FD4A2E");private String name;private SheetItemColor(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}}
}
//与js交互
public class WebContrl {private Context context;X5WebView mWebView;public WebContrl(Context context, X5WebView webView) {this.context=context;this.mWebView = webView;}@JavascriptInterfacepublic void finish(){((Activity)context).finish();}@JavascriptInterfacepublic void toastMessage(String message) {Log.i("toastMessage" , "传递过来的值是: "+message);}@JavascriptInterfacepublic String getMessage(String s){return s+"world !";}
}
//js部分代码
<script>function showMsgInAndroid(){webCtrl.finish();}
</script>
<div class="header_left" onclick="showMsgInAndroid()"></>
<input type="file" text="点击拍照">
需要引入的库implementation 'com.zhy.base:fileprovider:1.0.0'implementation 'com.google.code.gson:gson:2.8.2'implementation 'com.squareup.okio:okio:1.13.0'implementation 'com.squareup.okhttp3:okhttp:3.9.0'implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'implementation 'pub.devrel:easypermissions:1.2.0'
//权限部分代码checkWritePermission();public static String[] PERMISSION = {Manifest.permission.READ_PHONE_STATE,  Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};* 检查读写权限权限*/private void checkWritePermission() {boolean result = PermissionManager.checkPermission(this,PERMISSION);if (!result) {PermissionManager.requestPermission(this, Permission.WRITE_PERMISSION_TIP, Permission.WRITE_PERMISSION_CODE, PERMISSION);}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull  int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//将请求结果传递EasyPermission库处理EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);}@Overridepublic void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {Toast.makeText(this, "用户授权成功", Toast.LENGTH_SHORT).show();}@Overridepublic void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {Toast.makeText(this, "用户授权失败", Toast.LENGTH_SHORT).show();/*** 若是在权限弹窗中,用户勾选了'NEVER ASK AGAIN.'或者'不在提示',且拒绝权限。* 这时候,需要跳转到设置界面去,让用户手动开启。*/if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {new AppSettingsDialog.Builder(this).build().show();}}

最后说明:项目刚结束、匆匆忙忙附上代码、注释很多未注明,最后附上源代码https://download.csdn.net/download/jsniitqwh/10710585

Android集成腾讯X5WebView相关推荐

  1. Android集成腾讯X5浏览器内核库

    Android集成腾讯X5浏览器内核库 一.相关配置 1. 相关地址 2.引入SDK 3. AndroidManifest配置 二.Application中初始化内核 三.代码实现 1. 自定义带Pr ...

  2. android腾讯互联demo,Android集成腾讯小直播Demo,multidex问题

    在集成腾讯官方的小直播源码中发现的问题 首先是android studio编译中中gradle不能下载gson等文件,需要用jcenter()把原来的maven注掉,或者在ProjectStructu ...

  3. Android集成腾讯直播(无需后台配合一小时让你拥有直播APP)

    直播互动的功能,最终选择了腾讯云平台进行开发,LiveRoom组件里面包含了(直播,连麦,弹幕,私信等)功能,我们需要的是推流,拉流都交个腾讯云来处理,腾讯云这方面功能也是比较齐全的了(粗略大概2分钟 ...

  4. android 集成腾讯定位,Android集成腾讯云通信IM

    本篇文章结构 一.集成流程. 二.集成中遇到的问题 集成过程 TIM图片20180425151417.png 很清楚,前几部没什么说的,去官网注册账号就好了,我们接下来看账号集成. 首先第一步是集成模 ...

  5. android 集成腾讯IMSDK4.2.9 TUIKIT即时通信之更改头像

    使用环境: 2018年集成的腾讯IM云通信,使用的是随心聊类似的集成方式. 2019年集成的时候,官方推荐TUIKIT依赖module github官方demo : https://github.co ...

  6. Android集成腾讯信鸽推送SDK

    推送是每个应用中常见的功能今天使用一下腾讯的信鸽推送听说信鸽推送保活率比较高~ 第一步先去官网注册账号https://xg.qq.com/推荐使用QQ直接登陆,进去之后点击"新建应用&quo ...

  7. android 集成腾讯地图定位

    本文只教学定位功能,需要搜索.2D或3D地图的可以到腾讯地图开发平台看api文档,链接:腾讯地图 一.到腾讯地图开发平下载定位sdk,快速入口:腾讯地图定位 二.在项目的AndroidManiFest ...

  8. Android 项目集成腾讯X5浏览器内核

    1.为什么要集成腾讯 X5 浏览器内核 肯定是事出有因,简单来说,JS代码写的不标准,与部分机型内嵌套的浏览器内核产生矛盾,出现底层(os)bug导致,不得不费事搞一个其它内核进行加载网页,具体原因. ...

  9. Android集成三方浏览器之Crosswalk

    上一篇讲解了腾讯 X5 内核的集成,这一篇是讲解 Crosswalk 的集成 Crosswalk 也是采用了Chromenium 内核,是一款开源的 web 引擎,开发者可以直接把 Crosswalk ...

  10. 安卓集成腾讯即时通信IM完成聊天室功能

    安卓集成腾讯即时通信IM完成聊天室功能 没有效果图的文章都是扯淡 **请将下面的MainActivity的代码复制到源码里面,替换掉源码的MainActivity.class** 话不多说,下来上代码 ...

最新文章

  1. 人脸检测--Supervised Transformer Network for Efficient Face Detection
  2. js中的if与Java中的if_JavaScript if...else 语句
  3. 《Android 应用案例开发大全(第二版)》——2.6节绘制相关类
  4. 解决:RuntimeError: CUDA out of memory. Tried to allocate 2.00 MiB
  5. [Objective-c 基础 - 2.4] 多态
  6. python中sorted_Python中sorted()方法的用法
  7. Tuscany SCA 发布Web Service
  8. 孔维滢 20171010110《面向对象程序设计(java)》第十七周学习总结
  9. 华为机试HJ100:等差数列
  10. 【李宏毅2020 ML/DL】P75 Generative Adversarial Network | Conditional GAN
  11. cent os7 安装dubbo-admin 管理控制台
  12. 写于Silverlight整装待发之际【瞿杰】
  13. WCF Error: 客户端配置部分中,找不到引用协定{0}的默认终结点元素……
  14. lammps计算的应力的方法
  15. 郭沫若最恶心的7首诗_表面和气的郭沫若徐志摩,却因一首诗中的4字闹翻,到底谁有理?...
  16. 树莓派3b+安装home assistant
  17. f(!gotop.length) return false;
  18. java 进销存 crm websocket即时聊天发图片文字 好友群组 SSM源码
  19. linux 批量ping检测
  20. python调用笔记本摄像头

热门文章

  1. 西门子PLC与DCS通讯
  2. pe安装linux 12.04,乌班图系统Ubuntu 12.04安装教程(图文详解)
  3. matlab的共轭梯度法
  4. esp8266对接天猫精灵 微信控制
  5. 推荐几个代码静态分析工具
  6. w ndows10专业版连接不上网,Windows10连不上无线网怎么办 Windows10修复网络教程
  7. 完整的企业机房设计(上)
  8. tcping要安装什么工具linux,Linux下的TCP测试工具——TCPING安装简明教程
  9. ichariot测试路由器有线转发性能
  10. Ubuntu 16.04下如何安装QT5?