项目说明:最近在开发Android原生嵌套H5实现混合开发,刚开始采用原生的WebView各种兼容性问题,之后决定采用腾讯的x5浏览器来开发,遇到的一些问题列一下:
x5webview集成传送门
x5webview同步cookie问题传送门
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> 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 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 params, final DataCallBack callBack) {
RequestBody requestBody;
if (params == null) {
params = new HashMap<>();
}
FormBody.Builder builder = new FormBody.Builder();
/**
* 在这对添加的参数进行遍历
*/
for (Map.Entry 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 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 params) {
StringBuilder endUrl = new StringBuilder(url);
boolean isFirst = true;
Set> entrySet = params.entrySet();
for (Map.Entry 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 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 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 cookies) {
SaasCookieManager.loadCookie(cookies,url.host());
}
@Override
public List 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 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 cookies) {
StringBuilder sb = new StringBuilder();
List set = new ArrayList<>();
for (String cookie : cookies) {
String[] arr = cookie.split(";");
for (String s : arr) {
if (set.contains(s)) {
continue;
}
set.add(s);
}
}
Iterator 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 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 uploadFile;
private ValueCallback 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 uploadMsg, String acceptType) {
}
public void openFileChooser(ValueCallback uploadMsgs) {
}
// For Android > 4.1.1
public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) {
}
// For Android >= 5.0 该项目需求是在5.0之上开发、所以5.0以下不作处理
public boolean onShowFileChooser(com.tencent.smtt.sdk.WebView webView,
ValueCallback 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 cameraIntents = new ArrayList();
final Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
final PackageManager packageManager = getPackageManager();
final List 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 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 uploadFile,final ValueCallback 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();
}
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部分代码
>
需要引入的库
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 perms) {
Toast.makeText(this, "用户授权成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List 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