WebView的一些神坑

最近在开发过程中遇到一个问题,主要是WebView页面,需要调用本地相机拍照及图库,遇到一系列的神坑,这里结合本人所查阅的资料给大家一一说明。

进入正题,首先来了解webview,这里我分享两篇大佬的博客

1.WebView开车指南

2.WebView详解

3.WebView深坑之旅

本人也是首次使用H5页面调用相机及图库,本以为只要将页面展示就可以,其他的都是前端的事情,我还能偷偷懒,可是在测试的时候就遇到了

第一个神坑:权限的添加

Android 6.0以后对于一些危险权限都需要动态的添加权限,这些对于大家来说都已经很容易实现了,大家可参考:

https://blog.csdn.net/m0_37959831/article/details/77854548

但是,如果你已经添加了权限,调用相机之后,文件回调不了,其实根本就是没有回调.这个时候你要去检查你的webview的Settings了关键代码,是否给足了权限;

WebSettings settings = webView.getSettings();settings.setUseWideViewPort(true);settings.setLoadWithOverviewMode(true);settings.setDomStorageEnabled(true);settings.setDefaultTextEncodingName("UTF-8");settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 truesettings.setAllowFileAccess(true);    // 是否可访问本地文件,默认值 true// 是否允许通过file url加载的Javascript读取本地文件,默认值 falsesettings.setAllowFileAccessFromFileURLs(false);// 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 falsesettings.setAllowUniversalAccessFromFileURLs(false);//开启JavaScript支持settings.setJavaScriptEnabled(true);// 支持缩放settings.setSupportZoom(true);

其中 settings.setDomStorageEnabled(true);这个方法当时我没加,血崩了一次;

第二个神坑:Android端 webview根本不能让h5自己调用,ios是可以的。

这里是为什么呢?查了资料发现Android端H5页面对于相机及图库的调用有一套专门的代码:这里给大家推荐两篇文章:

https://blog.csdn.net/m0_37959831/article/details/77854548

https://www.cnblogs.com/nmdzwps/p/5841509.html

大家啊先参考文档,最后我会贴上本人的代码供大家参考

第三个神坑:WebchromClient的openFileChooser()只调用了一次

首先了解为什么这个方法只调用了一次,看这篇博客就可

Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明
看了他基本你就了解怎么回事了

第四个神坑 :android 7.0的FileProvider的坑

看洪阳大佬的这篇博客基本就了解了

Android 7.0 行为变更 通过FileProvider在应用间共享文件吧

第五个神坑:WebView软键盘冲突

对于H5页面中的输入框与软件盘冲突的问题,或者输入框被软键盘遮盖问题,相信大部分人都遇到过,这里就和大家说下解决方案:

软键盘挡住输入框问题的终极解决方案

解决安卓全屏状态下WebView的输入框被软键盘挡住的问题

第六个神坑:H5页面加载地图

https://blog.csdn.net/jyz_2015/article/details/52776195

https://blog.csdn.net/csdndouniwan/article/details/51159901

贴上自己的代码:

public class WebViewInstallationActivity extends BaseActivity {// 定位private static final int REQ_SET_LOCATION = 300;private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1;//打电话private WebView webView;private String dataMap;private String order_type;private String out_trade_no;private int code;private String userid;private String installer_phone;//H5调用Android 相册 相机的private ValueCallback<Uri> mUploadMessage;// 表单的数据信息private ValueCallback<Uri[]> mUploadCallbackAboveL;private final static int FILECHOOSER_RESULTCODE = 1;// 表单的结果回调</span>private Uri imageUri;private WaitDialog waitDialog;private String h5_notice_url;private String url;private String gobackurl;private Uri bitmap2uri;@Overridepublic int getLayoutResId() {waitDialog = new WaitDialog(activity, "", false, null);String user_id = SpUtils.getUserId(activity);h5_notice_url = getIntent().getStringExtra("h5_notice_url");if (StringUtils.isEmpty(h5_notice_url)) {if (StringUtils.isEmpty(user_id)) {user_id = "";}url = ServiceApi.getWebView(user_id);} else if (!StringUtils.isEmpty(h5_notice_url)) {url = h5_notice_url;}return R.layout.activity_webview;}@Overrideprotected void initView() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {Window window = getWindow();window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);}webView = (WebView) findViewById(R.id.webview);WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);//支持jswebSettings.setAppCacheEnabled(true);webSettings.setDomStorageEnabled(true);//自适应屏幕webSettings.setLoadWithOverviewMode(true);webSettings.setUseWideViewPort(false);  //将图片调整到适合webview的大小webSettings.setSupportZoom(false);  // 设置可以支持缩放webSettings.setBuiltInZoomControls(false); //设置出现缩放工具webSettings.setTextZoom(100); //禁止缩放,按照百分百显示//缓存模式boolean networkAvailable = MySystemUtils.isNetworkAvailable(activity);if (networkAvailable) {webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);  //缓存模式} else {webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  //缓存模式}webSettings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 truewebSettings.setAllowFileAccess(true);  //设置可以访问本地文件webSettings.setNeedInitialFocus(true); //当webview调用requestFocus时为webview设置节点webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口//启用数据库webSettings.setDatabaseEnabled(true);String dir = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();//webview定位相关设置webSettings.setGeolocationEnabled(true);//设置定位的数据库路径webSettings.setGeolocationDatabasePath(dir);webView.setWebChromeClient(new MyWebChromeClient());webView.setWebViewClient(new MyWebViewClient());//对webview页面加载管理、如url重定向}@Overrideprotected void initListener() {}@Overrideprotected void initData() {//添加定位权限 相机图库权限initPermission();}private void initPermission() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//SDK>=23if (!(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED)) {if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)) {Toast.makeText(this, "请确认是否开启定位的相关权限", Toast.LENGTH_LONG).show();MySystemUtils.goToSetPermission(activity, "在设置-应用-权限中开启定位的权限,以保证功能的正常使用", REQ_SET_LOCATION);} else {requestPermissions(new String[]{Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_COARSE_LOCATION}, REQ_SET_LOCATION);}} else {Log.i("--", "onClick granted");onLoadData();}} else {onLoadData();}}private void onLoadData() {webView.loadUrl(url);if (!isFinishing()) {waitDialog.show();}doSpecialiSomethingAsVirtualBar();}/*** 虚拟按键-tangZd*/private void doSpecialiSomethingAsVirtualBar() {//webview-软键盘-冲突//KeyBoardListenerManager.newInstance(this).init();//华为虚拟按键冲突问题://是否存在导航栏(虚拟功能键)if (PhoneSystemManager.AndroidWorkaround.checkDeviceHasNavigationBar(this)) {PhoneSystemManager.AndroidWorkaround.assistActivity(findViewById(android.R.id.content));ViewStub stub = (ViewStub) findViewById(R.id.view_stub);stub.inflate();View enuiStubView = this.findViewById(R.id.enuiNatView);LinearLayout.LayoutParams zLayoutParams = (LinearLayout.LayoutParams) enuiStubView.getLayoutParams();//获取虚拟功能键高度int virtualBarHeigh = PhoneSystemManager.AndroidWorkaround.getVirtualBarHeigh(this);zLayoutParams.height = virtualBarHeigh;enuiStubView.setLayoutParams(zLayoutParams);} else {//webview-软键盘-冲突KeyBoardListenerManager.newInstance(this).init();}}private class MyWebChromeClient extends WebChromeClient {//定位@Overridepublic void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {callback.invoke(origin, true, true);super.onGeolocationPermissionsShowPrompt(origin, callback);}//android 5.0以后google做了支持@Overridepublic boolean onShowFileChooser(WebView webView,ValueCallback<Uri[]> filePathCallback,FileChooserParams fileChooserParams) {mUploadCallbackAboveL = filePathCallback;//take();getPermissions();return true;}//下面的这些方法会根据android的版本自动选择//android3.0以下public void openFileChooser(ValueCallback<Uri> uploadMsg) {mUploadMessage = uploadMsg;getPermissions();}//android3.0-4.0public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {mUploadMessage = uploadMsg;getPermissions();}//4.0-4.3  4.4.4(android 4.4无方法)public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {mUploadMessage = uploadMsg;getPermissions();}}private void getPermissions() {//拍照权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//checkSelfPermission用来检测应用是否已经具有权限if (!(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)) {if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {//去设置权限(申请权限) dialogMySystemUtils.goToSetPermission(activity, getResources().getString(R.string.permission_camera), JHConstants.REQ_PERMISSION_CAMERA);} else {//进行请求单个或多个权限requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, JHConstants.REQ_PERMISSION_CAMERA);}} else {//具有权限take();}} else {// < 23take();}}private class MyWebViewClient extends MyWebViewClientUtils {@Overrideprotected boolean initShouldOverrideUrlLoading(WebView view, String url) {if (url.contains("installPhoneMyOrderPF/installerOrderCallPhone.do")) {//截取 电话Map urlParamMap = UIutils.getUrlParamMap(url);installer_phone = urlParamMap.get("INSTALLER_PHONE").toString();//web 打电话webViewCall();} else if (url.contains("iosIndex/gotoIndexPage.do")) {//返回waitDialog.dismiss();WebViewInstallationActivity.this.finish();} else if (url.contains("installPhoneIndexPF/phoneLoginPage.do")) {//去登录页面startActivity(new Intent(activity, LoginActivity.class));} else if (url.contains("installPhoneIndexPF/phoneRegisterPage.do")) {//去认证页面startActivity(new Intent(activity, IdentifyActivity.class));} else if (url.contains("kuaiQianJAZH5Pay/toPayPage.do")) {//去快钱支付页面Map urlParamMap = UIutils.getUrlParamMap(url);String order_id = urlParamMap.get("ORDER_ID").toString();//快钱支付Intent webviewkuaiqian = new Intent(activity, WebViewKuaiqianPayActivity.class);webviewkuaiqian.putExtra("pay_type", "web");webviewkuaiqian.putExtra("out_trade_no", order_id);startActivity(webviewkuaiqian);} else if (url.contains("submitToPayOrder")) {//待支付 去支付Map urlParamMap = UIutils.getUrlParamMap(url);userid = urlParamMap.get("USER_ID").toString();ServiceApi.getUrlParammap(NetUtils.H5_URL_WEBTOPAYPAGE, urlParamMap, new MyString2Callback() {@Overridepublic void onError(Call call, Exception e) {ToastUtils.showInternetErrorToast();}@Overridepublic void onResponse(Call call, String s) {String PAY_user_id = "PAY_" + userid;try {JSONObject jsonObject = new JSONObject(s);code = jsonObject.getInt("code");dataMap = jsonObject.getString("dataMap");JSONObject pay_user_id = jsonObject.getJSONObject(PAY_user_id);out_trade_no = pay_user_id.getString("out_trade_no");order_type = jsonObject.getString("order_type");} catch (JSONException e) {e.printStackTrace();}if (0 == code) {ZFBUtils zfbUtils = new ZFBUtils(WebViewInstallationActivity.this, dataMap);zfbUtils.payV2(new ZFBUtils.Pay_ZFB_Pay_Listener() {@Overridepublic void onPaySuccess(PayResult payResult) {ToastUtils.showToast("支付成功");webView.loadUrl(ServiceApi.getPaySuccessToDetail(userid, out_trade_no));}@Overridepublic void onPayError(PayResult payResult) {String s1 = payResult.toString();ToastUtils.showToast("支付失败");}});} else if (-1 == code) {ToastUtils.showToast("操作失败");}}});} else {return false;}return true;}@Overrideprotected void initOnPageStarted(WebView view, String url, Bitmap favicon) {gobackurl = url;if (!isFinishing()) {waitDialog.show();}}@Overrideprotected void initOnPageFinished(WebView view, String url) {waitDialog.dismiss();}@Overrideprotected void initOnReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {waitDialog.dismiss();}}//web 打电话private void webViewCall() {// 检查是否获得了权限(Android6.0运行时权限)if (ContextCompat.checkSelfPermission(WebViewInstallationActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {// 没有获得授权,申请授权if (ActivityCompat.shouldShowRequestPermissionRationale(WebViewInstallationActivity.this, Manifest.permission.CALL_PHONE)) {// 返回值://如果app之前请求过该权限,被用户拒绝, 这个方法就会返回true.//如果用户之前拒绝权限的时候勾选了对话框中”Don’t ask again”的选项,那么这个方法会返回false.// 如果设备策略禁止应用拥有这条权限, 这个方法也返回false.// 弹窗需要解释为何需要该权限,再次请求授权Toast.makeText(WebViewInstallationActivity.this, "请授权!", Toast.LENGTH_LONG).show();// 帮跳转到该应用的设置界面,让用户手动授权Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uri uri = Uri.fromParts("package", getPackageName(), null);intent.setData(uri);startActivity(intent);} else {// 不需要解释为何需要该权限,直接请求授权ActivityCompat.requestPermissions(WebViewInstallationActivity.this, new String[]{Manifest.permission.CALL_PHONE}, MY_PERMISSIONS_REQUEST_CALL_PHONE);}} else {// 已经获得授权,可以打电话CallPhone();}}private void CallPhone() {if (TextUtils.isEmpty(installer_phone)) {// 提醒用户// 注意:在这个匿名内部类中如果用this则表示是View.OnClickListener类的对象,// 所以必须用MainActivity.this来指定上下文环境。Toast.makeText(WebViewInstallationActivity.this, "号码不能为空!", Toast.LENGTH_SHORT).show();} else {// 拨号:激活系统的拨号组件Intent intent = new Intent(); // 意图对象:动作 + 数据intent.setAction(Intent.ACTION_CALL); // 设置动作Uri data = Uri.parse("tel:" + installer_phone); // 设置数据intent.setData(data);startActivity(intent); // 激活Activity组件}}// 处理权限申请的回调@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {switch (requestCode) {case MY_PERMISSIONS_REQUEST_CALL_PHONE: {//电话if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 授权成功,继续打电话CallPhone();} else {// 授权失败!Toast.makeText(this, "授权失败!", Toast.LENGTH_LONG).show();}break;}case REQ_SET_LOCATION: {//定位Boolean grantResultBoolean = false;for (int grantResult : grantResults) {grantResultBoolean = (grantResult == PackageManager.PERMISSION_GRANTED);if (!grantResultBoolean) {break;}}if (grantResultBoolean) {//通过onLoadData();} else {MySystemUtils.goToSetPermission(activity, "在设置-应用-权限中开启定位的权限,以保证功能的正常使用", REQ_SET_LOCATION);}break;}case JHConstants.REQ_PERMISSION_CAMERA: {//相机相册boolean isAllow = false;for (int result : grantResults) {if (PackageManager.PERMISSION_GRANTED == result) {isAllow = true;} else {isAllow = false;}}if (isAllow) {take();} else {MySystemUtils.goToSetPermission(activity, getResources().getString(R.string.permission_notice_storage_and_camera), JHConstants.REQ_PERMISSION_CAMERA);}break;}}}/*** 以下是webview的照片上传时候,用于在网页打开android图库文件*/@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data); //图库的图片 data != null, 拍照的图片时 data == nullif (requestCode == FILECHOOSER_RESULTCODE) {if (null == mUploadMessage && null == mUploadCallbackAboveL)return;Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();if (mUploadCallbackAboveL != null) {//android 5.0以后try {onActivityResultAboveL(requestCode, resultCode, data);} catch (IOException e) {e.printStackTrace();}} else if (mUploadMessage != null) {//android 5.0以前if (result != null) {//图库的图片try {String path = getPath(getApplicationContext(), result);Uri uri = Uri.fromFile(new File(path));Bitmap bitmapformuri = PhotoUtils.getBitmapFormUri(activity, uri);bitmap2uri = PhotoUtils.bitmap2uri(activity, bitmapformuri);mUploadMessage.onReceiveValue(bitmap2uri);} catch (IOException e) {e.printStackTrace();}} else {//拍照的图片//此处要设为null,否则在未选择图片的情况下,依然会出现上传空白文件,后台做了Uri 路径流是否可用判断
//                    imageUri = null;mUploadMessage.onReceiveValue(imageUri);}mUploadMessage = null;}}}//针对5.0的结果回调@SuppressWarnings("null")@TargetApi(Build.VERSION_CODES.LOLLIPOP)private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) throws IOException {if (requestCode != FILECHOOSER_RESULTCODE || mUploadCallbackAboveL == 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) {Uri[] compressResults = new Uri[results.length];for (int i = 0; i < results.length; i++) {Uri result = results[i];Bitmap bitmapFormUri = PhotoUtils.getBitmapFormUri(activity, result);bitmap2uri = PhotoUtils.bitmap2uri(activity, bitmapFormUri);compressResults[i] = bitmap2uri;}mUploadCallbackAboveL.onReceiveValue(compressResults);mUploadCallbackAboveL = null;} else {//此处要设为null,否则在未选择图片的情况下,依然会出现上传空白文件,results = null;mUploadCallbackAboveL.onReceiveValue(results);mUploadCallbackAboveL = null;}return;}//take方法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");//设定图片的uri路径imageUri = Uri.fromFile(file);final List<Intent> cameraIntents = new ArrayList<Intent>();//调用相机的intentfinal Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);//获取包管理器(就是包含了整个清单文件,其中包括application,activity)final PackageManager packageManager = getPackageManager();//查询相机intent的activity,ResolveInfo其实就是activity节点final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);//进行遍历for (ResolveInfo res : listCam) {//获取list中的元素,就activity,就是根据activity拿到相机的包名final String packageName = res.activityInfo.packageName;//将相机的intent 赋给新的intentfinal Intent i = new Intent(captureIntent);//重新设置当前intent的Component  (写全包名)i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));i.setPackage(packageName);i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);cameraIntents.add(i);}//这个是文档内容,包含image,音视频Intent i = new Intent(Intent.ACTION_GET_CONTENT);//添加分类i.addCategory(Intent.CATEGORY_OPENABLE);//类型为图片i.setType("image/*");//开始创建跳转应用的对话框,可以自己设置dialog样式,也可以像下面这样,同时创建包含多个//intent的对话框,但样式不好控制,只能由系统默认,比如在4.4.4的模拟器上样式,图片见末尾Intent chooserIntent = Intent.createChooser(i, "选择图片");//Image Chooser//添加额外初始相机intent,但要序列化chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));WebViewInstallationActivity.this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);}//由于4.4以后content后面不再表示路径,以下方法专为4.4以后路径问题的解决@TargetApi(Build.VERSION_CODES.KITKAT)public static String getPath(final Context context, final Uri uri) {final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;// DocumentProviderif (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {// ExternalStorageProviderif (isExternalStorageDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(":");final String type = split[0];if ("primary".equalsIgnoreCase(type)) {return Environment.getExternalStorageDirectory() + "/" + split[1];}// TODO handle non-primary volumes}// DownloadsProviderelse if (isDownloadsDocument(uri)) {final String id = DocumentsContract.getDocumentId(uri);final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));return getDataColumn(context, contentUri, null, null);}// MediaProviderelse if (isMediaDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(":");final String type = split[0];Uri contentUri = null;if ("image".equals(type)) {contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;} else if ("video".equals(type)) {contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;} else if ("audio".equals(type)) {contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;}final String selection = "_id=?";final String[] selectionArgs = new String[]{split[1]};return getDataColumn(context, contentUri, selection, selectionArgs);}}// MediaStore (and general)else if ("content".equalsIgnoreCase(uri.getScheme())) {return getDataColumn(context, uri, null, null);}// Fileelse if ("file".equalsIgnoreCase(uri.getScheme())) {return uri.getPath();}return null;}/*** Get the value of the data column for this Uri. This is useful for* MediaStore Uris, and other file-based ContentProviders.** @param context       The context.* @param uri           The Uri to query.* @param selection     (Optional) Filter used in the query.* @param selectionArgs (Optional) Selection arguments used in the query.* @return The value of the _data column, which is typically a file path.*/public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {Cursor cursor = null;final String column = "_data";final String[] projection = {column};try {cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);if (cursor != null && cursor.moveToFirst()) {final int column_index = cursor.getColumnIndexOrThrow(column);return cursor.getString(column_index);}} finally {if (cursor != null) cursor.close();}return null;}/*** @param uri The Uri to check.* @return Whether the Uri authority is ExternalStorageProvider.*/public static boolean isExternalStorageDocument(Uri uri) {return "com.android.externalstorage.documents".equals(uri.getAuthority());}/*** @param uri The Uri to check.* @return Whether the Uri authority is DownloadsProvider.*/public static boolean isDownloadsDocument(Uri uri) {return "com.android.providers.downloads.documents".equals(uri.getAuthority());}/*** @param uri The Uri to check.* @return Whether the Uri authority is MediaProvider.*/public static boolean isMediaDocument(Uri uri) {return "com.android.providers.media.documents".equals(uri.getAuthority());}@Overridepublic Resources getResources() {Resources res = super.getResources();Configuration config = new Configuration();config.setToDefaults();res.updateConfiguration(config, res.getDisplayMetrics());return res;}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {// TODO Auto-generated method stubif (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {if (gobackurl.contains("installPhoneIndexPF/phoneIndexPage.do")) {waitDialog.dismiss();WebViewInstallationActivity.this.finish();} else if (gobackurl.contains("installPhoneMyOrderPF/phoneMyOrderPage.do")) {waitDialog.dismiss();WebViewInstallationActivity.this.finish();} else if (gobackurl.contains("installPhonePersonCenterPF/phonePersonIndexPage.do")) {waitDialog.dismiss();WebViewInstallationActivity.this.finish();} else {webView.goBack();}return true;} else {waitDialog.dismiss();finish();}return super.onKeyDown(keyCode, event);}@Overrideprotected void onDestroy() {super.onDestroy();if (webView != null) {webView.setWebViewClient(null);webView.setWebChromeClient(null);webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);webView.clearHistory();webView.destroy();webView = null;}}
}
/*** Created by LiJinWei on 2018/1/22.* 调用方式为:KeyBoardListenerManager.newInstance(this).init();* 出现华为虚拟按键冲突问题:* 解决方案使用下面此类,此类主要作为封装处理特殊手机的工具类。*/
public class PhoneSystemManager {public static final String SYS_EMUI = "sys_emui";// 华为public static final String SYS_MIUI = "sys_miui";// 小米public static final String SYS_FLYME = "sys_flyme";// 魅族public static final String SYS_NORMAL = "sys_normal";// 一般市面手机private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";private static final String KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level";private static final String KEY_EMUI_VERSION = "ro.build.version.emui";private static final String KEY_EMUI_CONFIG_HW_SYS_VERSION = "ro.confg.hw_systemversion";public static String getTelPhoneSystem() {String SYS = SYS_NORMAL;try {Properties prop = new Properties();prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));if (prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null|| prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null|| prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null) {SYS = SYS_MIUI;//小米} else if (prop.getProperty(KEY_EMUI_API_LEVEL, null) != null|| prop.getProperty(KEY_EMUI_VERSION, null) != null|| prop.getProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, null) != null) {SYS = SYS_EMUI;//华为} else if (getMeizuFlymeOSFlag().toLowerCase().contains("flyme")) {SYS = SYS_FLYME;//魅族}} catch (IOException e) {e.printStackTrace();return SYS;}return SYS;}public static String getMeizuFlymeOSFlag() {return getSystemProperty("ro.build.display.id", "");}private static String getSystemProperty(String key, String defaultValue) {try {Class<?> clz = Class.forName("android.os.SystemProperties");Method get = clz.getMethod("get", String.class, String.class);return (String) get.invoke(clz, key, defaultValue);} catch (Exception e) {}return defaultValue;}/*** 设置透明状态栏目* setMiuiStatusBarDarkMode(this, true);* setMeizuStatusBarDarkIcon(this, true);**/public static boolean setMiuiStatusBarDarkMode(Activity activity, boolean darkmode) {Class<? extends Window> clazz = activity.getWindow().getClass();try {int darkModeFlag = 0;Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");darkModeFlag = field.getInt(layoutParams);Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);return true;} catch (Exception e) {e.printStackTrace();}return false;}public static boolean setMeizuStatusBarDarkIcon(Activity activity, boolean dark) {boolean result = false;if (activity != null) {try {WindowManager.LayoutParams lp = activity.getWindow().getAttributes();Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");darkFlag.setAccessible(true);meizuFlags.setAccessible(true);int bit = darkFlag.getInt(null);int value = meizuFlags.getInt(lp);if (dark) {value |= bit;} else {value &= ~bit;}meizuFlags.setInt(lp, value);activity.getWindow().setAttributes(lp);result = true;} catch (Exception e) {}}return result;}/*** 处理华为手机虚拟按键和底部导航遮挡问题* 出现原因-* getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);* getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);* 加:* -android::fitsSystemWindows="true"* 去除-FLAG_TRANSLUCENT_NAVIGATION* -仍旧不行使用本类-在setContentView(……* if (AndroidWorkaround.checkDeviceHasNavigationBar(this))* {* AndroidWorkaround.assistActivity(findViewById(android.R.id.content));* }*/public static class AndroidWorkaround {public static void assistActivity(View content) {new AndroidWorkaround(content);}private View mChildOfContent;private int usableHeightPrevious;private ViewGroup.LayoutParams frameLayoutParams;private AndroidWorkaround(View content) {mChildOfContent = content;mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {public void onGlobalLayout() {possiblyResizeChildOfContent();}});frameLayoutParams = mChildOfContent.getLayoutParams();}private void possiblyResizeChildOfContent() {int usableHeightNow = computeUsableHeight();if (usableHeightNow != usableHeightPrevious) {frameLayoutParams.height = usableHeightNow;mChildOfContent.requestLayout();usableHeightPrevious = usableHeightNow;}}private int computeUsableHeight() {Rect r = new Rect();mChildOfContent.getWindowVisibleDisplayFrame(r);return (r.bottom);}/*** 获取虚拟功能键高度*/public static int getVirtualBarHeigh(Context context) {int vh = 0;WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);Display display = windowManager.getDefaultDisplay();DisplayMetrics dm = new DisplayMetrics();try {@SuppressWarnings("rawtypes")Class c = Class.forName("android.view.Display");@SuppressWarnings("unchecked")Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);method.invoke(display, dm);vh = dm.heightPixels - windowManager.getDefaultDisplay().getHeight();} catch (Exception e) {e.printStackTrace();}return vh;}//是否存在导航栏(虚拟功能键)public static boolean checkDeviceHasNavigationBar(Context context) {boolean hasNavigationBar = false;Resources rs = context.getResources();int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");if (id > 0) {hasNavigationBar = rs.getBoolean(id);}try {Class systemPropertiesClass = Class.forName("android.os.SystemProperties");Method m = systemPropertiesClass.getMethod("get", String.class);String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");if ("1".equals(navBarOverride)) {hasNavigationBar = false;} else if ("0".equals(navBarOverride)) {hasNavigationBar = true;}} catch (Exception e) {}return hasNavigationBar;}//NavigationBar状态是否是显示public static boolean isNavigationBarShow(Context context) {Activity mContext = (Activity) context;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {Display display = mContext.getWindowManager().getDefaultDisplay();Point size = new Point();Point realSize = new Point();display.getSize(size);display.getRealSize(realSize);return realSize.y != size.y;} else {boolean menu = ViewConfiguration.get(context).hasPermanentMenuKey();boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);if (menu || back) {return false;} else {return true;}}}}
}
/*** Created by LiJinWei on 2018/1/22.* webview-软键盘-冲突* 由于项目中WebView输入数据,软键盘不会顶上布局,遮盖住页面,* 使用下边此类解决冲突后出现底部导航栏被遮挡的问题。*/
public class KeyBoardListenerManager {private Activity activity;private View mChildOfContent;private int usableHeightPrevious;private FrameLayout.LayoutParams frameLayoutParams;private static KeyBoardListenerManager keyBoardListener;public static KeyBoardListenerManager newInstance(Activity activity) {keyBoardListener = new KeyBoardListenerManager(activity);return keyBoardListener;}public KeyBoardListenerManager(Activity activity) {super();// TODO Auto-generated constructor stubthis.activity = activity;}public void init() {FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);mChildOfContent = content.getChildAt(0);mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {public void onGlobalLayout() {possiblyResizeChildOfContent();}});frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();}private void possiblyResizeChildOfContent() {int usableHeightNow = computeUsableHeight();if (usableHeightNow != usableHeightPrevious) {int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();int heightDifference = usableHeightSansKeyboard - usableHeightNow;if (heightDifference > (usableHeightSansKeyboard / 4)) {// keyboard probably just became visibleframeLayoutParams.height = usableHeightSansKeyboard - heightDifference;} else {// keyboard probably just became hiddenframeLayoutParams.height = usableHeightSansKeyboard;}mChildOfContent.requestLayout();usableHeightPrevious = usableHeightNow;}}private int computeUsableHeight() {Rect r = new Rect();mChildOfContent.getWindowVisibleDisplayFrame(r);return (r.bottom - r.top);}}

这里面有个photoUtils,已经封装好了各种调用,简单好用

/*** Created by LiJinWei on 2018/1/31.*/
public class PhotoUtils {private static final String TAG = "PhotoUtils";/*** @param activity    当前activity* @param imageUri    拍照后照片存储路径* @param requestCode 调用系统相机请求码*/public static void takePicture(Activity activity, Uri imageUri, int requestCode) {//调用系统相机Intent intentCamera = new Intent();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件}intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//将拍照结果保存至photo_file的Uri中,不保留在相册中intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);if (activity != null) {activity.startActivityForResult(intentCamera, requestCode);}}/*** @param activity    当前activity* @param requestCode 打开相册的请求码*/public static void openPic(Activity activity, int requestCode) {Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);photoPickerIntent.setType("image/*");activity.startActivityForResult(photoPickerIntent, requestCode);}/*** @param activity    当前activity* @param orgUri      剪裁原图的Uri* @param desUri      剪裁后的图片的Uri* @param aspectX     X方向的比例* @param aspectY     Y方向的比例* @param width       剪裁图片的宽度* @param height      剪裁图片高度* @param requestCode 剪裁图片的请求码*/public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {Intent intent = new Intent("com.android.camera.action.CROP");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);}intent.setDataAndType(orgUri, "image/*");intent.putExtra("crop", "true");intent.putExtra("aspectX", aspectX);intent.putExtra("aspectY", aspectY);intent.putExtra("outputX", width);intent.putExtra("outputY", height);intent.putExtra("scale", true);//将剪切的图片保存到目标Uri中intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);intent.putExtra("return-data", false);intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());intent.putExtra("noFaceDetection", true);activity.startActivityForResult(intent, requestCode);}/*** 读取uri所在的图片** @param uri      图片对应的Uri* @param mContext 上下文对象* @return 获取图像的Bitmap*/public static Bitmap getBitmapFromUri(Uri uri, Context mContext) {try {
//            Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri);Bitmap bitmapFormUri = getBitmapFormUri(mContext, uri);return bitmapFormUri;} catch (Exception e) {e.printStackTrace();return null;}}/*** 通过uri获取图片并进行压缩** @param uri*/public static Bitmap getBitmapFormUri(Context ac, Uri uri) throws FileNotFoundException, IOException {InputStream input = ac.getContentResolver().openInputStream(uri);BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();onlyBoundsOptions.inJustDecodeBounds = true;onlyBoundsOptions.inDither = true;//optionalonlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optionalBitmapFactory.decodeStream(input, null, onlyBoundsOptions);input.close();int originalWidth = onlyBoundsOptions.outWidth;int originalHeight = onlyBoundsOptions.outHeight;if ((originalWidth == -1) || (originalHeight == -1)) {return null;}//图片分辨率以480x800为标准float hh = 1280f;//这里设置高度为800ffloat ww = 720f;//这里设置宽度为480f//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;//be=1表示不缩放if (originalWidth > originalHeight && originalWidth > ww) {//如果宽度大的话根据宽度固定大小缩放be = (int) (originalWidth / ww);} else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的话根据宽度固定大小缩放be = (int) (originalHeight / hh);}if (be <= 0) {be = 1;}//比例压缩BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();bitmapOptions.inSampleSize = be;//设置缩放比例bitmapOptions.inDither = true;//optionalbitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optionalinput = ac.getContentResolver().openInputStream(uri);Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);input.close();return compressImage(bitmap);//再进行质量压缩}/*** 质量压缩方法** @param image* @return*/public static Bitmap compressImage(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中int options = 100;while (baos.toByteArray().length / 1024 > 512) {  //循环判断如果压缩后图片是否大于100kb,大于继续压缩baos.reset();//重置baos即清空baos//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差  ,第三个参数:保存压缩后的数据的流image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中options -= 10;//每次都减少10}byte[] bytes = baos.toByteArray();ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片return bitmap;}/*** bitmap 转 uri* @param c* @param b* @return*/public static Uri bitmap2uri(Context c, Bitmap b) {File path = new File(c.getCacheDir() + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");try {OutputStream os = new FileOutputStream(path);b.compress(Bitmap.CompressFormat.JPEG, 100, os);os.close();return Uri.fromFile(path);} catch (Exception ignored) {}return null;}/*** @param context 上下文对象* @param uri     当前相册照片的Uri* @return 解析后的Uri对应的String* 专为Android4.4设计的从Uri获取文件绝对路径,以前的方法已不好使*/@SuppressLint("NewApi")public static String getPath(final Context context, final Uri uri) {final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;String pathHead = "file:///";// DocumentProviderif (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {// ExternalStorageProviderif (isExternalStorageDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(":");final String type = split[0];if ("primary".equalsIgnoreCase(type)) {return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1];}}// DownloadsProviderelse if (isDownloadsDocument(uri)) {final String id = DocumentsContract.getDocumentId(uri);final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));return pathHead + getDataColumn(context, contentUri, null, null);}// MediaProviderelse if (isMediaDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(":");final String type = split[0];Uri contentUri = null;if ("image".equals(type)) {contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;} else if ("video".equals(type)) {contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;} else if ("audio".equals(type)) {contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;}final String selection = "_id=?";final String[] selectionArgs = new String[]{split[1]};return pathHead + getDataColumn(context, contentUri, selection, selectionArgs);}}// MediaStore (and general)else if ("content".equalsIgnoreCase(uri.getScheme())) {return pathHead + getDataColumn(context, uri, null, null);}// Fileelse if ("file".equalsIgnoreCase(uri.getScheme())) {return pathHead + uri.getPath();}return null;}/*** Get the value of the data column for this Uri. This is useful for* MediaStore Uris, and other file-based ContentProviders.** @param context       The context.* @param uri           The Uri to query.* @param selection     (Optional) Filter used in the query.* @param selectionArgs (Optional) Selection arguments used in the query.* @return The value of the _data column, which is typically a file path.*/private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {Cursor cursor = null;final String column = "_data";final String[] projection = {column};try {cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);if (cursor != null && cursor.moveToFirst()) {final int column_index = cursor.getColumnIndexOrThrow(column);return cursor.getString(column_index);}} finally {if (cursor != null) {cursor.close();}}return null;}/*** @param uri The Uri to check.* @return Whether the Uri authority is ExternalStorageProvider.*/private static boolean isExternalStorageDocument(Uri uri) {return "com.android.externalstorage.documents".equals(uri.getAuthority());}/*** @param uri The Uri to check.* @return Whether the Uri authority is DownloadsProvider.*/private static boolean isDownloadsDocument(Uri uri) {return "com.android.providers.downloads.documents".equals(uri.getAuthority());}/*** @param uri The Uri to check.* @return Whether the Uri authority is MediaProvider.*/private static boolean isMediaDocument(Uri uri) {return "com.android.providers.media.documents".equals(uri.getAuthority());}}

OK ! ! !  到这里 相信大家对WebView及所遇到的一些坑都有一定的了解,希望对你有帮助。给我点赞

Android WebView及WebView的神坑之旅相关推荐

  1. android webview mailto,Webview email link (mailto)

    问题 I have a view and view the site has malito code to send email. When I open the link opens in an e ...

  2. android webview详情,Android中的WebView详细介绍

    Android中WebView的详细解释: 1. 概念: WebView(网络视图)能加载显示网页,可以将其视为一个浏览器.它使用了WebKit渲染引擎加载显示网页. 2. 使用方法: (1).实例化 ...

  3. android动态设置错误页面,Android中替换WebView加载网页失败时的页面

    我们用webView去请求一个网页链接的时候,如果请求网页失败或无网络的情况下,它会返回给我们这样一个页面,如下图所示: 上面这个页面就是系统自带的页面,你觉得是不是很丑?反正小编本人觉得非常丑,很难 ...

  4. android学习之WebView

    2019独角兽企业重金招聘Python工程师标准>>> WebView是android.webkit包下的一个组件,能用来显示网页. WebView默认是不带地址栏和加进度条的,单单 ...

  5. Android开发之WebView的开发使用(源代码分享)

    如果我们想提供一个web应用程序(或只是一个网页)作为客户端应用程序的一部分,我们可以使用WebView.WebView类是Android的视图类的扩展,它允许您显示web页面的一部分活动布局.担它不 ...

  6. 解决 android 高低版本 webView 里内容 自适应屏幕的终极方法

    解决 android 高低版本 webView 里内容 自适应屏幕的终极方法 参考文章: (1)解决 android 高低版本 webView 里内容 自适应屏幕的终极方法 (2)https://ww ...

  7. Android中脱离WebView使用WebSocket实现群聊和推送功能

    WebSocket是Web2.0时代的新产物,用于弥补HTTP协议的某些不足,不过他们之间真实的关系是兄弟关系,都是对socket的进一步封装,其目前最直观的表现就是服务器推送和聊天功能.更多知识参考 ...

  8. Chromium on Android: 认识Chromium WebView

    Android KitKat一项重要的更新就是WebView采用Chromium/Blink渲染引擎,本文简要的叙述了新版WebView的主要特性.需要进一步改进的地方以及WebView的代码结构等. ...

  9. android 代码浏览,Webview实现android简单的浏览器实例代码

    WebView是Android中一个非常实用的组件,它和Safai.Chrome一样都是基于Webkit网页渲染引擎,可以通过加载HTML数据的方式便捷地展现软件的界面,下面通过本文给大家介绍Webv ...

最新文章

  1. c++ log函数_认识这19种深度学习损失函数,才能说你了解深度学习!
  2. Linux+mysql重置_linux环境mysql重置密码
  3. Axure RP使用攻略--入门级(七)之axure元件使用思路的补充
  4. python pytorch自定义_PyTorch使用自定义模块创建数据模型
  5. 2017校招真题在线编程-幸运的袋子
  6. 嵌入式linux开发考试题目,练一练!3道经典嵌入式Linux面试题,答案在文末。
  7. bootstarp js设置列隐藏_Bootstrap框架----DataTables列表移动端适配定义隐藏列
  8. (73)信号发生器DDS三角波设计(一)(第15天)
  9. JavaScript ES5之Object.create函数详解
  10. hbase的协处理器
  11. matlab函数in怎么表示什么意思,in函数(in函数用法)
  12. linux-2.6.32在mini2440开发板上移植(15)之移植看门狗驱动
  13. oracle gbk 无法识别,oracle 字符集总结(超出GBK范围的字符存取问题未解决)
  14. PCL报错:C2248 “pcl::Registration<PointSource,PointTarget,Scalar>::setInputCloud”
  15. ctypes调用海康威视人脸抓拍机并将抓拍的人脸上传到指定地址
  16. win10磁盘占用100%
  17. 展现AI与自动化测试技术之间的神奇化学反应
  18. 马士兵java框架_马士兵java架构师
  19. 未越狱iPhone访问限制密码忘了怎么办
  20. CPU除了导热硅脂还有那些不为人知的散热技巧

热门文章

  1. 【笔记软件obsidian】从入门到精通
  2. 移动GPU渲染原理的流派——IMR、TBR及TBDR
  3. PHP实现对小程序微信支付v2订单的结果查询
  4. 智汀智能家居知识普及篇——家居智能控制方式及控制系统的特点,你知道几个?
  5. HTML5期末考核大作业:基于Html+Css+javascript的网页制作(化妆品公司网站制作)...
  6. MySQL索引详解之索引的利弊以及高效使用
  7. 有苦有乐的算法 --- 一个数组中,有两种数出现了奇数次,其余数都出现了偶数次,找到这两种数
  8. 胶囊网络(CapsulesNet)理解
  9. 企业微信对接网易七鱼机器人
  10. vue数据更新,页面不更新