Android实战 | 详解MVC、MVP模式并分别实现登录界面案例

简介: Android实战 | 详解MVC、MVP模式并分别实现登录界面案例

参考资料(《(菜鸟窝)安卓进阶必学》)

本文参考技术资料做一个笔记,主要内容是总结MVC、MVP两个设计模式的思想,以及分别运用这两个模式的实现,实现两个project(MVCSmallTest还有MVPTest),内容都是登录界面。

文章主要内容摘要:

  • MVC模式的分析和实战
  • MVP模式的分析和实战
  • MVP模式下多个Activity情况下的接口抽取

实战案例效果如下:输入正确的密码并点击登录按钮时,Toast“登录成功”,若密码或账号错误,则Toast“登录失败”,若全部输入,则Toast“用户名和密码不能为空”:

MVC模式

View层其实就是程序的UI界面,用于向用户展示数据以及接收用户的输入(比如EditText.getText().toString())
Model层就是JavaBean实体类,用于保存实例数据
Controller控制器用于更新UI界面和数据实例

特点:
  • 使用多
  • 软件开发最早使用的设计模式
  • xml做View层,Activity做C层
弊端:Activity既是V又是C,UI逻辑和业务逻辑都写在一块(Activity.java 中),没有实现V和C的分离;



下面用MVC模式编写本例子

MainActivity.java:

package com.lwp.mvcsmalltest;

import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

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

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userName = et_username.getText().toString();
                String pwd = et_pwd.getText().toString();
                if(!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(pwd)){
                    login(userName,pwd);
                }else{
                    Toast.makeText(MainActivity.this,"用户名和密码不能为空",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private void login(final String userName, final String pwd) {
        //开一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);//沉睡两秒模拟登录
                //runOnUiThread切回主线程更新UI
                if(userName.equals("jiangxue") && pwd.equals("666666")){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"登录成功",Toast.LENGTH_SHORT).show();
                        }
                    });
                }else{
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"登录失败",Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }).start();
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"/>

    <EditText
        android:id="@+id/et_pwd" 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="密码"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"/>

</LinearLayout>

**刚刚说过View层其实就是程序的UI界面,用于向用户展示数据以及接收用户的输入;
Controller控制器用于更新UI界面和数据实例,
由此我们可以分析MainActivity.java得到以下的结果:MainActivity既是V又是C,没有实现V和C的分离;业务逻辑(本例中即login()跟UI逻辑都写在一个Activity里面),**这样写毫无疑问很冗杂,对于简单的项目也许没什么影响和明显的弊端,甚至显得方便,但是一旦项目大了,这样写会使可读性非常低,不利于项目后期的诸多工作;

到此,我们便用MVC模式完成了登录界面小案例;
下面分析MVP模式



MVP模式


presenter   n.节目主持人,演播员; 推荐者; 提出者; 赠送者

MVP模式的核心思想:

对比与MVC,把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model类。

作用:
  1. 分离视图逻辑和业务逻辑,降低耦合;
  2. Activity只处理生命周期的任务,代码简洁;
  3. 视图逻辑和业务逻辑抽象到了View和Presenter中,提高阅读性;
  4. Presenter被抽象成接口,可以有多种具体的实现
  5. 业务逻辑在Presenter中,避免后台线程引用Activity导致内存泄漏

下面从零到一开始实战:
首先新建一个项目,在主包下创建三个包(即model、presenter、view),待会儿用于存放MVP三层各自的代码。然后把MainActivity.java拉进view包:

接下来我们把方才的几个UI逻辑都抽象成View接口,方才哪几个UI逻辑呢?就登陆成功、登录失败、弹出toast等这些个UI逻辑了:

把UI逻辑抽象成BaseView接口:
package com.lwp.mvptest.view;

/**
 * Created by 700 on 2019/1/12.
 */

public interface BaseView {
    void showToast(String msg) ;
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}
把业务逻辑抽象成Presenter接口:

presenter/BasePresenter.java:

package com.lwp.mvptest.presenter;

import android.provider.UserDictionary;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;

/**
 * Created by 700 on 2019/1/12.
 */

public interface BasePresenter {

    void attachView(BaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void login(User user);
}

model/User.java:

package com.lwp.mvptest.model;

/**
 * Created by 700 on 2019/1/12.
 */

public class User {

    private String name;
    private String pwd;

    public User(String name, String pwd){
        this.name = name;
        this.pwd = pwd;
    }

    public String getName(){return name;}

    public void setName(String name) {this.name = name;}

    public String getPwd() {return pwd;}

    public void setPwd(String pwd) {this.pwd = pwd;}

}

此时目录:

编写MainActivity.java:
实例化各组件,实例化model类对象,实现UI逻辑接口:

public class MainActivity extends AppCompatActivity implements BaseView{

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

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

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new的时候把东西get下来,赋给model Class ,完美!
                User user = new User(et_username.getText().toString(),et_pwd.getText().toString());
            }
        });
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginSuccess(String msg) {
        showToast(msg);
    }

    @Override
    public void loginFailed(String msg) {
        showToast(msg);
    }
}

presenter包下再创建一个 MainPresenter(Implement) 类,书写业务逻辑,实现业务逻辑接口:

这里,
我们在 presenter包下写好 抽象的业务逻辑接口BasePresenter
  在接口里面定义好 抽象的业务逻辑方法
然后在 presenter包下又写下一个对应 抽象的业务逻辑接口BasePresenter业务逻辑实现类MainPresenter
  用于实现对应的接口;
这样子,我们便把 业务逻辑抽象出来,实现在 业务逻辑实现类中,
到时候 Activity.java中要使用对应的 业务逻辑的时候,
只需要简简单单 实例化一个对应的 业务逻辑实现类对象
用它调用一个 自定义方法(如下面的attachView())
Activity的this指针(也即activity本身)赋给 业务逻辑实现类对象中的全局变量
之后即可用这个 业务逻辑类对象去调用 实现类中对应的业务逻辑方法
接收对应的 数据,实现 对应的业务逻辑

也就是,
现在activity要使用业务逻辑的话就不用再在写具体的业务逻辑了,
抽象地说,可以说只要三行代码;
**第一行实例化业务逻辑实现类的对象,
第二行绑定this和业务逻辑实现类的对象,
第三行使用对象并以相关数据为参数调用相关的业务逻辑方法实现即可;**

package com.lwp.mvptest.presenter;

import android.text.TextUtils;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;

/**
 * Created by 700 on 2019/1/12.
 */

public class MainPresenter implements BasePresenter {

    private BaseView baseView;

    @Override
    public void attachView(BaseView v) {
        this.baseView = v;//绑定的时候把view置进来
    }

    @Override
    public void detachView() {
        baseView = null;//解绑时置空即可
    }

    @Override
    public void login(User user) {
        if(!TextUtils.isEmpty(user.getName()) && !TextUtils.isEmpty(user.getPwd())) {
            if (user.getName().equals("jiangxue") && user.getPwd().equals("666666")) {
                baseView.loginSuccess("登陆成功");
            }else {
                baseView.loginFailed("登录失败");
            }
        }else{
            baseView.showToast("用户名或密码不能为空");
        }
    }
}

接下来修改MainActivity,实例化业务逻辑类对象,绑定View(以及解绑逻辑),传入数据供给并调用业务逻辑方法:

package com.lwp.mvptest.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.lwp.mvptest.R;
import com.lwp.mvptest.model.User;
import com.lwp.mvptest.presenter.MainPresenter;

public class MainActivity extends AppCompatActivity implements BaseView{

    private EditText et_username;
    private EditText et_pwd;
    private Button btn_login;

    private MainPresenter mainPresenter;//声明业务逻辑类

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

        et_username = (EditText)findViewById(R.id.et_username);
        et_pwd = (EditText)findViewById(R.id.et_pwd);
        btn_login = (Button) findViewById(R.id.btn_login);

        mainPresenter = new MainPresenter();//实例化业务逻辑类对象
        mainPresenter.attachView(this);//绑定view(把this付给业务逻辑类中的全局变量,
                                        //业务逻辑类中的逻辑方法会使用到这个全局变量(传进去的this),
                                        // 从而具体实现业务逻辑类中的业务逻辑)

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new的时候把东西get下来,赋给model Class ,完美!
                User user = new User(et_username.getText().toString(),et_pwd.getText().toString());
                mainPresenter.login(user);//传入数据,调用业务逻辑方法
            }
        });
    }

    @Override
    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginSuccess(String msg) {
        showToast(msg);
    }

    @Override
    public void loginFailed(String msg) {
        showToast(msg);
    }

    //销毁状态周期时解除绑定
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mainPresenter.detachView();
    }
}

成功运行:

此时目录如下:
**小结:
User 用于存储数据;
BasePresenter是业务逻辑接口抽象;
MainPresenter实现业务逻辑接口;
BaseView是抽象的UI逻辑接口,在MainActivity中实现;
MainActivity统筹所有;**

至此我们其实便用MVP模式完成了登录界面小案例;


MVP模式下多个Activity情况下的接口抽取

假如我们项目中再来一个OtherActivity,实现功能如此如此(见下方代码注释):

package com.lwp.mvptest.view;

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

import com.lwp.mvptest.R;

public class OtherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);
        
        //业务逻辑相关        请求图片或下载上传。。。
        //UI逻辑          显示图片        显示进度条   弹Toast。。。
    }
}

我们按照上面的思路给它抽象出对应的逻辑接口和逻辑实现类:

UI逻辑接口OtherBaseView:

package com.lwp.mvptest.view;

/**
 * Created by 700 on 2019/1/13.
 */

public interface OtherBaseView {
    void showToast(String msg);
    void showProgress(int progress);
}

业务逻辑接口OtherPresenter:

package com.lwp.mvptest.presenter;

import com.lwp.mvptest.view.OtherBaseView;

/**
 * Created by 700 on 2019/1/13.
 */

public interface OtherPresenter {
    void attachView(OtherBaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();
    
    void uploadImage(String path);
}

业务逻辑接口实现类OtherPresenterImpl:

package com.lwp.mvptest.presenter;

import com.lwp.mvptest.view.OtherBaseView;

/**
 * Created by 700 on 2019/1/13.
 */

public class OtherPresenterImpl implements OtherPresenter {
    @Override
    public void attachView(OtherBaseView v) {
        
    }

    @Override
    public void detachView() {

    }

    @Override
    public void uploadImage(String path) {

    }
}

OtherActivity,实现OtherBaseView:

package com.lwp.mvptest.view;

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

import com.lwp.mvptest.R;

public class OtherActivity extends AppCompatActivity implements OtherBaseView{

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

        //业务逻辑相关        请求图片或下载上传。。。
        //UI逻辑          显示图片        显示进度条   弹Toast。。。
    }

    @Override
    public void showToast(String msg) {
        
    }

    @Override
    public void showProgress(int progress) {

    }
}

此时项目结构:

UI逻辑接口抽取

我们观察一下,OtherBaseView和BaseView有什么不同,

public interface BaseView {
    void showToast(String msg) ;
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}
--------------------------------------
public interface OtherBaseView {
    void showToast(String msg);
    void showProgress(int progress);
}

可以发现统一地都有一个showToast()方法(通过这个案例,我们可以体会开发过程中的一些相应的场景——两个抽象接口具有相同的方法时候),

这样的话,我们可以对这两个接口进行抽取
(抽取像我们数学表达式中的提公因式,是普适而重要的一环),

下面在View包下新建一个MainBaseView,代替原来BaseView的位置(MainActivity的UI逻辑接口),
抽取相同的部分放在BaseView中,MainBaseView和OtherBaseView继承BaseView:

public interface BaseView {
    void showToast(String msg) ;
}
------------------
package com.lwp.mvptest.view;

/**
 * 只负责MainActivity的UI逻辑
 * Created by 700 on 2019/1/13.
 */

public interface MainBaseView extends BaseView{
    void loginSuccess(String msg) ;
    void loginFailed(String msg) ;
}

------------------
package com.lwp.mvptest.view;

/**
 * 只负责OtherActivity的UI逻辑
 * Created by 700 on 2019/1/13.
 */

public interface OtherBaseView extends BaseView{
    void showProgress(int progress);
}

最后记得public class MainActivity extends AppCompatActivity implements MainBaseView


业务逻辑接口的抽取

我们观察一下BasePresenter和OtherPresenter的区别:

public interface BasePresenter {

    void attachView(BaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void login(User user);
}
------------------
public interface OtherPresenter {

    void attachView(OtherBaseView v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();

    void uploadImage(String path);
}

我们可以将attachView()以及detachView()抽取出来,放在BasePresenter中,新建一个MainBasePresenter,让其和OtherPresenter 继承自BasePresenter:

public interface BasePresenter<T> {

    void attachView(T v);//绑定view | 由原理图可知,presenter需要绑定View才行

    void detachView();
}
--------------------------------------
/**
 * 只为MainActivity提供业务逻辑
 * Created by 700 on 2019/1/13.
 */

public interface MainBasePresenter extends BasePresenter<MainBaseView>{
    void login(User user);
}
--------------------------------------
/**
 * 只为OtherActivity提供业务逻辑
 * Created by 700 on 2019/1/13.
 */

public interface OtherPresenter extends BasePresenter<OtherBaseView>{
    void uploadImage(String path);
}

注意这里的BasePresenter要用泛型进行定义,因为下面的子业务逻辑模块(如MainBasePresenter )都需要用attachView()绑定对应的UI逻辑接口(如MainBaseView),所以这里使用泛型,子逻辑模块在继承时可以动态匹配。

接下来进入MainPresenter,进行代码的修改,
删掉下面这一段,然后更改implement到MainBasePresenter,然后Alt+Enter自动生成对应方法,最后修改类中对应的内容:

package com.lwp.mvptest.presenter;

import android.text.TextUtils;

import com.lwp.mvptest.model.User;
import com.lwp.mvptest.view.BaseView;
import com.lwp.mvptest.view.MainBaseView;

/**
 *MainActivity业务逻辑的具体实现
 * Created by 700 on 2019/1/12.
 */

public class MainPresenter implements MainBasePresenter {

    private MainBaseView mainBaseView;

    @Override
    public void attachView(MainBaseView v) {
        this.mainBaseView = v;
    }

    @Override
    public void detachView() {
        mainBaseView = null;//解绑时置空即可
    }

    @Override
    public void login(User user) {
        if(!TextUtils.isEmpty(user.getName()) && !TextUtils.isEmpty(user.getPwd())) {
            if (user.getName().equals("jiangxue") && user.getPwd().equals("666666")) {
                mainBaseView.loginSuccess("登陆成功");
            }else {
                mainBaseView.loginFailed("登录失败");
            }
        }else{
            mainBaseView.showToast("用户名或密码不能为空");
        }
    }
}

最后为各个文件添加注释...

/**
 * OtherActivity业务逻辑的具体实现
 * Created by 700 on 2019/1/13.
 */

public class OtherPresenterImpl implements OtherPresenter...

到此,逻辑编写便完毕了,这里的Other系列都是作为演绎作用的空模板,暂时没有实现具体的功能,

最后成功运行:


相关文章
|
4月前
|
存储 前端开发 测试技术
MVC、MVP、MVVM 模式
MVC、MVP 和 MVVM 是三种常见的软件架构模式,用于分离用户界面和业务逻辑。MVC(Model-View-Controller)通过模型、视图和控制器分离数据、界面和控制逻辑;MVP(Model-View-Presenter)将控制逻辑移到 Presenter 中,减少视图的负担;MVVM(Model-View-ViewModel)通过数据绑定机制进一步解耦视图和模型,提高代码的可维护性和测试性。
|
4月前
|
缓存 前端开发 Android开发
Android实战之如何截取Activity或者Fragment的内容?
本文首发于公众号“AntDream”,介绍了如何在Android中截取Activity或Fragment的屏幕内容并保存为图片。包括截取整个Activity、特定控件或区域的方法,以及处理包含RecyclerView的复杂情况。
41 3
|
4月前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
51 2
|
3月前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
110 0
|
5月前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
67 3
|
4月前
|
Android开发
Android实战之如何快速实现自动轮播图
本文介绍了在 Android 中使用 `ViewPager2` 和自定义适配器实现轮播图的方法,包括添加依赖、布局配置、创建适配器及实现自动轮播等步骤。
149 0
|
4月前
|
Android开发
Android开发显示头部Bar的需求解决方案--Android应用实战
Android开发显示头部Bar的需求解决方案--Android应用实战
48 0
|
XML 前端开发 数据处理
Android——MVC、MVP、MVVM框架实现登录示例
MVC 描述 缺点 优点 MVP 效果图 描述 缺点 优点 代码解析 视图效果图 建立实体类 建立实体类接口 实现实体类接口 设置P层 建立交互接口 数据绑定 MVVM 效果图 描述 代码解析 导入dataBinding 实体类 建立viewmodel xml绑定数据 视图与数据绑定
473 0
Android——MVC、MVP、MVVM框架实现登录示例
|
2天前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
22 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
15天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
115 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 6
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 9
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 10
    一款基于 .NET MVC 框架开发、功能全面的MES系统