Flutter 页面嵌入 Android原生 View

前言

文章主要讲解Flutter页面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知识,建议先看完这篇讲解通信的文章

Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel-CSDN博客

数据观察监听,Flutter使用ValueNotifier,Android原生使用LiveData,在实体数据发生改变时,自动刷新。

效果图

图解

1、Android原生端

1.0 PlatformView

Android:ComputeLayoutPlatform.kt

package com.example.flutter_mix_android.ui.flutterplugin.platform;

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.lifecycle.ViewModelProvider
import com.example.flutter_mix_android.R
import com.example.flutter_mix_android.bean.CountBean
import com.example.flutter_mix_android.databinding.LayoutComputeBinding
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView

/**
 * 封装成PlatformView
 */
class ComputeLayoutPlatform(
    context: Context,
    rootContext: Context,
    messenger: BinaryMessenger,
    viewId: Int,
    args: Any?,
) : FrameLayout(context), PlatformView, MethodChannel.MethodCallHandler {

    private lateinit var mChannel: MethodChannel
    private lateinit var bind: LayoutComputeBinding
    private lateinit var viewModel: CountBean

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 发送数据
        private const val ANDROID_GET_FLUTTER_DATA_NOTICE: String = "androidGetFlutterDataNotice" // Android端 获取 Flutter端 数据
        private const val FLUTTER_SEND_ANDROID_DATA_NOTICE: String = "flutterSendAndroidDataNotice" // Flutter端 向 Android端 发送数据
        private const val FLUTTER_GET_ANDROID_DATA_NOTICE: String = "flutterGetAndroidDataNotice" // Flutter端 获取 Android端 数据
    }

    init {
        initChannel(messenger, viewId)
        initView()
        initData(rootContext, args)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger, viewId: Int) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = MethodChannel(messenger, "flutter.mix.android/compute/$viewId")

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        mChannel.setMethodCallHandler(this)
    }

    /**
     * 初始化视图
     */
    private fun initView() {
        LayoutInflater.from(context).inflate(R.layout.layout_compute, this, true)
        bind = LayoutComputeBinding.bind(getChildAt(0))
        bind.add.setOnClickListener {
            val count: Int = viewModel.curNum.value ?: 0
            viewModel.curNum.value = count + 1
        }

        bind.androidSendFlutterData.setOnClickListener {
            androidSendFlutterData()
        }

        bind.androidGetFlutterData.setOnClickListener {
            androidGetFlutterData()
        }
    }

    /**
     * Android端 向 Flutter端 发送数据,PUT 操作
     */
    private fun androidSendFlutterData() {
        val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
        map["androidNum"] = viewModel.curNum.value ?: 0

        mChannel.invokeMethod(
            ANDROID_SEND_FLUTTER_DATA_NOTICE,
            map,
            object : MethodChannel.Result {
                override fun success(result: Any?) {
                    Log.d("TAG", "success:$result")
                    updateFlutterNum((result as? Int) ?: 0)
                }

                override fun error(
                    errorCode: String,
                    errorMessage: String?,
                    errorDetails: Any?
                ) {
                    Log.d(
                        "TAG",
                        "errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
                    )
                }

                /**
                 * Flutter端 未实现 Android端 定义的接口方法
                 */
                override fun notImplemented() {
                    Log.d("TAG", "notImplemented")
                }
            })
    }

    /**
     * Android端 获取 Flutter端 数据,GET 操作
     */
    private fun androidGetFlutterData() {
        // 说一个坑,不传参数可以写null,
        // 但不能这样写,目前它没有这个重载方法,invokeMethod第二个参数是Object类型,所以编译器不会提示错误
        // mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE, object : MethodChannel.Result {

        // public void invokeMethod(@NonNull String method, @Nullable Object arguments)

        mChannel.invokeMethod(
            ANDROID_GET_FLUTTER_DATA_NOTICE,
            null,
            object : MethodChannel.Result {
                override fun success(result: Any?) {
                    Log.d("TAG", "success:$result")
                    updateGetFlutterNum((result as? Int) ?: 0)
                }

                override fun error(
                    errorCode: String,
                    errorMessage: String?,
                    errorDetails: Any?
                ) {
                    Log.d(
                        "TAG",
                        "errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
                    )
                }

                /**
                 * Flutter端 未实现 Android端 定义的接口方法
                 */
                override fun notImplemented() {
                    Log.d("TAG", "notImplemented")
                }
            })
    }

    /**
     * 初始化数据
     */
    private fun initData(rootContext: Context, args: Any?) {
        val owner = rootContext as FlutterFragmentActivity
        viewModel = ViewModelProvider(owner)[CountBean::class.java]
        bind.countBean = viewModel
        bind.lifecycleOwner = owner

        // 获取初始化时 Flutter端 向 Android 传递的参数
        val map: Map<String, Int> = args as Map<String, Int>
        viewModel.getFlutterNum.value = map["flutterNum"]
    }

    /**
     * 监听来自 Flutter端 的消息通道
     *
     * call: Android端 接收到 Flutter端 发来的 数据对象
     * result:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        // 获取调用函数的名称
        val methodName: String = call.method
        when (methodName) {
            FLUTTER_SEND_ANDROID_DATA_NOTICE -> {
                // 回调结果对象
                // 获取Flutter端传过来的数据
                val flutterCount: Int? = call.argument<Int>("flutterNum")
                updateFlutterNum(flutterCount ?: 0)
                result.success("success")

                // 回调状态接口对象,里面有三个回调方法
                // result.success(result: Any?)
                // result.error(errorCode: String, errorMessage: String?, errorDetails: Any?)
                // result.notImplemented()
            }

            FLUTTER_GET_ANDROID_DATA_NOTICE -> {
                result.success(viewModel.curNum.value)
            }

            else -> {
                result.notImplemented()
            }
        }
    }

    fun updateFlutterNum(flutterCount: Int) {
        viewModel.flutterNum.value = flutterCount
    }

    fun updateGetFlutterNum(flutterCount: Int) {
        viewModel.getFlutterNum.value = flutterCount
    }

    override fun getView(): View? {
        return this
    }

    override fun dispose() {
        // 解除绑定
        mChannel.setMethodCallHandler(null)
    }

}

1.1 PlatformViewFactory

Android:ComputeLayoutPlatformFactory.kt

package com.example.flutter_mix_android.ui.flutterplugin.factory

import android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.platform.ComputeLayoutPlatform
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

/**
 * 通过PlatformView工厂,创建PlatformView
 */
class ComputeLayoutPlatformFactory(
    private val rootContext: Context,
    private val messenger: BinaryMessenger, // 二进制信使
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { // 消息编解码器

    private lateinit var computeLayoutPlatform: ComputeLayoutPlatform

    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        computeLayoutPlatform = ComputeLayoutPlatform(context, rootContext, messenger, viewId, args)
        return computeLayoutPlatform
    }

}

1.2 FlutterPlugin

Android:FlutterPlugin.kt

package com.example.flutter_mix_android.ui.flutterplugin.plugin;

import android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.factory.ComputeLayoutPlatformFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin

/**
 * 将AndroidView 注册为 Flutter插件
 *
 * rootContext:这个context,我是用来作ViewModel观察的,setLifecycleOwner
 */
class ComputeLayoutPlugin(private val rootContext: Context) : FlutterPlugin {

    companion object {
        // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val viewType: String = "com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform"
    }

    /**
     * 连接到flutter引擎时调用
     */
    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        // 将Android原生View 在Flutter引擎上注册
        binding.platformViewRegistry.registerViewFactory(
            viewType,
            ComputeLayoutPlatformFactory(rootContext, binding.binaryMessenger)
        )
    }

    /**
     * 与flutter引擎分离时调用
     */
    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}

}

1.3 注册插件

Android:MainActivity.kt

Ps:建议大家直接使用FlutterFragmentActivity平替掉FlutterActivity,因为

FlutterActivity继承于Activity

FlutterFragmentActivity继承于FragmentActivity,它实现了 LifecycleOwnerViewModelStoreOwner

package com.example.flutter_mix_android.ui.activity

import com.example.flutter_mix_android.ui.flutterplugin.plugin.ComputeLayoutPlugin
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity: FlutterFragmentActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // 注册为Flutter插件
        flutterEngine.plugins.add(ComputeLayoutPlugin(this))
    }

}

1.4 实体 + LiveData

package com.example.flutter_mix_android.bean

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class CountBean : ViewModel() {

    var curNum: MutableLiveData<Int> = MutableLiveData<Int>() // Android端点击次数

    var flutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(接收到的)

    var getFlutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(主动获取的)

}

2、Flutter端

1.0 页面完整代码

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mix_android/bean/count_bean.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  final CountBean countBean = CountBean();

  late MethodChannel channel;

  // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
  final String viewType = 'com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform';
  static const String FLUTTER_SEND_ANDROID_DATA_NOTICE = 'flutterSendAndroidDataNotice'; // Flutter端 向 Android端 发送数据
  static const String FLUTTER_GET_ANDROID_DATA_NOTICE = 'flutterGetAndroidDataNotice'; // Flutter端 获取 Android端 数据
  static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 发送数据
  static const String ANDROID_GET_FLUTTER_DATA_NOTICE = 'androidGetFlutterDataNotice'; // Android端 获取 Flutter端 数据

  /// 初始化消息通道
  initChannel(int viewId) {
    channel = MethodChannel('flutter.mix.android/compute/$viewId'); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    channel.setMethodCallHandler(handler);
  }

  /// 监听来自 Android端 的消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> handler(MethodCall call) async {
    // 获取调用函数的名称
    final String methodName = call.method;
    switch (methodName) {
      case ANDROID_SEND_FLUTTER_DATA_NOTICE:
        {
          int androidCount = call.arguments['androidNum'];
          countBean.androidNum.value = androidCount;
          return '$ANDROID_SEND_FLUTTER_DATA_NOTICE ---> success';
        }
      case ANDROID_GET_FLUTTER_DATA_NOTICE:
        {
          return countBean.curNum.value ?? 0;
        }
      default:
        {
          return PlatformException(
              code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述');
        }
    }
  }

  /// Flutter端 向 Android端 发送数据,PUT 操作
  flutterSendAndroidData() {
    Map<String, int> map = {'flutterNum': countBean.curNum.value};
    channel.invokeMethod(FLUTTER_SEND_ANDROID_DATA_NOTICE, map).then((value) {
      debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Result:$value');
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:$e');
      }
    });
  }

  ///  Flutter端 获取 Android端 数据,GET 操作
  flutterGetAndroidData() {
    channel.invokeMethod(FLUTTER_GET_ANDROID_DATA_NOTICE).then((value) {
      debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Result:$value');
      countBean.getAndroidNum.value = value ?? 0;
    }).catchError((e) {
      if (e is MissingPluginException) {
        debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');
      } else {
        debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:$e');
      }
    });
  }

  /// 累计点击次数
  computeCount() {
    countBean.curNum.value += 1;
  }

  Widget computeWidget() {
    final ButtonStyle btnStyle = ElevatedButton.styleFrom(
        elevation: 0,
        padding: const EdgeInsets.symmetric(horizontal: 12),
        backgroundColor: Colors.white,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(35)));
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Flutter页面',
            style: TextStyle(
                color: Color(0xff0066ff),
                fontSize: 20,
                fontWeight: FontWeight.bold),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 16, bottom: 8),
            child: Row(
              children: [
                ValueListenableBuilder<int>(
                    valueListenable: countBean.curNum,
                    builder: (context, count, _) {
                      return Text('点击次数:$count',
                          style: const TextStyle(fontSize: 16));
                    }),
                Padding(
                  padding: const EdgeInsets.only(left: 16, right: 8),
                  child: ElevatedButton(
                    style: btnStyle,
                    onPressed: computeCount,
                    child: const Text('+1'),
                  ),
                ),
                ElevatedButton(
                  style: btnStyle,
                  onPressed: flutterSendAndroidData,
                  child: const Text('发送给Android端'),
                )
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Row(
              children: [
                ValueListenableBuilder(
                    valueListenable: countBean.getAndroidNum,
                    builder: (context, count, _) {
                      return Text('获取Android页面点击次数:$count',
                          style: const TextStyle(fontSize: 16));
                    }),
                Padding(
                  padding: const EdgeInsets.only(left: 16, right: 3),
                  child: ElevatedButton(
                    style: btnStyle,
                    onPressed: flutterGetAndroidData,
                    child: const Text('获取Android端数据'),
                  ),
                ),
              ],
            ),
          ),
          ValueListenableBuilder(
              valueListenable: countBean.androidNum,
              builder: (context, count, _) {
                return Text('接收Android端发送的点击次数:$count',
                    style: const TextStyle(fontSize: 16));
              }),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xffA4D3EE),
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        height: MediaQuery.of(context).size.height,
        child: SafeArea(
          top: true,
          child: Column(
            children: [
              Expanded(
                  flex: 1,
                  child: AndroidView(
                    viewType: viewType, // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样
                    creationParams: {'flutterNum': countBean.curNum.value}, // Flutter端 初始化时 向Android端 传递的参数
                    creationParamsCodec: const StandardMessageCodec(), // 消息编解码器
                    onPlatformViewCreated: (viewId) {
                      initChannel(viewId);
                      // 使用 viewId 构建不同名称的 MethodChannel,
                      // 主要应用于 多个相同AndroidView一起使用时,避免消息冲突
                      // List<MethodChannel> mChannels = [];
                      // mChannels.add(MethodChannel('flutter.mix.android/compute/$viewId'));
                      // mChannels[0].invokeMethod(method)
                      // mChannels[0].setMethodCallHandler((call) => null)
                    },
                  )),
              Expanded(flex: 1, child: computeWidget()),
            ],
          ),
        ),
      ),
    );
  }

}

1.1 实体 + ValueNotifier

import 'package:flutter/cupertino.dart';

class CountBean {

  ValueNotifier<int> curNum = ValueNotifier<int>(10); // Flutter端点击次数

  ValueNotifier<int> androidNum = ValueNotifier<int>(0); // Android端点击次数(接收到的)

  ValueNotifier<int> getAndroidNum = ValueNotifier<int>(0); // Android端点击次数(主动获取的)

}

6、源码地址

https://github.com/LanSeLianMa/flutter_mix_android

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/337779.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1027 打印沙漏 (20)

本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“*”&#xff0c;要求按下列格式打印 ************ *****所谓“沙漏形状”&#xff0c;是指每行输出奇数个符号&#xff1b;各行符号中心对齐&#xff1b;相邻两行符号数差2&#xff1b;符号数先从大到小顺序递…

数据结构:顺序表 模拟实现及详解

目录 一、线性表 二、顺序表 2.1顺序表的概念及结构 2.1.1静态顺序表 2.2.2动态顺序表 2.2动态顺序表接口实现 一、线性表 线性表&#xff08; linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见…

如何使用iPhone或iPad上的二维码共享Wi-Fi密码?这里有详细步骤

你有没有想过在不泄露网络密码的情况下与客人共享你的家庭或工作Wi-Fi?你肯定不是第一个这样想的人,我们很高兴地通知你,多亏了以下这个的变通方法,你现在可以使用iPhone或iPad做到这一点。 通常,如果你想让其他人访问网络,你需要共享你的Wi-Fi密码。苹果通过引入与任何…

Jetpack Compose -> 分包 自定义Composable

前言 上一章我们讲解了 Compose 基础UI 和 Modifier 关键字&#xff0c;本章主要讲解 Compose 分包以及自定义 Composable&#xff1b; Compose 如何分包 我们在使用 Button 控件的时候&#xff0c;发现如果我们想给按钮设置文本的时候&#xff0c;Button 函数并没有直接提供…

读书笔记-《数据结构与算法》-摘要8[桶排序]

桶排序和归并排序有那么点点类似&#xff0c;也使用了归并的思想。大致步骤如下&#xff1a; 设置一个定量的数组当作空桶。Divide - 从待排序数组中取出元素&#xff0c;将元素按照一定的规则塞进对应的桶子去。对每个非空桶进行排序&#xff0c;通常可在塞元素入桶时进行插入…

C#中ArrayList运行机制及其涉及的装箱拆箱

C#中ArrayList运行机制及其涉及的装箱拆箱 1.1 基本用法1.1.1 属性1.1.2 方法 1.2 内部实现1.3 装箱1.4 拆箱1.5 object对象的相等性比较1.6 总结1.7 其他简单结构类 1.1 基本用法 命名空间&#xff1a; using System.Collections; 1.1.1 属性 Capacity&#xff1a;获取或设…

【代码随想录10】20. 有效的括号 1047. 删除字符串中的所有相邻重复项 150. 逆波兰表达式求值

目录 20. 有效的括号题目描述参考代码 1047. 删除字符串中的所有相邻重复项题目描述参考代码 150. 逆波兰表达式求值题目描述参考代码 20. 有效的括号 题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;…

语音模块学习——LSYT201B模组(深圳雷龙科技)

目录 引子 处理器 外设 音频 蓝牙 模组展示 引子 关注我的老粉们应该知道我之前用过语音模块做东西&#xff0c;那个比较贵要50多。 今天这个淘宝20元左右比那个便宜&#xff0c;之前那个内核是51的&#xff0c;一个8位机。 后面我做东西的时候语音模块可能会换成这个&…

【51单片机】动态数码管

0、前言 参考&#xff1a; 普中51单片机开发攻略–A2.pdf 1、数码管介绍 上一章我们主要是介绍一位数码管的内部结构及控制原理。下面我们再来介 绍下多位数码管及动态显示原理的相关知识。 1.1 多位数码管简介 2、74HC245 和 74HC138 芯片介绍 2.1 74HC245 芯片简介 2.2 7…

Flink入门教程

使用flink时需要提前准备好scala环境 一、创建maven项目 二、添加pom依赖 <properties><scala.version>2.11.12</scala.version></properties><dependency><groupId>org.scala-lang</groupId><artifactId>scala-library<…

探索指针的奇妙世界,程序中的魔法箭头(上)

目录 一.指针是什么二.指针和指针类型1.指针加减整数2.指针的解引用 三.野指针1.野指针形成的原因&#xff08;1&#xff09;指针未初始化指针越界访问 2.如何规避野指针&#xff08;1&#xff09;指针初始化&#xff08;2&#xff09;小心指针越界&#xff08;3&#xff09;指…

用Python实现Excel中的Vlookup功能

目录 一、引言 二、准备工作 三、实现Vlookup功能 1、导入pandas库 2、准备数据 3、实现Vlookup功能 4、处理结果 5、保存结果 四、完整代码示例 五、注意事项 六、总结 一、引言 在Excel中&#xff0c;Vlookup是一个非常实用的函数&#xff0c;它可以帮助我们在表…

014-信息打点-JS架构框架识别泄漏提取API接口枚举FUZZ爬虫插件项目

014-信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&插件项目 #知识点&#xff1a; 1、JS前端架构-识别&分析 2、JS前端架构-开发框架分析 3、JS前端架构-打包器分析 4、JS前端架构-提取&FUZZ 解决&#xff1a; 1、如何从表现中的JS提取…

1.11马原

同一性是事物存在和发展的前提&#xff0c;一方的发展以另一方的发展为条件 同一性使矛盾双方相互吸收有利于自身的因素&#xff0c;在相互作用中各自得到发展 是事物发展根本规律&#xff0c;唯物辩证法的实质和核心 揭示了事物普遍联系的根本内容和变化发展的内在动力 是贯…

VIM工程的编译 / VI的快捷键记录

文章目录 VIM工程的编译 / VI的快捷键记录概述笔记工程的编译工程的编译 - 命令行vim工程的编译 - GUI版vim备注VIM的帮助文件位置VIM官方教程vim 常用快捷键启动vi时, 指定要编辑哪个文件正常模式光标的移动退出不保存 退出保存只保存不退出另存到指定文件移动到行首移动到行尾…

Java面试汇总——jvm篇

目录 JVM的组成&#xff1a; 1、JVM 概述(⭐⭐⭐⭐) 1.1 JVM是什么&#xff1f; 1.2 JVM由哪些部分组成&#xff0c;运行流程是什么&#xff1f; 2、什么是程序计数器&#xff1f;(⭐⭐⭐⭐) 3、介绍一下Java的堆(⭐⭐⭐⭐) 4、虚拟机栈(⭐⭐⭐⭐) 4.1 什么是虚拟机栈&…

【开源】基于JAVA语言的软件学院思政案例库系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理员2.2 普通教师 三、系统展示四、核心代码4.1 查询思政案例4.2 审核思政案例4.3 查询思政课程4.4 思政案例点赞4.5 新增思政案例评语 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的软件学…

2024最新:optee系统开发精讲 - 课程介绍

&#xff08;本课程中如有涉及代码或硬件架构&#xff0c;则对应的版本号&#xff1a;TF-A 2.80&#xff0c;optee 3.20, Linux Kernel 6.3&#xff0c;armv8.79.0的aarch64&#xff09; &#xff08;注意&#xff1a; 该课程没有PPT&#xff0c;该课程是对照代码讲解的&#x…

六、Netty核心模块组件

目录 6.1 BootStrap&#xff0c;ServerBootStrap6.2 Future&#xff0c;ChannelFuture6.3 Channel6.4 Selector6.5 ChannelHandler 以及其实现类6.6 Pipeline 和 ChannelPipeline6.7 ChannelHandlerContext6.8 ChannelOption6.9 EventLoopGroup和其实现类 NioEventLoopGroup6.1…

力扣 第 122 场双周赛 解题报告 | 珂学家 | 脑筋急转弯 + 滑窗反悔堆

前言 整体评价 倒开差点崩盘&#xff0c;T4这个反悔堆写吐了&#xff0c;T3往众数上去猜了&#xff0c;幸好case良心。 T1. 将数组分成最小总代价的子数组 I 思路: 取 nums[1:] 的最小2个值 可以部分排序&#xff0c;这样更快捷 class Solution {public int minimumCost(in…