1. 程式人生 > >Android整合騰訊X5WebView

Android整合騰訊X5WebView

專案說明:最近在開發Android原生巢狀H5實現混合開發,剛開始採用原生的WebView各種相容性問題,之後決定採用騰訊的x5瀏覽器來開發,遇到的一些問題列一下:

  • x5Webview與H5的互動問題
  • x5同步cookie問題
  • WebView載入進度條問題處理
  • H5呼叫Android攝像頭進行錄製視訊、H5呼叫Android相機進行拍照
  • x5WebView-WebChromeClient的方法onShowFileChooser只執行一次的問題
  • X5WebView的Setting需要配置那些東西

先看效果圖

1、Android整合X5核心

 public class MyApplication extends BaseApplication {
    @Override
    public 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);
    }

    @Override
    public 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() {
        @Override
        public void onViewInitFinished(boolean arg0) {
            // TODO Auto-generated method stub
        }
        @Override
        public 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() {
		@Override
		public boolean shouldOverrideUrlLoading(com.tencent.smtt.sdk.WebView view, String url) {
		   //這裡直接載入url
			view.loadUrl(url);
			return true;
		}

		@Override
		public void onPageStarted(WebView webView, String s, Bitmap bitmap) {
			super.onPageStarted(webView, s, bitmap);
		}

		@Override
		public 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);
		}

		@Override
		public 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);
		}
		@Override
		public 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() {
		@Override
		public 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;
		/**
		 * 全屏播放配置
		 */
		@Override
		public 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;
		}

		@Override
		public void onHideCustomView() {
			if (callback != null) {
				callback.onCustomViewHidden();
				callback = null;
			}
			if (myVideoView != null) {
				ViewGroup viewGroup = (ViewGroup) myVideoView.getParent();
				viewGroup.removeView(myVideoView);
				viewGroup.addView(myNormalView);
			}
		}

		@Override
		public void onProgressChanged(com.tencent.smtt.sdk.WebView webView, int i) {
			super.onProgressChanged(webView, i);
			mProgressview.setProgress(i);
		}

		@Override
		public 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() {
		@Override
		public 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() {
								@Override
								public void onClick(DialogInterface dialog,
													int which) {
									Toast.makeText(
											context,
											"fake message: i'll download...",
											Toast.LENGTH_LONG).show();
								}
							})
					.setNegativeButton("no",
							new DialogInterface.OnClickListener() {

								@Override
								public void onClick(DialogInterface dialog,
													int which) {
									// TODO Auto-generated method stub
									Toast.makeText(
											context,
											"fake message: refuse download...",
											Toast.LENGTH_SHORT).show();
								}
							})
					.setOnCancelListener(
							new DialogInterface.OnCancelListener() {

								@Override
								public void onCancel(DialogInterface dialog) {
									// TODO Auto-generated method stub
									Toast.makeText(
											context,
											"fake message: refuse download...",
											Toast.LENGTH_SHORT).show();
								}
							}).show();
		}
	};
	@Override
	protected 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;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        viewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        viewHeight = getMeasuredHeight();
    }

    @Override
    protected 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public void onFailure(Call call, IOException e) {
                deliverDataFailure(request, e, callBack);
            }
            @Override
            public 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() {
            @Override
            public void onFailure(Call call, IOException e) {
                deliverDataFailure(req, e, callBack);
            }
            @Override
            public 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() {
            @Override
            public void onFailure(Call call, IOException e) {
                deliverDataFailure(request, e, callBack);
            }

            @Override
            public 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() {
            @Override
            public void onFailure(Call call, IOException e) {
                deliverDataFailure(request, e, callBack);
            }

            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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;
    }

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        SaasCookieManager.loadCookie(cookies,url.host());
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        return new ArrayList<>();
    }
}
public class AddCookiesInterceptor implements Interceptor {
    private static final String COOKIE_PREF = "cookies_prefs";
    @Override
    public 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";
    @Override
    public 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() {
            @Override
            public void requestSuccess(String result) throws Exception {//TODO 成功
                JSONObject j = new JSONObject(result);
                //TODO 具體業務處理
            }

            @Override
            public void requestFailure(Request request, IOException e) {//TODO 失敗
              
            }

            @Override
            public 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;

    @Override
    protected int getContentView() {
        return R.layout.activity_browser;
    }

    @Override
    protected 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();
    }

    @Override
    protected 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);
    }

    @Override
    public 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方法
    @Override
    protected void onNewIntent(Intent intent) {
        if (intent == null || mWebView == null || intent.getData() == null)
            return;
        mWebView.loadUrl(intent.getExtras().getString("url"));
    }

    @Override
    public 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;
            } else
                return super.onKeyDown(keyCode, event);
        }
        return super.onKeyDown(keyCode, event);
    }

    private void changGoForwardButton(com.tencent.smtt.sdk.WebView view) {

    }
   //當前涉及呼叫拍照、攝像功能,需要重新設定WebChromeClient
    private WebChromeClient chromeClient = new WebChromeClient() {
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
        }

        public void openFileChooser(ValueCallback<Uri> uploadMsgs) {
        }

        // For Android  > 4.1.1
        public 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() {
                                @Override
                                public void onClick(int which) {
                                    take();
                                }
                            })
                    .addSheetItem("上傳視訊",
                            ActionSheetDialog.SheetItemColor.Blue,
                            new ActionSheetDialog.OnSheetItemClickListener() {
                                @Override
                                public 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 exist
        if (! 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;
    @Override
    protected 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;
	@Override
	protected 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() {
				@Override
				public 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() {
				@Override
				public 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() {
				@Override
				public 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;
    }

    @JavascriptInterface
    public void finish(){
        ((Activity)context).finish();
    }

    @JavascriptInterface
    public void toastMessage(String message) {
        Log.i("toastMessage" , "傳遞過來的值是: "+message);
    }
    @JavascriptInterface
    public 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);
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull  int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //將請求結果傳遞EasyPermission庫處理
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
        Toast.makeText(this, "使用者授權成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public 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