【Android】MVC与MVP的区别,MVP网络请求实践

news2024/9/23 5:23:22

一、MVC模式

目录

  • 一、MVC模式
  • 二、MVP模式
    • 1、MVP的简单应用
      • 1.1 导入相关依赖包并设置权限
      • 1.2 实现Model
      • 1.2 实现Presenter
      • 1.3 实现View
      • 1.4分析项目结构和绑定过程
      • 1.5效果展示
    • 2、MVP结合RxJava

一、MVC模式

MVC(Model(模型)——View(视图)——Controller(控制器))

Android中MVC的角色定义如下:

  • 模型(Model)层:针对业务模型建立的数据结构和相关类
  • 视图(View)层:一般作为UI的展示XML文件
  • 控制(Controller)层:通常在Activity和Fragment中,或者在由他们控制的业务类中

简单来说MVC就是通过Controller层操作Model层数据,并且返回给VIew展示。

[外链图片转存中…(img-oi8sABEO-1702569842215)]

MVC的缺点:

  • Activity不是一个标准的Controller,它主要是加载应用布局、初始化界面、并且收发用户操作的请求而做出响应
  • VIew层和Model层互相耦合,不易于开发维护

二、MVP模式

MVP(Model——View——presenter)它是MVC的演化版本,MPV角色定义如下:

  • Model:主要负责数据的存取功能
  • View:负责处理用户事件和视图展示,在Android中它可能是Activity、Fragment或者某给View的控件。
  • Presenter:作为View和Model之间的桥梁,它从Model中检索数据返回给View。使得View和Model之间没有耦合

主要的控制逻辑在Presenter里实现,而且Presenter与具体的View是没有直接关联的,而是通过定义好的接口交互。这样使得View变更时Presenter不会受到影响。绝不允许View直接访问Model,这就是它和MVC的不同之处。

1、MVP的简单应用

1.1 导入相关依赖包并设置权限

导入需要的相关依赖:

implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'org.projectlombok:lombok:1.18.30'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.bumptech.glide:glide:4.16.0'

接下来设置网络请求权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

我们使用这个免费的Api获取一首网易热门歌曲

https://api.uomg.com/api/rand.music?sort=热歌榜&format=json

JSON数据结构如下所示:

{
    "code": 1,
    "data": {
        "name": "晚安",
        "url": "http://music.163.com/song/media/outer/url?id=1359356908",
        "picurl": "http://p1.music.126.net/8N1fsMRm2L5HyZccc6I3ew==/109951164007377169.jpg",
        "artistsname": "颜人中"
    }
}

先看一下完整的项目目录结构:

[外链图片转存中…(img-pjbexowu-1702569842216)]

1.2 实现Model

创建一个musicData用于存储转化后的JSON数据:

@NoArgsConstructor
@Data
public class musicData {

    @SerializedName("data")
    private DataDTO data;

    @NoArgsConstructor
    @Data
    public static class DataDTO {
        @SerializedName("name")
        private String name;
        @SerializedName("url")
        private String url;
        @SerializedName("picurl")
        private String picurl;
        @SerializedName("artistsname")
        private String artistsname;

        public String getName() {
            return name;
        }

        public String getUrl() {
            return url;
        }

        public String getPicurl() {
            return picurl;
        }

        public String getArtistsname() {
            return artistsname;
        }
    }

    public DataDTO getData() {
        return data;
    }
}`

定义获取网络数据的接口类NetTask:

public interface NetTask<T> {

    void execute(T data, LoadTasksCallBack callBack);
}

这里有一个回调接口LoadTasksCallBack用于监听网络访问回调的各种状态:

public interface LoadTasksCallBack<T> {

    /**
     * 网络请求成功
     *
     * @param data:数据仓库,存放解析后的数据
     */
    void onSuccess(T data);

    /**
     *网络请求失败
     */
    void onFailed();
}

接下来我们编写NetTask的实现类以获取数据,如下所示:

public class MusicInfoTask implements NetTask<String> {
    private static MusicInfoTask INSTANCE = null;

    private static final String HOST = "https://api.uomg.com/api/rand.music?sort=%E7%83%AD%E6%AD%8C%E6%A6%9C&format=json";

    private LoadTasksCallBack loadTasksCallBack;

    public static MusicInfoTask getInstance(){
        if (INSTANCE == null){
            INSTANCE = new MusicInfoTask();
        }
        return INSTANCE;
    }

    @Override
    public void execute(String data, final LoadTasksCallBack callBack) {
        //这里展示一下传入的data数据,HOSP是写死的,没有拼接data,可以自行实现使用data重新拼接。
        Log.e("TAG", "execute: "+data);
        sendOkHttpRequest(HOST,new okhttp3.Callback(){
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                if (response.isSuccessful()){
                    String responseBody = response.body().string();
                    Gson gson = new Gson();
                    musicData musicData = gson.fromJson(responseBody, musicData.class);
                    callBack.onSuccess(musicData);
                }
            }

            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                callBack.onFailed();
            }
        });
    }


    /**
     * 发送网络请求
     *
     * @param address
     * @param callback
     */
    public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {

        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder().url(address).build();

        client.newCall(request).enqueue(callback);
    }
}

MusicInfoTask是一个单例模式,在execute方法中通过OKHttp来获取数据,同时在okhttp3.Callback中回调自定义的LoadTasksCallBack。

通过时序图理解execute方法是如何做的:

1.2 实现Presenter

我们首先定义一个契约接口MusicInfoContract用来存放具有相同业务的Presenter和View的接口,便于查找和维护。代码如下:

public interface MusicInfoContract {
    interface Presenter {
        void getMusiInfo(String ip);
    }

    /**
     * 视图需要的功能
     *
     * @author lukecc0
     * @date 2023/12/13
     */
    interface View extends BaseView<Presenter> {
        void setMusicData(musicData musicData);

        void showError();

        /**
         * 用于判断Fragment是否成功加入到Activity
         *
         * @return {@link Boolean}
         */
        Boolean isACtive();
    }
}

在这里看见Presenter接口定义了获取数据的方法,而View接口定义了与界面交互的方法。其中isActive方法用于判断Fragment是否加入到了Activity中。

另外,View接口基础于BaseView接口,BaseView接口如下所示:

public interface BaseView<T>{
    /**
     * 为视图绑定对应的presenter
     *
     * @param presenter
     */
    void setPresenter(T presenter);
}

BaseView接口的目的就是为View绑定对应的Presenter。它作为一个View的管理类,为每一个View实现绑定方法。

接着实现Presenter接口,如下所示:

public class MusicInfoPresenter implements MusicInfoContract.Presenter, LoadTasksCallBack<musicData> {

    private NetTask netTask;
    private MusicInfoContract.View addTaskView;


    /**
     * @param addTaskView 是我们传入的View用于实现Presenter绑定View,然后我们操作这个View实现Ui的变更
     * @param netTask   传入的Model用于实现Presenter绑定Model,通过这个Model获取数据
     */
    public MusicInfoPresenter(MusicInfoContract.View addTaskView, NetTask netTask) {
        this.netTask = netTask;
        this.addTaskView = addTaskView;
    }

    @Override
    public void onSuccess(musicData musicData) {
        if (addTaskView.isACtive()) {
            addTaskView.setMusicData(musicData);
        }
    }

    @Override
    public void onFailed() {
        if (addTaskView.isACtive()) {
            addTaskView.showError();
        }
    }

    @Override
    public void getMusiInfo(String ip) {
        //1、将自身回调,把自身传入到Model中,实现Model绑定Presenter
        netTask.execute(ip, this);
    }
}

在MusicInfoPresenter中含有NetTask和MusicInfoContract.View的实例,并且还实现了LoadTasksCallBack接口。

在注释1中MusicInfoPresenter将自身传递给NetTask的execute方法来获取数据。并回调给MusicInfoPresenter本身实现的onSuccess、onFailed两个方法,在这两个方法中通过addTaskView与View交互。我们看一下这个getMusiInfo方法的时序图理解它是怎么做的:

[外链图片转存中…(img-6HAbY7JS-1702569842216)]

这样我们就理解了,这里的Presenter作为一个中间代理,通过NetTask也就是Model来获取和保存数据,然后通过View更新页面。在这个过程中通过自定义接口使得Model和View没有任何交互。

1.3 实现View

在前面的契约接口MusicInfoContract中我们已经自定义了View接口,接下来我们使用MusicInfoFragment实现它:

public class MusicInfoFragment extends Fragment implements MusicInfoContract.View {
    private TextView textView;

    private ImageView imageView;

    private Button button;


    private MusicInfoContract.Presenter mPresenter;

    public static MusicInfoFragment newInstance() {
        return new MusicInfoFragment();
    }


    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View root = inflater.inflate(R.layout.music_fragment, container, false);

        textView = root.findViewById((int) R.id.textView);
        imageView = root.findViewById((int) R.id.image);
        button = root.findViewById((int) R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.getMusiInfo("热歌榜");
            }
        });

        return root;
    }

    @Override
    public void setPresenter(MusicInfoContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public void setMusicData(musicData musicData) {
        Log.e("TAG", "execute: " + musicData.getData().toString());
        if (musicData != null) {
            textView.setText(musicData.getData().getName());

            requireActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    String originalUrl = musicData.getData().getPicurl();
                    //将http协议换成https
                    String modifiedUrl = originalUrl.replace("http://", "https://");

                    Glide.with(requireActivity()).load(modifiedUrl).into(imageView);
                }
            });

        }
    }

    @Override
    public void showError() {
        textView.setText("Error");
    }

    @Override
    public Boolean isACtive() {
        //判断是否加入到Activity
        return isAdded();
    }
}

在上面注释1的部分通过实现setPresenter方法来实现注入MusicInfoPresenter,用于实现View绑定到Presenter。

在注释2中则调用MusicInfoPresenter的getMusiInfo方法来获取Ip地址的信息。另外Fragment实现了MusicInfoContract.View接口,用来接受MusicInfoPresenter的回调。

那么MusicInfoFragment是在哪里调用setPresenter实现注入MusicInfoPresenter?

其实在MainActivity中实现这个注入,我们看一下MainActivity做了什么事情:

public class MainActivity extends AppCompatActivity {

    private MusicInfoPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //1、创建Fragment
        MusicInfoFragment fragment = (MusicInfoFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);

        if (fragment == null){
            fragment = MusicInfoFragment.newInstance();
            //2、将Fragment加入到Activity中
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),fragment,R.id.fragment);
        }

        MusicInfoTask task = MusicInfoTask.getInstance();
        //3、将View和Model注入Presenter中
        presenter = new MusicInfoPresenter(fragment,task);
        //4、将Presenter注入View中实现双向绑定
        fragment.setPresenter(presenter);

    }
}

这个例子中Activity不作为View层,而是作为View、Model、Presenter三层的纽带。

📌注意我们看注释3、4可以发现View和Presenter是双向绑定的

ActivityUtils的addFragmentToActivity负责提交事务,实现了绑定Fragment到Activity,我们看一下代码:

public class ActivityUtils {
    public static void addFragmentToActivity(FragmentManager fragmentManager,Fragment fragment,int frameId){
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.add(frameId,fragment);
        transaction.commit();
    }
}

1.4分析项目结构和绑定过程

从图中可以看出,View和Model之间并没有什么联系;View和Presenter通过接口交互,并在Activity中互相注入。Model的NetTask在Activity中注入Presenter,并等待Presenter调用。

1.5效果展示

2、MVP结合RxJava

这个例子是在上个例子的基础上修改的,加入了RxJava3和Retrofit2实现网络请求。

首先导入需要的依赖:

implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

定义网络访问接口,如下所示:

public interface MusicService {
    @GET("rand.music?format=json")
    Observable<musicData> getMusicData(@Query("sort") String sort);
}

getMusicData返回Observable类型是为了支持RxJava,然后我们修改NetTask接口:

public interface NetTask<T> {

//    void execute(T data, LoadTasksCallBack callBack);

    Disposable execute(T data, LoadTasksCallBack callBack);
}

接下来修改NetTask接口的实现类MusicInfoTask,代码如下:

public class MusicInfoTask implements NetTask<String> {
    private static MusicInfoTask INSTANCE = null;

    private Retrofit retrofit;

    private static final String HOST = "https://api.uomg.com/api/";

    public MusicInfoTask() {
        createRetrofit();
    }

    public static MusicInfoTask getInstance(){
        if (INSTANCE == null){
            INSTANCE = new MusicInfoTask();
        }
        return INSTANCE;
    }

    /**
     *  初始化Retrofit
     */
    private void createRetrofit(){
        retrofit= new Retrofit.Builder()
                .baseUrl(HOST)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .build();
    }

    @Override
    public Disposable execute(String data, LoadTasksCallBack callBack) {
        MusicService service = retrofit.create(MusicService.class);
        Disposable disposable = service.getMusicData(data)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(new DisposableObserver<musicData>() {

                    @Override
                    public void onNext(@NonNull musicData musicData) {
                        // 收到数据时的操作
                        callBack.onSuccess(musicData);
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        // 发生错误时的操作
                        callBack.onFailed();
                    }

                    @Override
                    public void onComplete() {
                        // 完成时的操作
                    }
                });

        return disposable;
    }

}

主要变化就是使用RxJava和Retrofit代替OKHttp访问网络,在execute方法中返回Disposable。接下来我们实现取消网络请求功能。首先定义一个BasePresenter接口:

public interface BasePresenter {
    void subscribe();
    void unsubscribe();
}

然后改写Presenter接口,继承这个BasePresenter:

public interface MusicInfoContract {
//    interface Presenter {
//        void getMusiInfo(String ip);
//    }

    interface Presenter extends BasePresenter{
        void getMusiInfo(String ip);
    }
    ...........
}

最后改写MusicInfoPresenter类:

public class MusicInfoPresenter implements MusicInfoContract.Presenter, LoadTasksCallBack<musicData> {

    private NetTask netTask;
    private MusicInfoContract.View addTaskView;

    private CompositeDisposable compositeDisposable;

    private Disposable disposable;


    /**
     * @param addTaskView 是我们传入的View用于实现Presenter绑定View,然后我们操作这个View实现Ui的变更
     * @param netTask   传入的Model用于实现Presenter绑定Model,通过这个Model获取数据
     */
    public MusicInfoPresenter(MusicInfoContract.View addTaskView, NetTask netTask) {
        this.netTask = netTask;
        this.addTaskView = addTaskView;
        compositeDisposable = new CompositeDisposable();
    }

    @Override
    public void onSuccess(musicData musicData) {
        if (addTaskView.isACtive()) {
            addTaskView.setMusicData(musicData);
        }
    }

    @Override
    public void onFailed() {
        if (addTaskView.isACtive()) {
            addTaskView.showError();
            unsubscribe();
        }
    }

    @Override
    public void getMusiInfo(String ip) {
        //1、将自身回调,把自身传入到Model中,实现Model绑定Presenter
         disposable = netTask.execute(ip, this);
         subscribe();
    }

    @Override
    public void subscribe() {
        if (disposable != null) {
            compositeDisposable.add(disposable);
        }
    }

    @Override
    public void unsubscribe() {
        if (compositeDisposable != null && compositeDisposable.isDisposed()){
            compositeDisposable.dispose();
        }
    }
}

当注释1调用execute方法时返回一个disposable,然后调用subscribe方法传入compositeDisposable,用来管理这个网络请求,当我们需要取消这个网络请求时调用unsubscribe即可。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三层交换,DHCP的详解与VRRP

目录 一、三层交换 1、三层交换机的作用&#xff1a; 2.vlan的虚拟接口vlanif&#xff08;ifinterface接口&#xff09; 3.三层交换机实验 4.拓展实验​编辑 二、DHCP 1.自动获取ip地址&#xff1a; 2.DHCP的好处&#xff1a; 3.分配方式&#xff1a; 4.举例&#xff…

6.rk3588获取摄像头和激光雷达数据(用线程根据时间同步)

文件夹结构如下&#xff1a; 如果没有特殊说明&#xff0c;我们将py文件写在该路径里面。 保存数据的路径如下&#xff1a; ---img_lidar_save ---2023-12-13&#xff08;根据日期自动生成当天保存数据的文件夹) ---camera_data(相机数据文件夹&#xff09; ---image(保存相加…

C++1114新标准——模板模板参数(Template Template Parameter)、using

系列文章目录 C11&14新标准——Variadic templates&#xff08;数量不定的模板参数&#xff09; C11&14新标准——Uniform Initialization&#xff08;统一初始化&#xff09;、Initializer_list&#xff08;初始化列表&#xff09;、explicit C11&14新标准—— d…

SpringBoot 究竟是如何跑起来的

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《SpringBoot》。&#x1f3af;&#x1f3af; &…

怎么把文件转成附件放在公众号里?这篇教程给你详细说清楚

文件转附件&#xff0c;其实就是把文件上传到某个网站&#xff0c;获得文件的下载链接&#xff0c;从而放到文章或者其他地方供读者下载使用。因为公众号并不支持直接在文章里面添加下载链接&#xff08;至少订阅号不行&#xff09;&#xff0c;所以把文件转成下载链接的方式并…

怎么制作GIF动图?教你这几个简单方法

怎么制作gif动图&#xff1f;GIF动图是一种非常有趣且实用的图片格式&#xff0c;它能够以短小精悍的方式展示动画效果&#xff0c;因此在社交媒体和聊天应用中备受追捧。本文将向您介绍几种制作GIF动图的方法&#xff0c;让您轻松制作出自己的动图。 GIF动图制作方法一&#x…

Spark编程实验一:Spark和Hadoop的安装使用

一、目的与要求 1、掌握在Linux虚拟机中安装Hadoop和Spark的方法&#xff1b; 2、熟悉HDFS的基本使用方法&#xff1b; 3、掌握使用Spark访问本地文件和HDFS文件的方法。 二、实验内容 1、安装Hadoop和Spark 进入Linux系统&#xff0c;完成Hadoop伪分布式模式的安装。完成Ha…

【开源项目】智慧水厂—经典开源项目实景三维数字孪生智慧水厂

智慧水务可视化平台是以物联网IOT技术为核心&#xff0c;以数据库系统为支撑&#xff0c;以城市水资源安全提升和建造智能化为目标的智慧水务体系。飞渡科技利用数字孪生技术结合物联网IOT技术&#xff0c;建立起多个基础数据及管理层级矩阵&#xff0c;可以跨部门、跨层级进行…

【Java 集合】ConcurrentLinkedQueue

在日常中, 我们用到的数据结构有很多: 数组, 链表, 树等, 而在这些结构中, 还有一个叫做队列的存在。 和其他的集合相同, Java 原生提供了不同的实现。 而如果我们需要一个线程安全的队列的话, 可以基于实际的场景进行选择, 比如基于数组实现同时操作上会阻塞的 ArrayBlockingQ…

我们为什么经常使用List list = new ArrayList<>() 而不是ArrayListlist = new ArrayList<>()

为什么不直接去Arraylist list new Arraylist()&#xff1b;而是直接通过List list new ArrayList();使用接口的好处 在Java中&#xff0c;使用List接口声明ArrayList类的变量是一种良好的编程实践&#xff0c;因为这符合面向接口编程的原则。面向接口编程是一种编程范式&…

canvas基本绘制对象

目录 绘制画布 设置画布 绘制圆形 绘制矩形填充渐变色 绘制文字及文字样式 绘制画布 <canvas id"canvas" width"800" height"600"></canvas> 设置画布 //获得画布元素var canvasdocument.getElementById(canvas);var ctxca…

GitHub Universe 2023 Watch Party in Shanghai:开源开发者日盛会

目录 前言GitHub Universe 2023的背景开源开发者日活动亮点本次参会的意义活动日程最后 前言 作为全球最大的代码托管平台&#xff0c;GitHub每年都会举办一场令开源开发者们翘首以待的盛会——GitHub Universe&#xff0c;今年也不例外&#xff0c;就在2023年的12月10日&…

算法训练营Day14

#Java #二叉树层次遍历 #反转二叉树 开源学习资料 二叉树的层次遍历&#xff1a;力扣题目链接 二叉树的层次遍历很好理解&#xff1a; 就是从根结点一层一层地往下遍历&#xff08;同一层&#xff0c;从左到右&#xff09;&#xff1a; 迭代的方式很好理解&#xff1a;就是…

computed 和 watch 的奇妙世界:让数据驱动你的 Vue 应用(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

docker部署go gin框架 Windows环境

目录 文章目的是什么 环境介绍 Windows 环境下 docker 部署 go gin 详细步骤 运行容器时因为挂载文件可能会出现的问题 直接部署gin&#xff08;跳过运行容器时因为挂载文件可能会出现的问题&#xff09; 文章目的是什么 假设我们学习了 go 语言&#xff0c;在 Windows(本…

精选硬件连通性测试工具:企业如何做出明智选择

在当今数字化的商业环境中&#xff0c;企业的硬件连通性至关重要。选择适用的硬件连通性测试工具是确保网络和设备协同工作的关键一步。本文将探讨企业在选择硬件连通性测试工具时应考虑的关键因素&#xff0c;以帮助其做出明智的决策。 1. 功能全面性&#xff1a;首要考虑因素…

PHP微信朋友圈广告植入源码 +提供高效的广告植入解决方案,助力微信朋友圈广告推广

源码介绍 可以无限制帮用户开户&#xff0c;也可以理解为多用户版。 可以管理用户发布文章条数&#xff0c;也可以无限制发布。 用户可以上传多个广告&#xff0c;每个广告分别进行统计展示及点击。 用户一键植入&#xff0c;不用粘贴网址&#xff0c;每篇文章会 分别统计展示…

Peter算法小课堂—简单建模(3)

国王的奖赏系列 国王的奖赏1 题目描述&#xff1a; 你作为战斗英雄得到国王的奖赏&#xff0c;可以在地图上选一块土地。地图里共n*m格土地&#xff0c;第x行第y列的土地格子里标记着d[x][y]的整数价值&#xff0c;可能出现负数。国王让你选择若干列土地&#xff0c;只要是连…

车载以太网笔记

文章目录 以太网协议分层协议中间设备子网掩码物理层测试内容比较杂,后续会整理。 以太网协议分层 协议 中间设备

mfc配置halcon环境

新建mfc窗体 选择基于对话框 打开项目属性 1、附加包含目录添加&#xff1a; $(HALCONROOT)\include;$(HALCONROOT)\include\halconcpp 2、链接器->常规->附加库目录 $(HALCONROOT)\lib\x64-win64 3、链接器->输入->附加依赖项 halcon.lib;halconcpp.lib 在对话…