MVP模式开发之项目框架的搭建

MVP模式的ListView实践项目:mvpListViewDemo 最小化可行产品(Minimum Viable Product, MVP)模式是一种流行的软件开发模式,特别适用于敏捷开发和快速迭代的项目。MVP旨在将项目的展示层(View)、业务逻辑层(Presenter)和数据层(Model)进行明确的分离,以提升代码的可测试性、可维护性及可扩展性。在本章中,我们将探索MVP模式的起源、核心概念和它在现代软件开发中的重要性。 阅读详情

这段时间的感慨

其实在之前那一篇MVP模式中大概的对MVP模式做了一个阐述,但是实际运用中要考虑到蛮多的细节性问题 ,而且感觉这次MVP开发的十分顺畅,虽然用户端这边是我一个人开发,数了下大概100多个类,1w行代码的样子。怎么说呢,这次中软杯,由于我不是弄算法这块的,对于Android端我把我现在能做的最好水平做出来了。
虽然做到最后做的想吐了(一个人在各种model、view、presenter层进行切换接口来回调),但终于还是在昨晚12点撸完了第一期 ,其实身边一些新的技术新的事情一直都有在关注,比如最近google开发中大会上宣布kotlin成为官方的第一语言,所以也想在接下来抽出一段时间学习kotlin,可是最近确实忙的不成人形,虽然有时候听起来像借口,但是我可能目前是处于这种状态,就是做什么事情都喜欢一段一段时间的做,而且这一段时间会100%投入,比如学习,那么今天从起床就能学到睡觉,如果每天能够学习的时间不到两个小时,我可能就回去做别的事情了,其实这就是我的缺点吧,要是能够更加合理的规划时间就好了。最近又有个创业性的项目拉我进来负责Android用户端,因为投资人已经确认等到成品就可以投资了,可是当大牛找到我跟我阐述了项目大概要做的内容时,我的第一反应确实觉得如果接下来一段时间做这种类似类型的app不能很系统的学习而有点排斥,可是转念一想,这也算是个机会吧,人一辈子这么长时间,也不差这几个月,所以就答应大牛一起干了。
我觉得接下来的时间我应该要改变了,不管是对自己时间的分配还是别的什么,都要更加合理才行,不能因为自己觉得没时间就不学习了吧,因为只有做项目的时候为了有的东西焦头烂额的时候才知道对原理性的内容掌握不够,其实就算每天抽出半个小时看一些常用内容的源码,也是会有所收获的吧。好了,废话不多说,进入正题吧。

MVP项目框架搭建原则

  1. 对视图层、业务层和数据层进行解耦
  2. 从架构层避免内存泄漏、OOM等状况的发生
  3. 对子类层通用的细节抽象,非通用性细节先实现通过继承改写
  4. 对通用的组件进行引入,并保证其生命周期不会超过activity

MVP框架在项目中的应用

其实之前在我应用MVP的时候,我就一直苦苦的思考,什么样的写法才是正宗的mvp写法呢?思考很久也没有个答案,后来看到设计模式里的一段话,我想通了,其实包括对设计模式的应用,没有标准答案,书上罗列出的,只是一种简单的通用情况,实际的应用要根据具体情况来改变,如果严格遵循设计模式的原则,接口单一,可是实际情况下存在么,这是理想情况下最好的,可是实际下也要考虑开发情况开发成本等。包括这里的MVP,我觉得没有真正的答案,他可能罗列出一个通用解耦的情况,你可以对解耦进行进一步解耦,也可以修改,甚至有时候可以舍去某些层次,都是可以的。

mvp项目的时序图:

这里我以登录为例子,时序图如下:这里写图片描述
这里可以很明显的看到方法在不同层次间的调用和回调,其实所有的网络业务,基本上就是类似这张图的层次,从view层发起操作,调用presenter层的中间者执行操作,在调用presenter层发起网络请求拿到结果数据(这里是服务器处理完成后的结果),根据数据结果调用监听回调view层操作来觉得view层该怎么做,全程都是一层一层解开耦合,各类分工合理各司其职,这就是mvp实际使用情况的一个时序图了。

MVP项目框架的搭建

这个项目框架其实就是最关键的,剩下的内容我会等比赛完毕上传到GitHub上,到时候会附上链接。首先我们看看MVP各层的接口是怎么写的

登录的View层接口

package com.we.piccategory.view;

import android.app.Dialog;

/**
 * Created with Android Studio
 * User: 潘浩
 * School 南华大学
 * Date: 2017/5/1
 * Time: 0:39
 * Description:
 */
public interface ILoginView {

    void skip(Class clazz);

    void showMsgDialog(String msg);

    Dialog showLoading();

}

通用的presenter抽象类

package com.we.piccategory.mvp;

import com.we.piccategory.model.IModelImpl;

import java.lang.ref.WeakReference;


public abstract class BasePresenter<T> {

    protected WeakReference<T> viewRef;
    protected IModel model = new IModelImpl();


    public void attachView(T view) {
        viewRef = new WeakReference<T>(view);
    }

    public void detachView() {
        if (viewRef != null) {
            viewRef.clear();
            viewRef = null;
        }
    }

    protected T getView() {
        return viewRef.get();
    }


}

这里的泛型解释一下,传入的是View层的接口,比如登录操作传递的就是ILoginView。这边采用弱引用因为presenter层和view相互持有对方的引用,同时presenter层持有model层引用,我们的网络耗时操作实际上就是放在model中执行的,所以这里为了避免mvp层架构产生内存泄漏的问题从而采用了弱引用。

通用的Model层接口

package com.we.piccategory.mvp;

/**
 * Created by 86119 on 2017/3/21.
 */

public interface IModel {


    /**
     * 获取数据
     *
     * @param listener 监听回调
     */
    void load(OnCompletedListener listener);

    interface OnCompletedListener<T> {
        /**
         * 获取到数据并且数据要交互到界面成功时
         *
         * @param data
         */
        void onCompleted(T data);

        /**
         * 仅仅提交请求不需要数据时成功
         */
        void onCompleted();

        /**
         * 提交请求结果失败时
         */
        void onFail(String msg);
    }
}

其实写到后来发现,这个不带参数的onSuccess方法没必要的赶脚,这里先写着就这样子都作为回调的接口方法。这里的泛型,是作为通用数据类型的一个回传,也就是如果有指定类型的数据要回传展示等,就能从OnCompletedListener接口中根据指定类型回传到界面层,从而保证数据在传递过程中的一致性。这么咋一看,好像还没关联的样子,别急,往下看

通用的Activity基类

package com.we.piccategory.ui.base;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.we.piccategory.builder.DialogBuilder;
import com.we.piccategory.mvp.BasePresenter;

import butterknife.ButterKnife;



public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {

    protected T myPresenter;

    public DialogBuilder builder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        //调用子类的方法获取persenter
        myPresenter = createPresenter();
        myPresenter.attachView((V) this);

        setContentView(initRes());
        ButterKnife.inject(this);

        builder = new DialogBuilder();
        builder.attachView(this);


        initData();
    }


    protected abstract int initRes();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        myPresenter.detachView();
        builder.detachView();
    }

    /**
     * 创建presenter对象
     *
     * @return 子类中的实例presenter对象
     */
    public abstract T createPresenter();

    protected void initData() {
    }
}

可以看到,BaseActivity是一个抽象的基类,把具体实现不同的任务交给子类实现,比如这里的initRes用来回传layout布局用来setContentView,这里的createPresenter用来创建指定子类的具体presenter类型。这里有两个泛型,其实很容易看出来,泛型V指的是实现View层接口的Activity,泛型T指的是抽象presenter的子类,其实这里我就采取了一个比较巧妙的办法,虽然按照原则是面向接口编程,但是presenter的父类是一个抽象类而不是一个接口,而且同一个presenter里的业务各不相同,参数各不相同,也没法定义通用的接口方法,那么就干脆不要接口就好了,因为在BaseActivity层表明需要的是presenter的子类,那么子类具体的方法我们这边是可以调用到的,那么也就没必要纠结接口不接口的问题了。这里面这onCreate中调用presenter的attachView方法利用弱引用获取到Activity的引用,同时在onDestroy中调用detachView将Activity的引用clear掉,从而避免了Activity内存泄漏的发生。包括Dialog,因为基本每个Activity都会使用到包括加载提示dialog等,所以这边也是以这种方式接入进来。那么我们再来看看具体的loginActivity怎么写,不过可能这里举的例子不是很好,因为刚好我这个Activity由于业务需要操作有些复杂,就是类似拉钩网那种,直接按钮切换布局而不是跳转Activity的,这边我们主要关注登录操作的流程

package com.we.piccategory.ui.activity;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.we.piccategory.MainActivity;
import com.we.piccategory.R;
import com.we.piccategory.builder.DialogBuilder;
import com.we.piccategory.presenter.LoginPresenter;
import com.we.piccategory.ui.base.BaseActivity;
import com.we.piccategory.util.CommonUtil;
import com.we.piccategory.util.LoginManger;
import com.we.piccategory.view.ILoginView;

import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;

/**
 * Created by 86119 on 2017/4/9.
 */

public class LoginActivity extends BaseActivity<ILoginView, LoginPresenter> implements View.OnClickListener, ILoginView {

    //inject的是login界面的控件
    @InjectView(R.id.user_name)
    public EditText etUserName;//用户名

    @InjectView(R.id.password)
    public EditText etPassword;//密码

    @InjectView(R.id.btn_login)
    public Button loginBtn;//登录

    @InjectView(R.id.btn_to_regist)
    public Button toRegisterBtn;//跳转到注册的按钮

    @InjectView(R.id.tv_forget)
    public TextView forget;//忘记密码

    //以下是register界面的控件
    private EditText etPhone;
    private EditText etCheck;

    private Button register;

    private Button toLoginBtn;

    private TextView tvIdentify;

    @Override
    protected int initRes() {
        return R.layout.activity_login;
    }

    @Override
    protected void initData() {
        boolean login_state = LoginManger.getLoginState();
        if (login_state == true) {
            skip(MainActivity.class);
        }
    }

    @Override
    public LoginPresenter createPresenter() {
        return new LoginPresenter();
    }

    @OnClick({R.id.btn_login, R.id.btn_to_regist, R.id.tv_forget})
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:
                String userName = etUserName.getText().toString();
                String password = etPassword.getText().toString();
                myPresenter.login(userName, password);
                break;
            case R.id.btn_to_regist:
                initToRegister();
                break;
            case R.id.tv_forget:
                Intent intent = new Intent();
                intent.setClass(LoginActivity.this, ForgetActivity.class);
                startActivity(intent);
                break;
            case R.id.btn_to_login:
                Log.i("ph", "aaa");
                setContentView(R.layout.activity_login);
                ButterKnife.inject(this);
                break;
            case R.id.btn_register:
                String num = etPhone.getText().toString().trim();
                String value = etCheck.getText().toString().trim();
                myPresenter.checkValues(num, value);
//                skip(RegisterInfoActivity.class);
                break;
            case R.id.tv_identify://验证码
                String telNum = etPhone.getText().toString();
                if (CommonUtil.checkNum(telNum, 11)) {
                    myPresenter.checkPhone(telNum);
                }
                break;

            default:
                break;
        }
    }

    private void initToRegister() {
        setContentView(R.layout.activity_register);
        etPhone = (EditText) findViewById(R.id.phone_num);
        etCheck = (EditText) findViewById(R.id.check_value);
        register = (Button) findViewById(R.id.btn_register);
        toLoginBtn = (Button) findViewById(R.id.btn_to_login);
        tvIdentify = (TextView) findViewById(R.id.tv_identify);
        register.setOnClickListener(this);
        toLoginBtn.setOnClickListener(this);
        tvIdentify.setOnClickListener(this);
    }

    @Override
    public void skip(Class clazz) {
        Intent intent = new Intent();
        intent.setClass(LoginActivity.this, clazz);
        if (etPhone != null) {
            intent.putExtra("telNum", etPhone.getText().toString());
        }
        startActivity(intent);
        this.finish();
    }

    @Override
    public void showMsgDialog(final String msg) {
        AlertDialog dialog = builder.createLoadingDialog(msg, DialogBuilder.COMMON_MODEL);
        dialog.show();
    }

    @Override
    public Dialog showLoading() {
        return builder.createLoadingDialog("", DialogBuilder.LOADING_MODEL);
    }

}

然后是具体对应的LoginPresenter

package com.we.piccategory.presenter;

import android.app.Dialog;
import android.util.Log;

import com.we.piccategory.MainActivity;
import com.we.piccategory.bean.Token;
import com.we.piccategory.decorator.CheckModelDecorator;
import com.we.piccategory.decorator.CheckValuesDecorator;
import com.we.piccategory.decorator.LoginModelDecorator;
import com.we.piccategory.decorator.TelChangeDecorator;
import com.we.piccategory.mvp.BasePresenter;
import com.we.piccategory.mvp.IModel;
import com.we.piccategory.ui.activity.RegisterInfoActivity;
import com.we.piccategory.util.LoginManger;
import com.we.piccategory.view.ILoginView;


public class LoginPresenter extends BasePresenter<ILoginView> {

    /**
     * 登录操作
     *
     * @param userName 用户名
     * @param password 密码
     */
    public void login(final String userName, String password) {
        //获取装饰类
        model = new LoginModelDecorator(model, userName, password);
        final ILoginView iLoginView = viewRef.get();

        final Dialog dialog = iLoginView.showLoading();
        dialog.show();

        model.load(new IModel.OnCompletedListener<Token>() {
            @Override
            public void onCompleted(Token token) {
                dialog.dismiss();
                //登录成功以后将token值写入本地
                String tokenValue = token.getToken();
                Integer userId = token.getUserId();
                LoginManger.setLoginState(tokenValue, userId, true);

                //跳转界面
                iLoginView.skip(MainActivity.class);

            }

            @Override
            public void onCompleted() {

            }

            @Override
            public void onFail(String msg) {
                dialog.dismiss();
                iLoginView.showMsgDialog(msg);
            }
        });

    }

    /**
     * 校验手机号是否正确
     * 请求
     *
     * @param telNum 手机号
     */
    public void checkPhone(String telNum) {
        model = new CheckModelDecorator(model, telNum);
        final ILoginView iLoginView = viewRef.get();

        model.load(new IModel.OnCompletedListener() {

            @Override
            public void onCompleted(Object data) {
            }

            @Override
            public void onCompleted() {
                Log.i("ph", "onCompleted: 获取验证码成功");
            }

            @Override
            public void onFail(String msg) {
                iLoginView.showMsgDialog(msg);
            }
        });
    }


    /**
     * 校验手机号和验证码是否正确
     * 这里的请求是发送给mob第三方
     *
     * @param telNum
     * @param identifyNum
     */
    public void checkValues(String telNum, String identifyNum) {
        //发送获取验证码的指令
        model = new CheckValuesDecorator(model, telNum, identifyNum);
        final ILoginView iLoginView = viewRef.get();
        model.load(new IModel.OnCompletedListener<String>() {

            @Override
            public void onCompleted(String data) {
                iLoginView.skip(RegisterInfoActivity.class);
            }

            @Override
            public void onCompleted() {

            }

            @Override
            public void onFail(String msg) {
                iLoginView.showMsgDialog(msg);
            }
        });

    }


    public void changeNum(final String telNum, String identifyNum) {
        //发送获取验证码的指令
        model = new CheckValuesDecorator(model, telNum, identifyNum);
        final ILoginView iLoginView = viewRef.get();
        model.load(new IModel.OnCompletedListener<String>() {

            @Override
            public void onCompleted(String data) {
                //当第三方校验通过时
                changeTel(telNum, iLoginView);

            }

            @Override
            public void onCompleted() {

            }

            @Override
            public void onFail(String msg) {
                iLoginView.showMsgDialog(msg);
            }
        });
    }


    public void changeTel(final String telNum, final ILoginView iLoginView) {
        model = new TelChangeDecorator(model, telNum);
        model.load(new IModel.OnCompletedListener<String>() {
            @Override
            public void onCompleted(String text) {
                LoginManger.setTelNum(telNum);
                if (iLoginView != null) {
                    iLoginView.showMsgDialog(text);
                }
            }

            @Override
            public void onCompleted() {

            }

            @Override
            public void onFail(String msg) {

                if (iLoginView != null) {
                    iLoginView.showMsgDialog(msg);
                }
            }
        });
    }


}

同样,因为界面需要一个presenter的不可能只做一件单一的事情,所以包含的别的业务操作,但是你可以看到每个方法除了参数基本的操作类似,也就是拿到结果回传或者做数据的回显。这里也是主要关注login这个方法。再看model层是如何表现的

package com.we.piccategory.decorator;

import com.loopj.android.http.RequestParams;
import com.we.piccategory.bean.RgbResult;
import com.we.piccategory.bean.Token;
import com.we.piccategory.mvp.IModel;
import com.we.piccategory.net.HttpUtil;
import com.we.piccategory.net.SuccessRespHandler;
import com.we.piccategory.ui.base.BaseApp;
import com.we.piccategory.util.Constant;
import com.we.piccategory.util.MD5Util;
import com.we.piccategory.util.RSAUtils;

import java.io.InputStream;


public class LoginModelDecorator extends ModelDecorator {
    private String userName;
    private String password;

    public LoginModelDecorator(IModel model, String userName, String password) {
        super(model);
        this.userName = userName;
        this.password = password;
    }

    @Override
    public void load(OnCompletedListener listener) {
        this.login(listener);
    }

    private void login(final OnCompletedListener listener) {
        //校验数据
        try {
            String dest = "/user/login";
            RequestParams params = new RequestParams();
            params.put("userName", userName);
           ...//加密过程这里注释掉
                       params.put("password", encrypt);
                       params.put("md5", md5);

            HttpUtil.doPost(dest, params, new SuccessRespHandler(listener) {

                @Override
                protected RgbResult getResult(String resp) {
                    return RgbResult.formatToPojo(resp, Token.class);
                }

                @Override
                protected void onStatusFail(RgbResult rgbResult) {
                    mListener.onFail("用户名或密码不正确");
                }

                @Override
                protected void onStatusOk(RgbResult rgbResult) {
                    Token token = (Token) rgbResult.getData();
                    mListener.onCompleted(token);
                }

            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

这里用了个装饰者模式,其实吧,是脑子抽抽了,本来想的是presenter层没接口就用装饰者增加presenter的功能,可是最后想明白这边反而脑子抽抽用了装饰者,其实在这边作用并不大,好吧,这不是重点,看这个类实际的内容,就是访问网络请求结果,成功了会返回一个Token类对象,我这边拿到Token对象所以Presenter的匿名内部OnCompleted类传的也是Token,这边就刚好做一个数据的回传,如果view层需要,接口就可以给定数据类型来拿到数据展示或其它功能等。

结语
好啦,以上就是MVP模式可以在实际项目中应用的情况,这里以一个简单的mvp模式进行登录给大家讲解mvp的具体使用情况,当然,这只是我在我的项目中的使用情况,具体可以结合RxJava等一起使用,也可根据自己的实际项目情况灵活的加以运用,总之呢,知识是死的,人是活的,要学会活学活用,才能够让自己适应各种情况,挺晚的,该睡了,晚安了各位。

DDPG深度强化学习移动机器人导航:从环境搭建到训练避坑指南 深度强化学习在机器人连续控制领域正成为解决端到端导航问题的关键技术。它通过马尔可夫决策过程建模,让智能体在未知环境中学习从高维传感器观测到低层运动指令的映射。其中,确定性策略梯度方法因其方差低、适合高频控制,被广泛应用于移动机器人的速度与角速度决策。然而,训练效果往往受限于仿真环境、奖励函数设计与超参设置。借助Gazebo物理仿真与TensorFlow框架,可高效构建可重复的碰撞与随机化场景,为策略训练提供稳定数据流。这类技术在自主避障、路径规划、无人车等场景中具有重要工程价值,也是机器人毕业设计与科研实验 阅读详情

相关推荐

构建高效Android应用的快速开发框架指南

本文还有配套的精品资源,点击获取 简介:快速开发框架在Android应用开发中起到了提高效率、减少重复工作的作用。它通过封装常用功能模块和设计模式,简化了代码编写过程。本指南旨在介绍如何利用这些工具来集中精力于业务逻辑而非基础架构搭建,包括对框架源码的理解、核心组件功能分析和最佳实践的掌握。此外,指南还包括组件化、架构模式、依赖注入、自动化构建、网络请求、数据持久化、UI组...

weixin_42376614的博客 1846

MVP

MVPUML类图时序图元素MVP框架MVP请求数据-渲染的过程 UML 类图 泛化关系(空心箭头实线):继承非抽象类(SUV是小汽车) 实现关系(空心箭头虚线):继承抽象类(小汽车是车) 聚合关系(空心菱形箭头):B由A组成,但是如果B没了,A依旧存在(公司破产了,员工依旧存在) 组合关系(实心菱形箭头):B由A组成,如果B没了,A也就没了(公司破产了,各个部门也就不存在了) 关联关系(直线箭头)...

qq_40384370的博客 343

Mvp项目架构

利用Retrofit,RxJava,Okhttp搭建的一个Mvp模式的项目,可以在此基础上进行快速开发

项目管理之mvp

定义 MVP(minimum viable product,最小化可行产品) 两种MVP——Validating MVP 和 Invalidating MVPMVP的模型分为两种,Validating MVP 和 Invalidating MVP。 第一种是Validating MVP(可行的MVP,无对应中文,斗胆翻译),也是是较为常用的MVP:接近你的目标客户,把现有未完成的产品低价出售。如果他们乐意接受,则产品验证成功;反之,失败,但这并不影响项目的进程。 举个例子:小明做了一款社交付费APP,

u011993094的博客 2202

RS485设备联网避坑指南:塔石串口服务器在MODBUS平台中的3个关键配置项

本文针对塔石串口服务器在MODBUS物联网平台接入中的常见问题,详细解析了三个关键配置项:工作模式选择、远程端口与保活机制、寄存器映射与轮询策略。通过对比错误与正确配置案例,帮助工程师避开配置陷阱,实现RS-485设备的稳定、高效联网,提升批量部署的成功率与系统可靠性。

weixin_29281941的博客 393

清理异常值(MAD:绝对中位差)

清理异常数据: 中位数绝对偏差(MAD):用来描述单变量样本在定量数据中可变的一种标准 在统计学中,绝对中位差是刻画一元数据样本变化的一个鲁棒度量。由公式可以看出,其求解还算简单,给定一个数据样本集,首先求其中位数,然后求原始数据减去中位数的绝对值从而形成一个新的数据样本,再求新的数据样本的中位数即为绝对中位差。比如说有一个数据样本集X={2 3 8 7 9 6 4},这时候数据的中位数是6,原...

spider_man 1万+

MVPArch - Android MVP 快速开发框架

MVPArch 一个可有效提高Android开发效率的MVP框架 封装Activity/Fragment基类-BaseActivity/BaseFragment(Fragment懒加载开关配置) 封装MVP模式Activity/Fragment基类-BaseMVPActivity/BaseMVPFragment,V与P层生命周期监听和绑定,解决诸多内存泄漏问题 使用 LoadingHelper实现可定制化的页面LCE视图 LoadingDialog加载框定制化,可随意切换 使用TitleBar 实现可全

黄小梁的博客 1724

手把手教你用RMII接口配置以太网PHY芯片(附YT8512实战代码)

本文详细介绍了如何使用RMII接口配置以太网PHY芯片,以国产YT8512为例,提供从硬件设计到软件驱动的全流程实战指南。内容包括RMII接口技术解析、YT8512硬件设计要点、驱动开发实战代码及常见问题排查,帮助开发者快速实现以太网通信功能。

mac99的博客 988

关于MVP的项目经验心得以及对Jetpack Compose的思考

Google目前在Android端主推的Jetpack Compose声明式UI框架正是基于单向数据流的架构模型的一种具体实现,在这种架构中,只有两种角色UI和数据,但影响UI更新的只能是状态,普通的数据是不能直接影响UI的,必须变成mutableState(可被观察的可变状态)才行。

川峰的博客 1023

Qwen3-Omni-30B-A3B-Instruct微调指南:如何打造专属智能博物官(附数据集)

本文详细介绍了如何对Qwen3-Omni-30B-A3B-Instruct模型进行微调,以打造专业的智能博物官。指南涵盖了从理解模型架构优势、构建高质量文博数据集,到使用LLaMA-Factory进行实战微调、效果评估与部署优化的完整流程,并提供了关键的环境配置与性能优化建议,帮助开发者在垂直领域高效定制专属AI助手。

weixin_29096377的博客 1138

RISC-V详细介绍

文章目录写在前面什么是RISC-VRISC-V诞生的背景ISA霸权摩尔定律的穷途末路穷困潦倒的学者不断增长的指令数量RISC-V架构设计思想如何设计一个好的ISARISC-V之RV32I四个典型特点RISC-V之乘除法指令指令格式用乘法代替常数除法RISC-V之RV64RISC-V的特权架构机器模式监管者模式 写在前面 本文主要基于RISC-V中文手册,其中包括但不限于一些网络的资料。本文版权属于skywf,转载或借用请联系作者。 什么是RISC-V RISC-V(发音为“risk-five”)是一个基于

skywf的博客 3万+

Android之Mvp模式框架搭建

    前言 在做Android开发时,我们通常为了更合理,更高效,优质的开发项目,并不是直接上来就开始开发功能,而是先搭建好自己的一个开发框架,一个好的开发框架,通常会让我们事倍功半,它就好比我们去建造一个房子,如果你的地基打的越深,越牢固,那么就可以建造出很高很大的房子. 在搭建一个框架前,我们先要考虑一下,这个框架要达到一个什么样的效果,主要从下面三点来进行考虑: 降低项目的...

gxflh的博客 519

SKF SimPro Quick 4.8.1发布

发布说明可以在SimPro手册的SKF SimPro Quick应用程序的一般部分下找到(帮助→SKF SimPro快速手册→一般→发布说明)。请删除旧版本,请备份您存储在C:\SKF SimPro xxx\files文件夹中的文件以及所有自定义报告模板和表格。我们很高兴地通知您,我们的轴承应用工程软件SKF SimPro Quick 4.8.1可供下载。将新的安装文件下载到本地计算机后,双击它安装向导将打开(按照说明操作)3.安装后,在本地机器上启动SKF SimPro Expert软件。

小韩的博客 1811

Github标星5-8K今年最热门技术-Android-MVP-阿里大牛-倾情-详解(上)

2014年年底偶然得知在Android开发中出现了MVP这种模式,当时觉得这东西挺好,正好赶上公司要做一个新的小项目,于是尝试了一下。仿照网上的Demo分出View、Model、Presenter层,抽取View接口,看起来像那么回事的用MVP完成了整个项目。因为项目简单,期间也没有遇到什么坑,但是总觉得还有那些地方不对。

2401_84897081的博客 1039

【配环境】ubuntu18.04 3080ti显卡+cuda+cudnn+torch

1.打开系统设置中的software & updates点击 System Settings,选择 Additional Drivers 标签,下面会列出当前显卡可用的驱动版本。 注意:3080ti建议不要安装最新版本驱动 点击Apply Changes即可安装; 注意:3080ti只能装cuda11.x 修改软连接修改系统使用的cuda 卸载老版本cuda 重新查询看是否卸载成功 显卡驱动及CUDA Toolkit最高对应版本 查询驱动版本............

sinat_41670685的博客 3910

S7-1200PLC与昆仑通态触摸屏以太网通信的具体方法和步骤(图文)

S7-1200PLC与昆仑通态触摸屏以太网通信的具体方法和步骤(图文)

Robot_PLC_HMI_Automation 1万+

【Android架构】基于MVP模式的Retrofit2+RXjava封装

最近有个新项目要做,搭建框架的时候,顺便梳理了下MVP模式,特此记录,欢迎大家指正。 项目地址:https://github.com/18702953620/MVPDemo 一 、首先是依赖 compile 'com.google.code.gson:gson:2.8.0' compile 'com.squareup.okhttp3:okhttp:3....

weixin_34025051的博客 119

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

MVPArms MVP快速集成框架

前言 今年的Android技术圈中MVP,Dagger2,Rxjava,Retrofit这些词汇非常火,随便打开一个技术论坛都充斥着大量的关于这些技术的文章,Github也充斥着各种以基于MVP+Retrofit+RxJava+Dagger2+MaterialDesign开发的xxxx为标题的开源项目或Demo. 但是大家这么热心的开源此类项目,一直重复的做着同样的事...

dazhen7289的博客 538

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

从零开始搭建一个主流项目框架(二)—MVP+Dagger2

个人博客:haichenyi.com。感谢关注   接着上一篇简单的框架,没有看过的同鞋可以去喵一眼。上一篇我们搭好了简单的框架,初始化一次的内容丢在Application里面,所有的activity继承一个类BaseActivity,还有Fragment继承的一个类BaseFragment   现在我们来加上MVP,不懂MVP的同鞋可以看一下,我前面写过的三种主流框架的对比。我们先导入dag...

海晨忆的博客 4963

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

从零开始搭建一个主流项目框架(一)—简单的框架

个人博客:haichenyi.com。感谢关注 目的   首先先说出,最终的目的是现在主流的MVP+RxJava+Retrofit+OkHttp框架。让大家心里有底   开发工具Android Studio3.0,还在用eclipse的同鞋,强烈推荐你跨出这一步,你会发现一个新的世界。android studio都出来这么久了,你还在远古时代做开发,说句不好听的,你完全与时代脱轨,你不适...

海晨忆的博客 5892

我的Android重构之旅:框架篇

在我这几年的学习和成长中,慢慢的意识到搭建一个优秀的 Android 开发框架是一件非常困难以及痛苦的事情,它不仅需要满足不断增长的业务需求,还要保证框架自身的整洁与扩展性,这让事情变得非常有挑战,但我们必须这样做,因为健壮的 Android 开发框架是一款优秀APP的基础。 这是“我的Android重构之旅”的第二篇文章,在前面的文章中,我们介绍了常用的几种架构模式,本篇中将和大家分享下我们...

weixin_34409703的博客 207

MVPArms框架深度解析:构建高效Android MVP架构的利器

在Android开发领域,架构设计一直是开发者关注的焦点。随着项目规模扩大和业务复杂度提升,如何构建一个清晰、可维护、可扩展的应用架构变得尤为重要。MVPArms框架正是为解决这些问题而生的一个优秀解决方案。 ## 什么是MVPArms框架 MVPArms是一个高度可配置化的Android MVP快速集成框架,它整合了大量主流开源项目,为开发者提供了一套完整的MVP架构解决方案。该框架基于Go...

gitblog_00150的博客 512

Android MVP框架搭建与使用(含源码)

**Toast消息提示 字符@param llw*//**Toast消息提示 资源ID*//***//***/= null) {/***/if(!/**返回 toolbar控件点击*/@Overrideif (!});/**两次点击间隔不能少于500ms*/一般来说这样就可以了,但是以我的开发习惯来说我还会加上这个页面加载的弹窗和页面销毁的控制,那么下午我给加上。

2401_85112749的博客 389

Android Mvp快速搭建框架MVP+Dagger2+Retrofit+Rxjava

一个整合了大量主流开源项目的Android Mvp快速搭建框架 http://www.jianshu.com/u/1d0c0bc634db http://blog.csdn.net/JessYanCoding/article/details/54311860

qq_31860607的博客 568
上一篇: 干货!Android的保活的两种解决方案
下一篇: 从源码层分析和学习事件传递机制
pan861190079
博客等级 码龄11年 48粉丝 46原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值