前 言

一般在Android应用的开发过程中,一些核心的业务功能开发往往需要Android原生的开发,一些需要改动比较频繁的功能且不是核心的业务功能往往只需要在Android应用调用一个网页就行了。在Android开发过程中有一个专门用嵌入网页的控件WebView布局,WebView控件是基于Google Chrome浏览器的内核,只需在逻辑代码里传入网址的url就可以在应用里打开一个网页,但是只是单纯的打开一个网页,不能进行原生与WebView数据的交互。比如,一个web页面要调用手机系统的相机和相册的话不进行二次封装的话是无法实现的。

在未封装的WebView控件对web页面属性的兼容性方面不是很友好,比如在播放一个网页视频、打开网页图片等方面不如原生的好,而通过封装后web页面的体验方面趋向原生。

下面就正式进入编码的操作流程吧。

新建一个js通信接口ImageClickInterface.java类

/*** js通信接口*/
public class ImageClickInterface {private Context context;public ImageClickInterface(Context context) {this.context = context;}@JavascriptInterfacepublic void imageClick(String imgUrl, String hasLink) {
//        Toast.makeText(context, "----点击了图片", Toast.LENGTH_SHORT).show();// 查看大图
//        Intent intent = new Intent(context, ViewBigImageActivity.class);
//        context.startActivity(intent);Log.e("----点击了图片 url: ", "" + imgUrl);}@JavascriptInterfacepublic void textClick(String type, String item_pk) {if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(item_pk)) {Toast.makeText(context, "----点击了文字", Toast.LENGTH_SHORT).show();}}
}

该类的作用就是实现js与原生系统属性的交互。

新建一个IWebPageView.java接口类

public interface IWebPageView {// 隐藏进度条void hindProgressBar();// 显示webviewvoid showWebView();// 隐藏webviewvoid hindWebView();/*** 进度条变化时调用*/void startProgress(int newProgress);/*** 添加js监听*/void addImageClickListener();/*** 播放网络视频全屏调用*/void fullViewAddView(View view);void showVideoFullView();void hindVideoFullView();}

该接口的作用就是实现监听回调网页的进度条隐藏、WebView显示和隐藏、进度条变化的监听、js监听以及播放全屏网络视频监听的接口回调。

新建一个MyWebChromeClient.java监听类

/*** - 播放网络视频配置* - 上传图片(兼容)*/
public class MyWebChromeClient extends WebChromeClient {private ValueCallback<Uri> mUploadMessage;private ValueCallback<Uri[]> mUploadMessageForAndroid5;public static int FILECHOOSER_RESULTCODE = 1;public static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;private View mXProgressVideo;private WebViewActivity mActivity;private IWebPageView mIWebPageView;private View mXCustomView;private CustomViewCallback mXCustomViewCallback;public MyWebChromeClient(IWebPageView mIWebPageView) {this.mIWebPageView = mIWebPageView;this.mActivity = (WebViewActivity) mIWebPageView;}// 播放网络视频时全屏会被调用的方法@Overridepublic void onShowCustomView(View view, CustomViewCallback callback) {mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);mIWebPageView.hindWebView();// 如果一个视图已经存在,那么立刻终止并新建一个if (mXCustomView != null) {callback.onCustomViewHidden();return;}mActivity.fullViewAddView(view);mXCustomView = view;mXCustomViewCallback = callback;mIWebPageView.showVideoFullView();}// 视频播放退出全屏会被调用的@Overridepublic void onHideCustomView() {if (mXCustomView == null)// 不是全屏播放状态return;mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);mXCustomView.setVisibility(View.GONE);if (mActivity.getVideoFullView() != null) {mActivity.getVideoFullView().removeView(mXCustomView);}mXCustomView = null;mIWebPageView.hindVideoFullView();mXCustomViewCallback.onCustomViewHidden();mIWebPageView.showWebView();}// 视频加载时进程loading@Overridepublic View getVideoLoadingProgressView() {if (mXProgressVideo == null) {LayoutInflater inflater = LayoutInflater.from(mActivity);mXProgressVideo = inflater.inflate(R.layout.video_loading_progress, null);}return mXProgressVideo;}@Overridepublic void onProgressChanged(WebView view, int newProgress) {super.onProgressChanged(view, newProgress);mIWebPageView.startProgress(newProgress);}/*** 判断是否是全屏*/public boolean inCustomView() {return (mXCustomView != null);}@Overridepublic void onReceivedTitle(WebView view, String title) {super.onReceivedTitle(view, title);// 设置titlemActivity.setTitle(title);this.title = title;}private String title = "";public String getTitle() {return title + " ";}//扩展浏览器上传文件//3.0++版本public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {openFileChooserImpl(uploadMsg);}//3.0--版本public void openFileChooser(ValueCallback<Uri> uploadMsg) {openFileChooserImpl(uploadMsg);}public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {openFileChooserImpl(uploadMsg);}// For Android > 5.0@Overridepublic boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, FileChooserParams fileChooserParams) {openFileChooserImplForAndroid5(uploadMsg);return true;}private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {mUploadMessage = uploadMsg;Intent i = new Intent(Intent.ACTION_GET_CONTENT);i.addCategory(Intent.CATEGORY_OPENABLE);i.setType("image/*");mActivity.startActivityForResult(Intent.createChooser(i, "文件选择"), FILECHOOSER_RESULTCODE);}private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {mUploadMessageForAndroid5 = uploadMsg;Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);contentSelectionIntent.setType("image/*");Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);chooserIntent.putExtra(Intent.EXTRA_TITLE, "图片选择");mActivity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);}/*** 5.0以下 上传图片成功后的回调*/public void mUploadMessage(Intent intent, int resultCode) {if (null == mUploadMessage)return;Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();mUploadMessage.onReceiveValue(result);mUploadMessage = null;}/*** 5.0以上 上传图片成功后的回调*/public void mUploadMessageForAndroid5(Intent intent, int resultCode) {if (null == mUploadMessageForAndroid5)return;Uri result = (intent == null || resultCode != RESULT_OK) ? null : intent.getData();if (result != null) {mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});} else {mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});}mUploadMessageForAndroid5 = null;}}

该监听类继承于WebChromeClient类,它的作用就是实现播放网络视频的配置、上传文件以及调用原生的相册上传图片等操作。

新建一个MyWebViewClient.java监听类

/*** 监听网页链接:* - 优酷视频直接跳到自带浏览器* - 根据标识:打电话、发短信、发邮件* - 进度条的显示* - 添加javascript监听*/
public class MyWebViewClient extends WebViewClient {private IWebPageView mIWebPageView;private WebViewActivity mActivity;public MyWebViewClient(IWebPageView mIWebPageView) {this.mIWebPageView = mIWebPageView;mActivity = (WebViewActivity) mIWebPageView;}@SuppressWarnings("deprecation")@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {
//        DebugUtil.error("----url:"+url);if (TextUtils.isEmpty(url)) {return false;}if (url.startsWith("http:") || url.startsWith("https:")) {// 可能有提示下载Apk文件if (url.contains(".apk")) {handleOtherwise(mActivity, url);return true;}return false;}handleOtherwise(mActivity, url);return true;}@Overridepublic void onPageFinished(WebView view, String url) {if (!CheckNetwork.isNetworkConnected(mActivity)) {mIWebPageView.hindProgressBar();}// html加载完成之后,添加监听图片的点击js函数mIWebPageView.addImageClickListener();super.onPageFinished(view, url);}// 视频全屏播放按返回页面被放大的问题@Overridepublic void onScaleChanged(WebView view, float oldScale, float newScale) {super.onScaleChanged(view, oldScale, newScale);if (newScale - oldScale > 7) {view.setInitialScale((int) (oldScale / newScale * 100)); //异常放大,缩回去。}}/*** 网页里可能唤起其他的app*/private void handleOtherwise(Activity activity, String url) {String appPackageName = "";// 支付宝支付if (url.contains("alipays")) {appPackageName = "com.eg.android.AlipayGphone";// 微信支付} else if (url.contains("weixin://wap/pay")) {appPackageName = "com.tencent.mm";// 京东产品详情} else if (url.contains("openapp.jdmobile")) {appPackageName = "com.jingdong.app.mall";} else {startActivity(url);}if (BaseTools.isApplicationAvilible(activity, appPackageName)) {startActivity(url);}}private void startActivity(String url) {try {Intent intent1 = new Intent();intent1.setAction("android.intent.action.VIEW");Uri uri = Uri.parse(url);intent1.setData(uri);intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mActivity.startActivity(intent1);} catch (Exception e) {e.printStackTrace();}}}

该监听类继承于WebViewClient类,它的作用就是实现原生系统一些常用的功能,比如在当在一个网页里下载完一个文件的后缀名.apk格式时,系统就会自动打开这个文件进行安装操作。除此之外,该监听类还实现在web页面调用系统原生打电话、发短信、发邮件以及唤起其它应用(比如支付时唤起支付宝或微信支付应用的支付功能)的功能。

新建一个WebViewActivity.java类

/*** 网页可以处理:* 点击相应控件:拨打电话、发送短信、发送邮件、上传图片、播放视频* 进度条、返回网页上一层、显示网页标题*/
public class WebViewActivity extends AppCompatActivity implements IWebPageView {// 进度条private ProgressBar mProgressBar;private WebView webView;// 全屏时视频加载viewprivate FrameLayout videoFullView;private Toolbar mTitleToolBar;// 加载视频相关private MyWebChromeClient mWebChromeClient;// titleprivate String mTitle;// 网页链接private String mUrl;// 可滚动的title 使用复杂 文字显示有渐变效果,文字两旁没有阴影private TextSwitcher mTsTitle;// 可滚动的title 使用简单 没有渐变效果,文字两旁有阴影private TextView tvGunTitle;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_web_view);getIntentData();initTitle();initWebView();webView.loadUrl(mUrl);}private void initTitle() {StatusBarUtil.setColor(this, CommonUtils.getColor(R.color.colorPrimaryDark), 0);mProgressBar = findViewById(R.id.pb_progress);webView = findViewById(R.id.webview_detail);videoFullView = findViewById(R.id.video_fullView);mTitleToolBar = findViewById(R.id.title_tool_bar);mTsTitle = findViewById(R.id.ts_title);tvGunTitle = findViewById(R.id.tv_gun_title);initToolBar();}private void initToolBar() {setSupportActionBar(mTitleToolBar);ActionBar actionBar = getSupportActionBar();if (actionBar != null) {//去除默认Title显示actionBar.setDisplayShowTitleEnabled(false);}mTitleToolBar.setOverflowIcon(ContextCompat.getDrawable(this, R.drawable.actionbar_more));tvGunTitle.postDelayed(() -> tvGunTitle.setSelected(true), 1900);setTitle(mTitle);}private void initTextSwitch() {mTsTitle.setFactory(() -> {TextView textView = new TextView(this);textView.setTextAppearance(this, R.style.WebTitle);textView.setSingleLine(true);textView.setEllipsize(TextUtils.TruncateAt.MARQUEE);textView.postDelayed(() -> textView.setSelected(true), 1900);return textView;});mTsTitle.setInAnimation(this, android.R.anim.fade_in);mTsTitle.setOutAnimation(this, android.R.anim.fade_out);setTitle(mTitle);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.webview_menu, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {switch (item.getItemId()) {case android.R.id.home:// 返回键onBackPressed();break;case R.id.actionbar_share:// 分享到String shareText = mWebChromeClient.getTitle() + webView.getUrl() + "(分享自XX)";ShareUtils.share(WebViewActivity.this, shareText);break;case R.id.actionbar_cope:// 复制链接BaseTools.copy(webView.getUrl());ToastUtil.showToast("复制成功");break;case R.id.actionbar_open:// 打开链接BaseTools.openLink(WebViewActivity.this, webView.getUrl());break;default:break;}return super.onOptionsItemSelected(item);}private void getIntentData() {if (getIntent() != null) {mTitle = getIntent().getStringExtra("mTitle");mUrl = getIntent().getStringExtra("mUrl");}}public void setTitle(String mTitle) {tvGunTitle.setText(mTitle);}private void initWebView() {mProgressBar.setVisibility(View.VISIBLE);WebSettings ws = webView.getSettings();// 网页内容的宽度是否可大于WebView控件的宽度ws.setLoadWithOverviewMode(false);// 保存表单数据ws.setSaveFormData(true);// 是否应该支持使用其屏幕缩放控件和手势缩放ws.setSupportZoom(true);ws.setBuiltInZoomControls(true);ws.setDisplayZoomControls(false);// 启动应用缓存ws.setAppCacheEnabled(true);// 设置缓存模式ws.setCacheMode(WebSettings.LOAD_DEFAULT);// setDefaultZoom  api19被弃用// 设置此属性,可任意比例缩放。ws.setUseWideViewPort(true);// 不缩放webView.setInitialScale(100);// 告诉WebView启用JavaScript执行。默认的是false。ws.setJavaScriptEnabled(true);//  页面加载好以后,再放开图片ws.setBlockNetworkImage(false);// 使用localStorage则必须打开ws.setDomStorageEnabled(true);// 排版适应屏幕ws.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);// WebView是否新窗口打开(加了后可能打不开网页)
//        ws.setSupportMultipleWindows(true);// webview从5.0开始默认不允许混合模式,https中不能加载http资源,需要设置开启。if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {ws.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);}/** 设置字体默认缩放大小(改变网页字体大小,setTextSize  api14被弃用)*/ws.setTextZoom(100);mWebChromeClient = new MyWebChromeClient(this);webView.setWebChromeClient(mWebChromeClient);// 与js交互webView.addJavascriptInterface(new ImageClickInterface(this), "injectedObject");webView.setWebViewClient(new MyWebViewClient(this));}@Overridepublic void hindProgressBar() {mProgressBar.setVisibility(View.GONE);}@Overridepublic void showWebView() {webView.setVisibility(View.VISIBLE);}@Overridepublic void hindWebView() {webView.setVisibility(View.INVISIBLE);}@Overridepublic void fullViewAddView(View view) {FrameLayout decor = (FrameLayout) getWindow().getDecorView();videoFullView = new FullscreenHolder(WebViewActivity.this);videoFullView.addView(view);decor.addView(videoFullView);}@Overridepublic void showVideoFullView() {videoFullView.setVisibility(View.VISIBLE);}@Overridepublic void hindVideoFullView() {videoFullView.setVisibility(View.GONE);}@Overridepublic void startProgress(int newProgress) {mProgressBar.setVisibility(View.VISIBLE);mProgressBar.setProgress(newProgress);if (newProgress == 100) {mProgressBar.setVisibility(View.GONE);}}@Overridepublic void addImageClickListener() {// 这段js函数的功能就是,遍历所有的img节点,并添加onclick函数,函数的功能是在图片点击的时候调用本地java接口并传递url过去// 如要点击一张图片在弹出的页面查看所有的图片集合,则获取的值应该是个图片数组webView.loadUrl("javascript:(function(){" +"var objs = document.getElementsByTagName(\"img\");" +"for(var i=0;i<objs.length;i++)" +"{" +//  "objs[i].onclick=function(){alert(this.getAttribute(\"has_link\"));}" +"objs[i].onclick=function(){window.injectedObject.imageClick(this.getAttribute(\"src\"),this.getAttribute(\"has_link\"));}" +"}" +"})()");// 遍历所有的a节点,将节点里的属性传递过去(属性自定义,用于页面跳转)webView.loadUrl("javascript:(function(){" +"var objs =document.getElementsByTagName(\"a\");" +"for(var i=0;i<objs.length;i++)" +"{" +"objs[i].onclick=function(){" +"window.injectedObject.textClick(this.getAttribute(\"type\"),this.getAttribute(\"item_pk\"));}" +"}" +"})()");}public FrameLayout getVideoFullView() {return videoFullView;}/*** 全屏时按返加键执行退出全屏方法*/public void hideCustomView() {mWebChromeClient.onHideCustomView();setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}/*** 上传图片之后的回调*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent intent) {if (requestCode == MyWebChromeClient.FILECHOOSER_RESULTCODE) {mWebChromeClient.mUploadMessage(intent, resultCode);} else if (requestCode == MyWebChromeClient.FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {mWebChromeClient.mUploadMessageForAndroid5(intent, resultCode);}}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK) {//全屏播放退出全屏if (mWebChromeClient.inCustomView()) {hideCustomView();return true;//返回网页上一页} else if (webView.canGoBack()) {webView.goBack();return true;//退出网页} else {finish();}}return false;}@Overrideprotected void onPause() {super.onPause();webView.onPause();}@Overrideprotected void onResume() {super.onResume();webView.onResume();// 支付宝网页版在打开文章详情之后,无法点击按钮下一步webView.resumeTimers();// 设置为横屏if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}}@Overrideprotected void onDestroy() {super.onDestroy();videoFullView.removeAllViews();if (webView != null) {webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);webView.clearHistory();ViewGroup parent = (ViewGroup) webView.getParent();if (parent != null) {parent.removeView(webView);}webView.removeAllViews();webView.stopLoading();webView.setWebChromeClient(null);webView.setWebViewClient(null);webView.destroy();webView = null;}}/*** 打开网页:** @param mContext 上下文* @param mUrl     要加载的网页url* @param mTitle   title*/public static void loadUrl(Context mContext, String mUrl, String mTitle) {if (CheckNetwork.isNetworkConnected(mContext)) {Intent intent = new Intent(mContext, WebViewActivity.class);intent.putExtra("mUrl", mUrl);intent.putExtra("mTitle", mTitle == null ? "" : mTitle);mContext.startActivity(intent);} else {ToastUtil.showToastLong("当前网络不可用,请检查您的网络设置");}}}

该Activity类的作用就是实现由上一个Activity或Fragment界面传递过来的url地址,而该Activity负责打开url地址并解析显示url的内容(网页)。

调用方式

在Android项目里新建Activity类或者Fragment类,然后在事件点击的地方调用以下代码:

WebViewActivity.loadUrl(this, "https://www.baidu.com", "百度");

在调用WebViewActivity的loadUrl方法里有三个参数,第一个参数为上下文,第二参数为要打开网页(或视频、图片)的url地址,第三参数为自定义网页显示的标题栏。

最后不要忘了在AndroidStudio的清单文件AndroidManifest.xml里添加访问网络相关权限。

    <!-- 允许联网 --><uses-permission android:name="android.permission.INTERNET" /><!-- 允许查看当前网络状态 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

这两个权限必须要填全,缺一不可。

apk下载体验地址:
可以扫描以下二维码进行下载安装,或者点击以下链接 https://fir.im/webviewapp 进行下载安装。

———————— The end ————————

如果您觉得这篇博客写的比较好的话,赞赏一杯咖啡吧~~


Demo程序源码下载地址一(GitHub)
Demo程序源码下载地址二(码云)

Android开发-Android原生与WebView的js交互的实现相关推荐

  1. 移动开发周刊:Android ImageView正确使用、WebView与JS交互解析

    写在前面 从 2011 年创建首刊至今,移动开发周刊内容聚焦 Android.iOS.VR/AR 等前沿移动开发技术,精选一周最热点,解读开发技巧,从中希望能够让你有一些收获,如果你有好的文章以及优化 ...

  2. Android WebView与JS交互入门

    2019独角兽企业重金招聘Python工程师标准>>> 首先在Anndroid代码中对WebView进行初始化 webView = (WebView) findViewById(R. ...

  3. android list嵌套list,Android开发日常-listVIiew嵌套webView回显阅读位置

    详情页布局结构 需求是回显webview展示网页的阅读位置 方案1: 使用webview.getScrollY()获取滑动到的位置,用setScrollY()回显设置, 但是两个方法都出现了问题,ge ...

  4. Carson带你学Android:你要的WebView与 JS 交互方式都在这里了

    前言 现在很多App里都内置了Web网页(Hybrid App),比如说很多电商平台,淘宝.京东.聚划算等等,如下图 上述功能是由Android的WebView实现的,其中涉及到Android客户端与 ...

  5. Android开发——Android手机屏幕适配方案总结

    0. 前言 Android的屏幕适配,即使得某一元素在Android不同尺寸.不同分辨率的手机上具备相同的显示效果,这个问题一直以来都是我们Android开发者不得不面对的问题.本文参考了很多前人的博 ...

  6. Android开发-Android studio自带模拟器使用中文拼音输入法

    Android开发-Android studio自带模拟器使用中文拼音输入法 搜狗输入法下载 搜狗输入法安装 修改模拟器语言 搜狗输入法下载 上篇文章中我们在Android studio中创建了X86 ...

  7. WebView---android webview组件如何使用 Webview与js交互

    浏览器控件是每个开发环境都具备的,这为马甲神功提供了用武之地,windows的有webbrowser,android和ios都有webview.只是其引擎不同,相对于微软的webbrowser,and ...

  8. Flutter:加载本地Html、WebView与JS交互

    本次教程使用的是Flutter官方提供的WebView组件webview_flutter 2.3.1,flutter_android 2.2.1 一. WebView介绍 以下为Flutter Web ...

  9. ios webview html交互 卡死,iOS 之webview 的js交互(alert、confirm、prompt)弹窗造成界面卡死...

    概述:当iOS客户端中webView 与js交互,在主线程执行js脚本时,而js脚本存在alert().confirm().prompt()这三种弹窗时会造成iOS界面卡死. 1.造成卡死时的代码如下 ...

最新文章

  1. Iocomp控件教程之Pie Chart——饼状图控件
  2. redis主从集群搭建eclipse_【Redis】Redis 主从模式搭建
  3. linux挂载硬盘_Linux中如何对硬盘进行分区、格式化、挂载使用
  4. fscanf返回值被忽略怎么解决_pytest的fixture怎么用?
  5. mysql实战20 | 幻读是什么,幻读有什么问题?
  6. error和warning指令
  7. Python+Selenium+Edge浏览器安装与简单运行(1/2)
  8. Navicat Premium 11.2.7 中文破解版安装
  9. 华为笔记本没有网线口_笔记本电脑没有网线接口的怎么办?
  10. SCI论文撰写——Conclusion
  11. ubuntu shell命令划重点
  12. 科普|股东需要对企业债务承担连带责任
  13. 小游戏之斗兽棋(uniapp)
  14. bat 自定义位数随机密码生成器
  15. springboot启动 lombok 找不到符号
  16. TF-IDF 算法详解及 Python 实现
  17. 计算机写字的好处,喜欢写字的十大好处!
  18. TCP与Web服务器
  19. 测试地铁速度的软件,地铁速度传感器作用,看这一篇就够了!
  20. 记面试中问到的MySQL的SQL调优问题

热门文章

  1. bWAPP之环境搭建及HTML注入
  2. Unity 优化包体大小
  3. Ubuntu 添加用户,分配 root权限
  4. Docker - Docker挂载mysql
  5. axure 8 表格合并_Python办公自动化(六)|自动更新表格,告别繁琐
  6. css 背景图旋转 只让背景图片旋转180度的实现示例
  7. 最新微信内测版!限时放出!
  8. ESP8266模块连接手机WIFI热点
  9. 自考英语基础差怎么学?
  10. php中的lc_time,LC_TIME - [ C语言中文开发手册 ] - 在线原生手册 - php中文网