版权声明:本文为HaiyuKing原创文章,转载请注明出处!

前言

封装webview的常用配置和选择文件、打开相机、录音、打开本地相册的用法。【如果想要使用简单的预览功能,可以参考《MyBridgeWebViewDemo【集成JsBridge开源库的的封装的webview】》】

注意:如果使用选择文件、打开相机、录音、打开本地相册的功能,那么就需要搭配《Android6.0运行时权限(基于RxPermission开源库)》的申请运行时权限(相机、录音、存储权限)、《AppUtils【获取手机的信息和应用版本号、安装apk】》的适配7.0FileProvider功能。

效果图

  

代码分析

一、申请运行时权限主要涉及到以下文件:

app中的build.gradle

AndroidManifest.xml

MainActivity.java

二、适配7.0File Provider主要涉及到以下文件:

AndroidManifest.xml

xml/provider_paths.xml

使用步骤

一、项目组织结构图

注意事项:

1、 导入类文件后需要change包名以及重新import R文件路径

2、 Values目录下的文件(strings.xml、dimens.xml、colors.xml等),如果项目中存在,则复制里面的内容,不要整个覆盖

二、导入步骤

0-1、申请运行时权限,参考《Android6.0运行时权限(基于RxPermission开源库)

0-2、适配Android7.0FileProvider功能,参考《AppUtils【获取手机的信息和应用版本号、安装apk】

1、将assets文件夹复制到项目中

404.html【自定义404页面,会调用WebViewJSInterface中的refresh方法】

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<meta http-equiv="keywords" content="404">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit"> <title>404哟</title>
</head>
<body>
<div class="demo">
<p><span>4</span><span>0</span><span>4</span></p>
<p>网络正在开小差(´・ω・`)</p><br/>
<p><a onclick="window.androidMethod.refresh();">重新加载</a></p>
</div>
</body>
</html> <style type="text/css">
body {
background-color: #ECECEC;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
color: #3c3c3c;
} .demo p:first-child {
text-align: center;
font-family: cursive;
font-size: 150px;
font-weight: bold;
line-height: 100px;
letter-spacing: 5px;
color: #fff;
} .demo p:first-child span {
cursor: pointer;
text-shadow: 0px 0px 2px #686868,
0px 1px 1px #ddd,
0px 2px 1px #d6d6d6,
0px 3px 1px #ccc,
0px 4px 1px #c5c5c5,
0px 5px 1px #c1c1c1,
0px 6px 1px #bbb,
0px 7px 1px #777,
0px 8px 3px rgba(100, 100, 100, 0.4),
0px 9px 5px rgba(100, 100, 100, 0.1),
0px 10px 7px rgba(100, 100, 100, 0.15),
0px 11px 9px rgba(100, 100, 100, 0.2),
0px 12px 11px rgba(100, 100, 100, 0.25),
0px 13px 15px rgba(100, 100, 100, 0.3);
-webkit-transition: all .1s linear;
transition: all .1s linear;
} .demo p:first-child span:hover {
text-shadow: 0px 0px 2px #686868,
0px 1px 1px #fff,
0px 2px 1px #fff,
0px 3px 1px #fff,
0px 4px 1px #fff,
0px 5px 1px #fff,
0px 6px 1px #fff,
0px 7px 1px #777,
0px 8px 3px #fff,
0px 9px 5px #fff,
0px 10px 7px #fff,
0px 11px 9px #fff,
0px 12px 11px #fff,
0px 13px 15px #fff;
-webkit-transition: all .1s linear;
transition: all .1s linear;
} .demo p:not(:first-child) {
text-align: center;
color: #666;
font-family: cursive;
font-size: 20px;
text-shadow: 0 1px 0 #fff;
letter-spacing: 1px;
line-height: 2em;
margin-top: -50px;
}
</style>

demo.html【用于演示选择文件、打开相机、录音、打开本地相册功能】

<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
<meta http-equiv="keywords" content="测试">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>
webview
</title>
</head> <body>
<p>
<input type="file" value="打开文件" />
</p> <p>
点击下面的按钮,获取的文件路径:
</p>
<p>
<input type="text" id="filePath" value="文件路径" style="width:100%"/>
</p>
<p>
<input type="button" id="openRecord" value="打开录音" onclick="window.androidMethod.openRecord();"/>
</p>
<p>
<input type="button" id="takePicture" value="打开相机" onclick="window.androidMethod.takePicture();"/>
</p>
<p>
<input type="button" id="choosePic" value="打开本地相册" onclick="window.androidMethod.choosePic();"/>
</p>
<p>
<a href='tel:10010'>拨打电话:10010</a>
</p>
</body>
<script>
//打开录音、打开相机、打开本地相册,选择文件后返回的路径
function setInputText(urlPath){
document.getElementById("filePath").value = urlPath;
}
</script> </html>

demo.html

2、将customwebview包复制到项目中

3、将mywebview_progress_dialog_img_drawable.xml复制到项目中

<?xml version="1.0" encoding="utf-8"?>
<!-- WebView使用的进度加载对话框进度圆圈 -->
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/mywebview_progress_dialog_img"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0.0"
android:toDegrees="360.0"
android:repeatMode="restart"
/>

mywebview_progress_dialog_img_drawable.xml

4、将mywebview_progress_dialog_img.png图片复制到项目中

5、将mywebview_dialog_webviewprogress.xml复制到项目中

<?xml version="1.0" encoding="utf-8"?>
<!-- WebView使用的进度加载对话框布局文件 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_view"
android:layout_width="match_parent"
android:layout_height="match_parent"> <!-- 自定义圆形进度条 -->
<!-- android:indeterminateDrawable自定义动画图标 -->
<ProgressBar
android:id="@+id/loadProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:indeterminateDrawable="@drawable/mywebview_progress_dialog_img_drawable"
/> </RelativeLayout>

mywebview_dialog_webviewprogress.xml

6、在styles.xml文件中添加以下代码

<resources>

    <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style> <!-- ==================MyWebview========网页加载时的进度对话框========================== -->
<style name="mywebview_loading_style" parent="android:style/Theme.Dialog">
<!-- Dialog的windowFrame框为无 -->
<item name="android:windowFrame">@null</item>
<!-- 是否显示title -->
<item name="android:windowNoTitle">true</item>
<!-- 是否浮现在activity之上 -->
<item name="android:windowIsFloating">true</item>
<!-- 设置dialog的背景:#00000000透明色 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 背景变灰:整个屏幕变灰,配合setCanceledOnTouchOutside(false) -->
<item name="android:backgroundDimEnabled">false</item>
<!-- 对话框是否有遮盖 -->
<item name="android:windowContentOverlay">@null</item>
</style> </resources>

7、在AndroidManifest.xml中添加以下代码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.why.project.mywebviewdemo"> <!-- ======================(MyWebView)========================== -->
<!-- 允许程序打开网络套接字 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- ======================拍照用到的========================== -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- ======================录音用到的========================== -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 向SD卡写入数据权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"> <!-- =================7.0上读取文件========================== -->
<!--参考资料https://blog.csdn.net/lmj623565791/article/details/72859156-->
<!--authorities:{app的包名}.provider
grantUriPermissions:必须是true,表示授予 URI 临时访问权限
exported:必须是false
resource:中的@xml/provider_paths是我们接下来要添加的文件-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider> <activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- 网页页面 -->
<activity android:name=".MyWebviewActivity">
</activity>
</application> </manifest>

三、使用方法

在布局文件activity_mywebview.xml中声明【实际项目中实际新的完整路径】

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <com.why.project.mywebviewdemo.customwebview.mywebview.MyWebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"> </com.why.project.mywebviewdemo.customwebview.mywebview.MyWebView> </android.support.constraint.ConstraintLayout>

在Activity中使用如下

package com.why.project.mywebviewdemo;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.widget.Toast; import com.why.project.mywebviewdemo.customwebview.mywebview.MyWebView;
import com.why.project.mywebviewdemo.customwebview.mywebview.WebViewJSInterface;
import com.why.project.mywebviewdemo.customwebview.utils.GetPathFromUri4kitkat;
import com.why.project.mywebviewdemo.customwebview.utils.WebviewGlobals; import java.io.File; /**
* Created by HaiyuKing
* Used webview
*/ public class MyWebviewActivity extends AppCompatActivity {
private static final String TAG = MyWebviewActivity.class.getSimpleName(); private MyWebView myWebView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mywebview); initViews();
initDatas();
initEvents();
} @Override
public void onDestroy()
{
//销毁webview控件
myWebView.removeAllViews();
myWebView.destroy();
super.onDestroy();
} private void initViews() {
myWebView = findViewById(R.id.web_view);
myWebView.setCanBackPreviousPage(true,MyWebviewActivity.this);//可以返回上一页
} private void initDatas() {
String openUrl = getIntent().getExtras().getString("urlKey");
if(TextUtils.isEmpty(openUrl)){
myWebView.loadLocalUrl("demo.html");
}else {
myWebView.loadWebUrl(openUrl);
}
} private void initEvents() { } /*=========================================实现webview调用相机、打开文件管理器功能==============================================*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.w(TAG, "{onActivityResult}resultCode="+resultCode);
Log.w(TAG, "{onActivityResult}requestCode="+requestCode);
Log.w(TAG, "{onActivityResult}data="+data);
if (resultCode == Activity.RESULT_OK) {
//webview界面调用打开本地文件管理器选择文件的回调
if (requestCode == WebviewGlobals.CHOOSE_FILE_REQUEST_CODE ) {
Uri result = data == null ? null : data.getData();
Log.w(TAG,"{onActivityResult}文件路径地址:" + result.toString()); //如果mUploadMessage或者mUploadCallbackAboveL不为空,代表是触发input[type]类型的标签
if (null != myWebView.getMyWebChromeClient().getmUploadMessage() || null != myWebView.getMyWebChromeClient().getmUploadCallbackAboveL()) {
if (myWebView.getMyWebChromeClient().getmUploadCallbackAboveL() != null) {
onActivityResultAboveL(requestCode, data);//5.0++
} else if (myWebView.getMyWebChromeClient().getmUploadMessage() != null) {
myWebView.getMyWebChromeClient().getmUploadMessage().onReceiveValue(result);//将文件路径返回去,填充到input中
myWebView.getMyWebChromeClient().setmUploadMessage(null);
}
}else{
//此处代码是处理通过js方法触发的情况
Log.w(TAG,"{onActivityResult}文件路径地址(js):" + result.toString());
String filePath = GetPathFromUri4kitkat.getPath(MyWebviewActivity.this, Uri.parse(result.toString())); setUrlPathInput(myWebView,"打开本地相册:" + filePath);//修改网页输入框文本
}
}
//因为拍照指定了路径,所以data值为null
if(requestCode == WebviewGlobals.CAMERA_REQUEST_CODE){
File pictureFile = new File(WebViewJSInterface.mCurrentPhotoPath); Uri uri = Uri.fromFile(pictureFile);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(uri);
MyWebviewActivity.this.sendBroadcast(intent); // 这里我们发送广播让MediaScanner 扫描我们制定的文件
// 这样在系统的相册中我们就可以找到我们拍摄的照片了【但是这样一来,就会执行MediaScanner服务中onLoadFinished方法,所以需要注意】 //拍照
// String fileName = FileUtils.getFileName(WebViewJSInterface.mCurrentPhotoPath);
Log.e(TAG,"WebViewJSInterface.mCurrentPhotoPath="+ WebViewJSInterface.mCurrentPhotoPath);
setUrlPathInput(myWebView,"打开相机:" + WebViewJSInterface.mCurrentPhotoPath);//修改网页输入框文本
} //录音
if(requestCode == WebviewGlobals.RECORD_REQUEST_CODE){
Uri result = data == null ? null : data.getData();
Log.w(TAG,"录音文件路径地址:" + result.toString());//录音文件路径地址:content://media/external/audio/media/111 String filePath = GetPathFromUri4kitkat.getPath(MyWebviewActivity.this, Uri.parse(result.toString()));
Log.w(TAG,"录音文件路径地址:" + filePath); setUrlPathInput(myWebView,"打开录音:" + filePath);//修改网页输入框文本
}
}else if(resultCode == RESULT_CANCELED){//resultCode == RESULT_CANCELED 解决不选择文件,直接返回后无法再次点击的问题
if (myWebView.getMyWebChromeClient().getmUploadMessage() != null) {
myWebView.getMyWebChromeClient().getmUploadMessage().onReceiveValue(null);
myWebView.getMyWebChromeClient().setmUploadMessage(null);
}
if (myWebView.getMyWebChromeClient().getmUploadCallbackAboveL() != null) {
myWebView.getMyWebChromeClient().getmUploadCallbackAboveL().onReceiveValue(null);
myWebView.getMyWebChromeClient().setmUploadCallbackAboveL(null);
}
}
} //5.0以上版本,由于api不一样,要单独处理
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, Intent data) { if (myWebView.getMyWebChromeClient().getmUploadCallbackAboveL() == null) {
return;
}
Uri result = null;
if (requestCode == WebviewGlobals.CHOOSE_FILE_REQUEST_CODE) {//打开本地文件管理器选择图片
result = data == null ? null : data.getData();
} else if (requestCode == WebviewGlobals.CAMERA_REQUEST_CODE) {//调用相机拍照
File pictureFile = new File(WebViewJSInterface.mCurrentPhotoPath); Uri uri = Uri.fromFile(pictureFile);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(uri);
MyWebviewActivity.this.sendBroadcast(intent); // 这里我们发送广播让MediaScanner 扫描我们制定的文件
// 这样在系统的相册中我们就可以找到我们拍摄的照片了【但是这样一来,就会执行MediaScanner服务中onLoadFinished方法,所以需要注意】 result = Uri.fromFile(pictureFile);
}
Log.w(TAG,"{onActivityResultAboveL}文件路径地址:"+result.toString());
myWebView.getMyWebChromeClient().getmUploadCallbackAboveL().onReceiveValue(new Uri[]{result});//将文件路径返回去,填充到input中
myWebView.getMyWebChromeClient().setmUploadCallbackAboveL(null);
return;
} //设置网页上的文件路径输入框文本
private void setUrlPathInput(WebView webView, String urlPath) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("setInputText('"+ urlPath +"')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.i(TAG, "onReceiveValue value=" + value);
}});
}else{
Toast.makeText(MyWebviewActivity.this,"当前版本号小于19,无法支持evaluateJavascript,需要使用第三方库JSBridge", Toast.LENGTH_SHORT).show();
}
}
}

混淆配置

注意:根据实际项目的路径修改下面表红色的文字

# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#} # Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile #====WebView + js====
-keepclassmembers class com.why.project.mywebviewdemo.customwebview.mywebview.MyWebView {
public *;
}
-keepclassmembers class com.why.project.mywebviewdemo.customwebview.mywebview.MyWebChromeClient {
public *;
}
-keepclassmembers class com.why.project.mywebviewdemo.customwebview.mywebview.MyWebViewClient {
public *;
}
# keep 使用 webview 的类
-keepclassmembers class com.why.project.mywebviewdemo.MyWebviewActivity {
public *;
}
-keepattributes *Annotation*
#解决:android sdk api >= 17 时需要加@JavascriptInterface”所出现的问题。
-keepattributes *JavascriptInterface*

参考资料

Android-WebView-解决对选择文件 input type="file"无响应

项目demo下载地址

https://github.com/haiyuKing/MyWebviewDemo

MyWebViewDemo【封装Webview常用配置和选择文件、打开相机、录音、打开本地相册的用法】的更多相关文章

  1. android系统webview使用input实现选择文件并预览

    一般系统的实现方式: 代码实现 <!doctype html> <html> <head> <meta charset="utf-8"&g ...

  2. log4j常用配置以及日志文件保存位置

    log4j.rootLogger=INFO,CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender ...

  3. Tomcat server.xml常用配置 含有外带文件及默认host

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE server-xml [<!ENTITY ...

  4. MFC_选择目录对话框_选择文件对话框_指定目录遍历文件

    选择目录对话框 void C资源共享吧视频广告清理工具Dlg::OnBnClickedCls() { // 清空编辑框内容 m_Edit.SetWindowTextW(L""); ...

  5. UltraEdit设置打开的文件类型,怎么打开大文本文件

    点击高级,配置,选择文件处理下的临时文件,设置如图即可打开超大文本文件. 补充:视图——显示行号.

  6. IOS研究院之打开照相机与本地相册选择图片

    如下图所示 在本地相册中选择一张图片后,我们将他拷贝至沙盒当中,在客户端中将它的缩略图放在按钮旁边,这个结构其实和新浪微薄中选择图片后的效果一样.最终点击发送将按钮将图片2进制图片上传服务器. 下面我 ...

  7. IOS研究院之打开照相机与本地相册选择图片(六)

    原创文章如需转载请注明:转载自雨松MOMO程序研究院本文链接地址:IOS研究院之打开照相机与本地相册选择图片(六) Hello 大家好 IOS的文章好久都木有更新了,今天更新一篇哈. 这篇文章主要学习 ...

  8. SpringMVC常用配置(二),最简洁的配置实现文件上传

    Spring.SpringMVC持续介绍中,基础配置前面已经介绍了很多,如果小伙伴们还不熟悉可以参考这几篇文章: 1.Spring基础配置 2.Spring常用配置 3.Spring常用配置(二) 4 ...

  9. zend studio一些常用配置

    zend studio 常用 配置 1.zend中添加注释是ctrl+slash,这个slash在哪里?如何来取消注释 slash是斜杠'/'那个键,就是在,.之后的那个. 进行注释是 ctrl+'/ ...

随机推荐

  1. Asp.Net Core NLog 将日志输出到数据库以及添加LayoutRenderer的支持

    在这之前打算用Apache的Log4Net,但是发现其AdoNetAppender方法已经不存在了,无法使用配置文件直接输出到数据库了,因此我便改用了NLog框架. 一.对项目添加NLog 通过Nug ...

  2. 提示“本地连接没有有效的ip配置-未修复“窗口

    很多人在使用电脑时可能会遇到了这样一个网络问题,电脑无法连接网络,使用自带网络诊断工具诊断提示:"本地连接没有有效的ip配置".这种网络故障多数是出在使用路由器共享上网的windo ...

  3. election.go

    package)) ].GetResponseRange().Kvs[] ) ].Value),]         }         wch := client.Watch(cctx, string ...

  4. C++11标准中常用到的各种算法汇总.

    在C++11标准中定义了很多算法,这些算法可以让我们很方便的操作各种容器和数组,这里要注意一下,这些算法操作的并非容器,而是迭代器,然后通过迭代器来操作容器中的数据,算法本身并不会关注容器中保存的数据 ...

  5. php与html实现交互的基本操作

    今天我们来实现php与html页面注册和登录的效果.中国有句古话叫: 第一步:我们来了解一些php的基本格式. <?php php代码 ?> 第二步:了解php与js的一些基本区别 我们在 ...

  6. js、jq事件绑定方式总结——以click事件为例

    一.JavaScript点击事件绑定方法 1.1 HTML onclick事件属性 <button onclick="clickMe(this)">click me&l ...

  7. Java IO--字符流--BufferedReader和BufferedWriter

    从昨天开始没事情干时,决定梳理梳理Java IO流,因为觉得太混乱这个东西,妈的,咋就这么多类型,想累死谁啊,这里并不是埋怨创造者,而是气自己看着看着老跑偏,实在看不进去,太多了,想睡觉,所以现在决定 ...

  8. istio添加Fluentd

    这个教程展示了istio如何自定义日志格式,并且将其发送给fluent.Fluentd 是一个开源的日志收集器,支持多种数据输出并且有一个可插拔架构.Elasticsearch是一个流行的后端日志记录 ...

  9. matplotlib解决中文乱码

    调试以前写的matplotlib相关脚本,中文呈方块样:重新解决一遍,感觉比以前的理解更进一步,故而记下一笔: 1. 首先要为matplotlib添加中文字体库: 系统字体库在/usr/share/f ...

  10. EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?

    前言 这个问题从未遇见过,是一位前辈问我EF Core内存泄漏问题时我才去深入探讨这个问题,刚开始我比较惊讶,居然还有这种问题,然后就有了本文,直接拿前辈的示例代码并稍加修改成就了此文,希望对在自学E ...