基于Android Architecture Blueprints的MVP案例分析与实现

MVP架构模式详解 MVP(Model-View-Presenter)架构通过将应用程序分为三个主要组件,实现了关注点分离,提高了代码的可维护性、可测试性和可读性。它特别适用于需要清晰结构和高测试覆盖率的中大型应用程序。虽然MVP架构存在一些挑战,如代码量增加、学习曲线和潜在的Presenter膨胀问题,但通过遵循最佳实践和合理规划,这些问题可以得到有效缓解。随着技术的发展,MVP也在不断演进,与新的技术和模式结合,继续发挥其价值。 阅读详情

基于Android Architecture Blueprints的MVP案例分析与实现

株洲新程IT教育 李赞红

1 概述

         Android Architecture Blueprints是Google官方版本的MVP实现,我们通过https://github.com/googlesamples/android-architecture/tree/todo-mvp可以阅读具体源码。AAB用于TODO计划任务的管理,主要有TODO列表、添加新的TODO、查看TODO详情与TODO统计等功能,总体来说,该案例具有较强的参考价值,特别是在数据存储方面提供了三种方式:远程数据存储、本地数据存储和缓存存储,并使用了标准的MVP设计模式,如图1所示。

图1:AndroidArchitecture Blueprints 的MVP实现

        

         本文将结合AndroidArchitecture Blueprints实现一个业务更简单的案例,一方面帮助大家理解MVP的设计思路与实现过程,另一方面把常见的查询、添加和删除操作作为参考实现,方便大家理解。

2 MVP

         MVP从MVC架构模式演化而来, MVC分别代表模型、视图和控制器,在Android中,定义Class类作为模型,Layout XML表示视图,而Activity用作控制器,这样一来,在Activity中充斥了大量代码,无论是从扩展性和重用性都无法达到理想的效果。个人以为,MVC分层在Android App开发中显得极为牵强。而MVP解决了这个问题。

 

         MVP即为Model、View和Presenter。Model表示模型,实现数据存储与业务逻辑;View表示视图,提供用户交互的接口;Presenter表示主导器,相当于MVC中的Controller但比Controller更灵活。MVP的关系如图2所示。


图2:MVP关系

 

         从上图可以看出:

A) View将功能委托给Presenter完成,Presenter调用Model完成业务功能与数据存储,并再次通过View更新UI;

B) View和Model没有直接关联,无法相互调用;

C)  Presenter和View可以相互调用;

D) Presenter调用Model完成业务功能。

 

根据Google官方说明,Fragment作为View层的实现组件,包含Presenter的引用;Model就是一个普通的类,使用了单例模式获取对象;Presenter虽然也是一个普通的类,但拥有View和Model的引用,是一个八面玲珑的家伙。

 

使用Fragment而不是Activity作为View,主要有两个原因:

A)通过Activity和Fragment分离更适合对于MVP架构的实现,Activity将作为全局的控制者将Presenter于View联系在一起;

B)采用Fragment更有利于平板电脑的布局或者是多视图屏幕。

 

         我们为View和Presenter定义了最基本的实现,View的基本实现为BaseView接口,Presenter的基本实现为BasePresenter接口。

 

public interface BaseView<T> {

   void setPresenter(T presenter);

}

 

public interface BasePresenter {

   void start();

}

 

         BasePresenter接口中定义了start()方法,主要用于完成一些初始化的工作,BaseView接口定义的setPresenter()方法则用于建立与Presenter的关系。

 

         MVP的优点归纳如下:

A) 各个层次之间的职责更加单一清晰;

B) 很大程度上降低了代码的耦合度;

C)  复用性大大提高;

D) 面向接口编程,定义与实现分离,方便测试与维护;

E)  代码更简洁。

 

有优点自然有缺点:

A) 类变得更多了;

B) 组件与组件之间的关系很复杂。

3 面向接口编程

         案例AndroidArchitecture Blueprints严格遵循面向接口编程,同时,数据存储模块提供了面向接口编程的最佳实践,当我们怀疑面向接口编程的必要性时,Google通过现实案例马上打消了我们的这种想法。面向接口编程是一门优雅而美好的代码艺术,功能之间通过轻量级的接口联系在一起,摆脱了低级趣味的依赖关系。通过这种极度抽象的上层建筑来维护组件之间的关联,大大增强了代码的艺术性、维护性与重用性。

 

         所以,View要定义接口,Presenter要定义接口,Model也要定义接口。

 

         每一个界面都涉及到五个组件:View、Presenter、Contract、Model和Activity。其中,View定义了与UI相关的操作,比如提示信息的显示与隐藏、状态的改变、数据的显示、新Activity的打开等等,Fragment将实现View接口;Presenter是功能实现的粘合剂,View的功能委托给Presenter,Presenter调用Model完成业务功能,最后Presenter再次调用View来更新UI;Contract将View与Presenter两个接口进行集中管理;Model实现业务功能与数据存储;Activity是界面显示的起点,负责创建其他各个组件,并依次维护各组件之间的关系。

 

         在Android ArchitectureBlueprints案例中,是以界面功能为单位对package进行定义的:

图3:package划分

 

         如图3所示,添加和修改TODO定义在addedittask包中,TODO详情定义在taskdetail包中,TODO列表显示定义在tasks包中,TODO数据统计定义在statistics包中。而data包定义了Model有关的类,util包主要是一些工具类。

 

         现有一个接受客户意见和反馈的建议收集模块,主要有我的建议列表、提建议和删除我的建议等功能,我们以提建议为例通过面向接口编程的思想来定义主要的功能接口。首先,我们定义一个Model层的接口AdviceDataSource,该接口定义如下:

public interfaceAdviceDataSource {

    public void addAdvice(@NonNull Stringcontent, @NonNull int uid, @NonNull AddedAdviceCallBack callBack);

 

    public interface AddedAdviceCallBack{

        void onAdviceAdded();

    }

}

 

         方法addAdvice()用于添加一条新建议,参数content表示建议的内容,uid表示用户id。考虑到所有请求都是基于网络的异步请求,方法最后的参数指定了一个回调接口,用于响应请求结束后的后续动作(比如添加成功后要关闭当前Activity)。

 

         接下来,定义AddAdviceContract接口,该接口集中管理View和Presenter两个接口:

public interfaceAddAdviceContract {

    public interface Presenter extendsBasePresenter{

        public void addAdvice(String content);

    }

 

    public interface View extendsBaseView<Presenter>{

        public void showAdvicesList();

    }

}

 

         代码中AddAdviceContract .Presenter接口继承自BasePresenter接口,定义了addAdvice()方法,该方法将在Fragment中调用,接收来自Fragment中的建议内容,并传递给AdviceDataSource.addAdvice()方法进行处理(保存到远程服务器)。AddAdviceContract .View接口是BaseView的派生接口,定义了showAdvicesList()方法,该方法用于添加完建议后,关闭当前Activity以便重新加载并显示建议列表。

 

         现在我们来理顺提建议这个功能的调用流程:在实现了View接口的Fragment中,调用AddAdviceContract .Presenter的addAdvice()方法提交新的建议,该方法又调用AdviceDataSource. addAdvice()方法将建议内容保存到远程服务器,确定保存成功后,在AddAdviceContract .Presenter的addAdvice()方法中调用AddAdviceContract.View的showAdvicesList()方法关闭当前窗口以显示新的建议列表。

 

         总体来说,在View中所有的功能都要委托给Presenter,Presenter调用Model完成任务后再次通过View更新结果,如图4所示。



图4:各组件之间的调用顺序

4 功能实现

4.1 数据接口

         本案例中使用了株洲新程IT教育提供的数据接口,在这里,我们统一使用id为5的用户作为测试,数据接口的url如下(不保证以后能用):

         A)获取用户的建议列表

         http://tr.api.gson.cn/system/feedback/{用户id}

         请求类型:get

         B)提交建议

         http://tr.api.gson.cn/system/feedback/{用户id}

         请求类型:post

         参数:content 建议内容

         C)删除指定建议

         http://tr.api.gson.cn/system/feedback/delete/{用户id}/{建议id}

         请求类型:get

4.2 第三方jar库

         为了简化访问HTTP请求,使用了xUtils3.x第三方库,该工具主要集成了view、bitmap、db、http和task五大模块,简单易用,值得推荐。数据接口返回的是json格式的数据,阿里的fastjson具有强大的JSON数据解析功能,牛逼得不要不要的。

4.3 初始化xUtils

         这个功能与MVP无关,但为了保持案例的完整性,顺便提一下。定义MvpApplication类如下:

public classMvpApplication extends Application {

    @Override

    public void onCreate() {

        super.onCreate();

        x.Ext.init(this);

        x.Ext.setDebug(true);

    }

}

 

         在AndroidManifest.xml配置文件中,配置自定义的Application。

<?xmlversion="1.0" encoding="utf-8"?>

<manifestxmlns:android="http://schemas.android.com/apk/res/android"

         package="com.example.mvp">

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

    <application

        android:name=".MvpApplication"

        android:allowBackup="true"

       android:icon="@mipmap/ic_launcher"

       android:label="@string/app_name"

        android:supportsRtl="true"

       android:theme="@style/AppTheme">

        <activityandroid:name=".advices.AdviceActivity" android:label="建议">

            <intent-filter>

                <actionandroid:name="android.intent.action.MAIN"/>

                <categoryandroid:name="android.intent.category.LAUNCHER"/>

            </intent-filter>

        </activity>

 

        <activityandroid:name=".addadvice.AddAdviceActivity" android:label="提建议">

            <intent-filter>

                <actionandroid:name="com.example.mvp.addadvice.AddAdviceActivity"/>

                <categoryandroid:name="android.intent.category.DEFAULT"/>

            </intent-filter>

        </activity>

    </application>

</manifest>

        

         注意,访问网络数据必须授予android.permission.INTERNET权限。

 

4.4 定义Model

         我们定义了一个名为Advice的JavaBean,用于保存建议的基本属性,并生成了一系列的getter方法和setter方法。

@HttpResponse(parser= AdviceDataSource.AdviceJSONResponseParser.class)

public classAdvice {

    private int id; //建议id

    private int uid; //用户id

    private String content; //建议内容

    private String reply; //回复内容

    private Date createTime;//创建时间

    private Date replydate;//回复时间

    private int replyUid;//回复用户id

 

    public Advice() {

    }

 

    public Advice(int uid, String content, DatecreateTime) {

        this.uid = uid;

        this.content = content;

        this.createTime = createTime;

    }

 

    public Advice(int id, int uid, Stringcontent, String reply, Date createTime, Date replydate, int replyUid) {

        this.id = id;

        this.uid = uid;

        this.content = content;

        this.reply = reply;

        this.createTime = createTime;

        this.replydate = replydate;

        this.replyUid = replyUid;

    }

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public int getUid() {

        return uid;

    }

 

    public void setUid(int uid) {

        this.uid = uid;

    }

 

    public String getContent() {

        return content;

    }

 

    public void setContent(String content) {

        this.content = content;

    }

 

    public String getReply() {

        return reply;

    }

 

    public void setReply(String reply) {

        this.reply = reply;

    }

 

    public Date getCreateTime() {

        return createTime;

    }

 

    public void setCreateTime(Date createTime){

        this.createTime = createTime;

    }

 

    public Date getReplydate() {

        return replydate;

    }

 

    public void setReplydate(Date replydate) {

        this.replydate = replydate;

    }

 

    public int getReplyUid() {

        return replyUid;

    }

 

    public void setReplyUid(int replyUid) {

        this.replyUid = replyUid;

    }

}

 

         在Class中定义了@HttpResponse(parser= AdviceDataSource.AdviceJSONResponse

Parser.class)元注释,这是xUtils提供的功能,指定了AdviceJSONResponseParser解析器,用于将响应结果中的json数据解析为Advice对象。AdviceJSONResponseParser定义在AdviceDataSource接口中,AdviceDataSource接口还定义了建议列表、提建议和删除建议的功能方法,定义如下:

public interfaceAdviceDataSource {

    public class AdviceJSONResponseParserimplements ResponseParser{

 

        @Override

        public void checkResponse(UriRequesturiRequest) throws Throwable {

 

        }

 

        @Override

        public Object parse(Type type,Class<?> aClass, String s) throws Throwable {

            JSONObject json =JSON.parseObject(s);

 

            if(aClass == List.class) {

                if(json.getBoolean("success")) {

                    JSONArray data =json.getJSONArray("data");

                    List<Advice> list =new ArrayList<>();

                    for (int i = 0; i <data.size(); i++) {

                        JSONObject current =data.getJSONObject(i);

                        Advice advice = newAdvice(current.getIntValue("id"),

                               current.getIntValue("uid"),

                               current.getString("content"),

                               current.getString("reply"),

                               current.getDate("create_time"),

                               current.getDate("reply_date"),

                               current.getIntValue("reply_uid"));

                        list.add(advice);

                    }

                    return list;

                }

            }else{

                return s;

            }

            return null;

        }

    }

 

    public void addAdvice(@NonNull Stringcontent, @NonNull int uid, @NonNull AddedAdviceCallBack callBack);

    public void getMyAdvices(@NonNull int uid,@NonNull LoadAdviceCallBack callBack);

    public void deleteAdvice(@NonNull int id,@NonNull  int uid, @NonNullDeletedAdviceCallBack callBack);

 

    public interface LoadAdviceCallBack{

        void onAdviceLoaded(List<Advice>advices);

    }

 

    public interface DeletedAdviceCallBack{

        void onAdviceDeleted();

    }

 

    public interface AddedAdviceCallBack{

        void onAdviceAdded();

    }

}

 

         AdviceRepository类实现了AdviceDataSource接口,使用单例模式获取AdviceRepository对象。通过xUtils提供的http模块非常简单方便地实现了数据读写的功能。

public classAdviceRepository implements AdviceDataSource{

    private static AdviceRepositorysAdviceRepository;

 

    private AdviceRepository() {

    }

 

    public static AdviceRepository getInstance(){

        if(sAdviceRepository == null){

            sAdviceRepository = newAdviceRepository();

        }

        return sAdviceRepository;

    }

 

    @Override

    public void addAdvice(@NonNull Stringcontent, @NonNull int uid, @NonNull final AddedAdviceCallBack callBack) {

        String url ="http://tr.api.gson.cn/system/feedback/" + uid;

        RequestParams params = newRequestParams(url);

       params.addBodyParameter("content", content);

        x.http().post(params, newCommonCallBackAdapter<String>() {

            @Override

            public void onSuccess(String s) {

                callBack.onAdviceAdded();

            }

        });

    }

 

    @Override

    public void getMyAdvices(@NonNull int uid,@NonNull final LoadAdviceCallBack callBack) {

        String url ="http://tr.api.gson.cn/system/feedback/" + uid;

        RequestParams params = newRequestParams(url);

        x.http().get(params, newCommonCallBackAdapter<List<Advice>>() {

            @Override

            public voidonSuccess(List<Advice> advices) {

               callBack.onAdviceLoaded(advices);

            }

        });

    }

 

    @Override

    public void deleteAdvice(@NonNull int id,@NonNull  int uid, @NonNull finalDeletedAdviceCallBack callBack) {

        String url ="http://tr.api.gson.cn/system/feedback/delete/" + uid + "/"+ id;

        LogUtil.d(url);

        RequestParams params = newRequestParams(url);

        x.http().get(params, newCommonCallBackAdapter<String>() {

            @Override

            public void onSuccess(String s) {

                callBack.onAdviceDeleted();

            }

        });

    }

}       

 

         因为所有的http请求都是异步请求,所以每个方法必须通过接口回调才能在请求结束后处理后续事宜。

 

         CommonCallBackAdapter抽象类是CommonCallBack接口的适配器,后者定义了多达4个方法,但常用的只有1个,CommonCallBackAdapter类提供了另外3个不常用方法的默认实现,这样代码就简单多了。

public abstractclass CommonCallBackAdapter<ReturnType>

        implementsCallback.CommonCallback<ReturnType> {

 

    @Override

    public void onError(Throwable throwable,boolean b) {

    }

 

    @Override

    public void onCancelled(CancelledExceptione) {

    }

 

    @Override

    public void onFinished() {

    }

}

4.5 我的建议列表功能实现

         “我的建议列表”UI中包含了下面几个功能:

A) 显示我的建议;

B) 菜单:提建议,将打开新的Activity;

C)  菜单:刷新建议列表。后台如果回复了用户的建议,通过刷新可以马上看到回复内容。

 

运行效果如图5所示。

图5:我的建议列表

 

         本UI涉及到三个布局文件:item_advices.xml、fragment_advices.xml和activity_advices.xml。其中item_advices.xml是ListView列表项的布局,fragment_advices.xml是Fragment的布局文件,包含了一个ListView组件,activity_advices.xml是Activity的布局文件,包含了一个FrameLayout(帧布局),Fragment对象将添加到FrameLayout布局中。

 

item_advices.xml:

<?xmlversion="1.0" encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

             android:orientation="horizontal"

              android:padding="10dp"

             android:layout_width="match_parent"

              android:layout_height="match_parent">

 

    <TextView

        android:layout_width="100dp"

        android:layout_height="100dp"

        android:id="@+id/tv_date"

        android:text="02/06"

        android:gravity="center"

        android:textSize="30sp"

       />

 

    <LinearLayout

        android:layout_width="0dp"

       android:layout_height="wrap_content"

        android:layout_weight="1"

       android:orientation="vertical"

        >

        <TextView

           android:layout_width="match_parent"

           android:layout_height="wrap_content"

            android:text="建议"

           android:id="@+id/tv_content"

            android:textSize="16dp"

            />

        <TextView

           android:layout_marginTop="10dp"

           android:textColor="@android:color/holo_blue_dark"

            android:layout_width="match_parent"

           android:layout_height="0dp"

            android:layout_weight="1"

           android:id="@+id/tv_reply"

            android:text="回复"

            />

    </LinearLayout>

</LinearLayout>

        

fragment_advices.xml:

<?xmlversion="1.0" encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

             android:orientation="vertical"

             android:layout_width="match_parent"

              android:layout_height="match_parent">

    <ListView

       android:layout_width="match_parent"

       android:layout_height="match_parent"

       android:id="@+id/lv_advices"></ListView>

</LinearLayout>

 

activity_advices.xml:

<?xmlversion="1.0" encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

             android:orientation="vertical"

             android:layout_width="match_parent"

              android:layout_height="match_parent">

    <FrameLayout

        android:layout_width="match_parent"

       android:layout_height="match_parent"

       android:id="@+id/frame_content"

        ></FrameLayout>

</LinearLayout>

 

         在advices包中,定义了“我的建议列表”所需要的View、Presenter和Activity,因为Model已经统一在AdviceRepository类中实现,所以不需要重复定义Model。一般来说,一个小模块定义一个对应的Model类,比如,用户模块可以将注册、登陆、修改密码、修改基本资料、注销等功能都定义在一个Model类中。

 

         AdvicesContract接口用于集中管理View和Presenter两个接口,两个接口放在一块儿也更利用我们阅读源码。

public interfaceAdvicesContract {

    public interface Presenter extendsBasePresenter{

        /**

         * 加载我的建议

         */

        void loadMyAdvices();

 

        /**

         * 删除建议

         * @param id

         */

        void deleteAdvice(@NonNull int id);

 

        /**

         * 添加新建议

         */

        void addNewAdvice();

    }

 

    public interface View extendsBaseView<Presenter>{

        /**

         * 显示我的建议

         * @param advices

         */

        void showAdvices(List<Advice>advices);

 

        /**

         * 打开提建议的Activity

         */

        void showAddAdvice();

 

        /**

         * 删除我的一条建议

         * @param id

         */

        void deleteAdvice(@NonNull int id);

    }

}

 

         AdvicesPresenter类是对AdvicesContract.Presenter接口的实现,AdvicesPresenter持有View和Model的引用,本类中分别为AdvicesContract.View和AdviceRepositorymRepository。在构造方法中不仅要建立AdvicesPresenter与AdvicesContract.View和AdviceRepository的关系,同时也要建立AdvicesContract.View与AdvicesPresenter的关系(Presenter和View是相互持有对方引用的)。

 

public classAdvicesPresenter implements AdvicesContract.Presenter {

    private AdvicesContract.View mView;

    private AdviceRepository mRepository;

 

    publicAdvicesPresenter(AdvicesContract.View view, AdviceRepository repository) {

        mView = view;//建立Presenter与View的关系

        mRepository = repository;

 

        //建立View与Presenter的关系

        mView.setPresenter(this);

    }

 

    @Override

    public void loadMyAdvices() {

        //读取id为5的用户的建议

        mRepository.getMyAdvices(5, newAdviceDataSource.LoadAdviceCallBack() {

            @Override

            public voidonAdviceLoaded(List<Advice> advices) {

                mView.showAdvices(advices);

            }

        });

    }

 

    @Override

    public void deleteAdvice(@NonNull final intid) {

        //删除5号用户对应的建议

        mRepository.deleteAdvice(id, 5, newAdviceDataSource.DeletedAdviceCallBack() {

            @Override

            public void onAdviceDeleted() {

                mView.deleteAdvice(id);

            }

        });

    }

 

    @Override

    public void addNewAdvice() {

        mView.showAddAdvice();

    }

 

    @Override

    public void start() {

        loadMyAdvices();

    }

}

 

         Presenter的start()方法通常用于初始化,本例中需要显示所有的建议列表,所以调用了loadMyAdvices()方法。

 

         在loadMyAdvices()方法中,通过调用Model类AdviceRepository的getMyAdvices()方法获取我的建议,再调用AdvicesContract.View中的showAdvices()方法刷新列表。其他道理都差不多。再次强调一下,View将功能委托给Presenter完成,Presenter又调用View刷新结果。

 

         AdvicesFragment类是一个继承自Fragment的View,在AdvicesFragment类中,我们早早为ListView定义了一个适配器AdviceAdapter对象,一开始什么都不显示,读取到数据后再刷新结果。在Fragment的onResume()回调方法中,通常要调用Presenter的start()方法完成初始化。以下是AdviceFragment类的源码:

public classAdvicesFragment extends Fragment implements AdvicesContract.View {

    private AdvicesContract.PresentermPresenter;

    private AdviceAdapter mAdviceAdapter;

 

    public static AdvicesFragment getInstance(){

        return new AdvicesFragment();

    }

 

    @Override

    public void onCreate(@Nullable BundlesavedInstanceState) {

        super.onCreate(savedInstanceState);

        mAdviceAdapter = new AdviceAdapter(newArrayList<Advice>(0));

    }

 

    @Nullable

    @Override

    public View onCreateView(LayoutInflaterinflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

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

        setHasOptionsMenu(true);//显示选项菜单

        return root;

    }

 

    @Override

    public void onViewCreated(View view,@Nullable Bundle savedInstanceState) {

        ListView listView = (ListView)view.findViewById(R.id.lv_advices);

        listView.setOnItemLongClickListener(newAdapterView.OnItemLongClickListener() {

            @Override

            public booleanonItemLongClick(AdapterView<?> adapterView, View view,

                                           inti, final long l) {

                AlertDialog.Builder builder =new AlertDialog.Builder(getContext());

                AlertDialog alertDialog =builder.setTitle("确定删除")

                        .setMessage("确定要删除该建议吗?")

                       .setPositiveButton("删除", new DialogInterface.OnClickListener() {

                            @Override

                            public voidonClick(DialogInterface dialogInterface, int i) {

                               LogUtil.d("==建议id:" + l);

                               mPresenter.deleteAdvice((int) l);

                            }

                        })

                       .setNegativeButton("取消", new DialogInterface.OnClickListener() {

                            @Override

                            public voidonClick(DialogInterface dialogInterface, int i) {

 

                            }

                        }).create();

                alertDialog.show();

                return true;

            }

        });

        listView.setAdapter(mAdviceAdapter);

    }

 

    @Override

    public void onResume() {

        super.onResume();

        mPresenter.start();

    }

 

    @Override

    public void showAdvices(List<Advice>advices) {

        mAdviceAdapter.refreshAdvices(advices);

    }

 

    @Override

    public void showAddAdvice() {

        Intent intent = newIntent(AddAdviceActivity.ACTION);

        startActivityForResult(intent, 0x002);

    }

 

    @Override

    public void onActivityResult(intrequestCode, int resultCode, Intent data) {

        if(requestCode == 0x002 &&resultCode == Activity.RESULT_OK) {

            mPresenter.loadMyAdvices(); //添加完建议后重新加载,实时将新的建议显示出来

        }

    }

 

    @Override

    public void deleteAdvice(@NonNull int id) {

        mAdviceAdapter.deleteAdvice(id);//删除建议

    }

 

    @Override

    public voidsetPresenter(AdvicesContract.Presenter presenter) {

        this.mPresenter = presenter;

    }

 

    @Override

    public void onCreateOptionsMenu(Menu menu,MenuInflater inflater) {

        inflater.inflate(R.menu.menu_advices,menu);

    }

 

    @Override

    public booleanonOptionsItemSelected(MenuItem item) {

        if(item.getItemId() ==R.id.menu_add_advice){

            mPresenter.addNewAdvice();

        }else if(item.getItemId() ==R.id.menu_refresh_advice){

            mPresenter.loadMyAdvices();//刷新

        }

        return true;

    }

 

    /**

     * 适配器

     */

    class AdviceAdapter extends BaseAdapter {

        private List<Advice> mAdvices;

 

        public AdviceAdapter(List<Advice>advices) {

            mAdvices = advices;

        }

 

        public voidsetAdvices(List<Advice> advices) {

            mAdvices = advices;

        }

 

        @Override

        public int getCount() {

            return mAdvices.size();

        }

 

        @Override

        public Object getItem(int i) {

            return mAdvices.get(i);

        }

 

        @Override

        public long getItemId(int i) {

            return mAdvices.get(i).getId();

        }

 

        @Override

        public View getView(int i, View view,ViewGroup viewGroup) {

            View root =LayoutInflater.from(viewGroup.getContext())

                   .inflate(R.layout.item_advices, viewGroup, false);

            TextView tvDate = (TextView)root.findViewById(R.id.tv_date);

            TextView tvContent = (TextView)root.findViewById(R.id.tv_content);

            TextView tvReply = (TextView)root.findViewById(R.id.tv_reply);

 

            Advice advice = mAdvices.get(i);

            Calendar calendar = Calendar.getInstance();

           calendar.setTime(advice.getCreateTime());

           tvDate.setText(calendar.get(Calendar.MONTH) + 1

                    + "/" +calendar.get(Calendar.DAY_OF_MONTH));

           tvContent.setText(advice.getContent());

            tvReply.setText(advice.getReply());

 

            return root;

        }

 

        public voidrefreshAdvices(List<Advice> advices) {

            setAdvices(advices);

            notifyDataSetChanged();

        }

 

        public void deleteAdvice(int id) {

            mPresenter.loadMyAdvices();

        }

    }

}

 

         在Fragment中可以定义ActionBar的菜单,但默认是关闭的,需要通过setHasOptionsMenu(true)方法启用。另外,当需要改变ListView中的数据时,建议将功能封装在Adapter中。

 

         本界面还实现了长按列表项删除建议的功能,您可以阅读源码了解方法的调用规律,基本顺序为View -> Presenter -> View。

 

         最后是AdviceActivity类,该类继承自AppCompatActivity,在onCreate()方法中,先判断AdviceFragment对象是否存在,如果存在,就不需要创建新的AdviceFragment对象了,不存在才创建,节约内存。然后将AdviceFragment添加到Activity的FrameLayout中。最后,通过new AdvicesPresenter(fragment, AdviceRepository.getInstance())语句创建Presenter,这个语句同时创建了Model层的AdviceRepository对象。至此,Fragment(View)、Model和Presenter创建完毕,而在Presenter类中则构建了View与Presenter、Presenter与View、Presenter与Model的关系。

 

@ContentView(R.layout.activity_advices)

public classAdviceActivity extends AppCompatActivity {

    private AdvicesPresenter mAdvicesPresenter;

 

    @Override

    protected void onCreate(@Nullable BundlesavedInstanceState) {

        super.onCreate(savedInstanceState);

        x.view().inject(this);

 

        FragmentManager fragmentManager =getSupportFragmentManager();

        //判断Fragment是否存在

        AdvicesFragment fragment =(AdvicesFragment) fragmentManager

               .findFragmentById(R.id.frame_content);

        //不存在则创建

        if(fragment == null){

            fragment =AdvicesFragment.getInstance();

           ActivityUtils.addFragmentToActivity(fragmentManager,

                    fragment,R.id.frame_content);

        }

 

        //创建Presenter

        mAdvicesPresenter = new AdvicesPresenter(fragment,AdviceRepository.getInstance());

    }

}

 

         上面代码中的ActivityUtils是一个工具类,定义了一个addFragmentToActivity()方法,用于将Fragment添加到Activity中。

 

public classActivityUtils {

    public static voidaddFragmentToActivity(FragmentManager fragmentManager,

                                      Fragmentfragment, int resId){

        FragmentTransaction transaction =fragmentManager.beginTransaction();

        transaction.add(resId, fragment);

        transaction.commit();

    }

}

4.6 提建议功能实现

         提建议就是一个添加功能,运行效果图6所示:

图6:提建议

 

         本功能定义在addadvice包中,同样包含了View、Presenter、Contract和Activity,也就是说,每一个界面都至少需要这几个组件。本功能包含了fragment_add_advice.xml和activity_add_advice.xml两个布局文件,前者为Fragment定义了界面,后者为Activity定义界面。

 

fragment_add_advice.xml:

<?xmlversion="1.0" encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

             android:orientation="vertical"

             android:layout_width="match_parent"

             android:layout_height="match_parent">

 

    <EditText

       android:layout_width="match_parent"

        android:layout_height="300px"

        android:hint="请输入您宝贵的建议"

        android:padding="10dp"

        android:gravity="left|top"

        android:id="@+id/et_content"

        />

 

    <Button

       android:layout_width="match_parent"

       android:layout_height="wrap_content"

        android:id="@+id/btn_send"

        android:text="提交建议"

        />

</LinearLayout>

 

activity_add_advice.xml:

<?xmlversion="1.0" encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

             android:orientation="vertical"

              android:layout_width="match_parent"

             android:layout_height="match_parent">

    <FrameLayout

       android:layout_width="match_parent"

       android:layout_height="match_parent"

       android:id="@+id/frame_content"

        ></FrameLayout>

</LinearLayout>

 

         首先,定义AddAdviceContract接口,该接口集中定义View接口和Presenter接口。因为功能较少,所以方法也很少。

public interfaceAddAdviceContract {

    public interface Presenter extendsBasePresenter{

        public void addAdvice(String content);

    }

 

    public interface View extendsBaseView<Presenter>{

        public void showAdvicesList();

    }

}

 

         其次,分别定义View(AddAdviceFragment)和Presenter(AddAdvicePresenter),其中,AddAdvicePresenter类代码如下:

public classAddAdvicePresenter implements AddAdviceContract.Presenter {

    private AddAdviceContract.View mView;

    private AdviceRepository mAdviceRepository;

 

    publicAddAdvicePresenter(AddAdviceContract.View view, AdviceRepositoryadviceRepository) {

        mView = view;

        mAdviceRepository = adviceRepository;

 

        mView.setPresenter(this);

    }

 

    @Override

    public void addAdvice(String content) {

        mAdviceRepository.addAdvice(content, 5,new AdviceDataSource.AddedAdviceCallBack() {

            @Override

            public void onAdviceAdded() {

                mView.showAdvicesList();

            }

        });

    }

 

    @Override

    public void start() {

        //啥也不干

    }

}

 

         在方法addAdvice()中,保存建议后,调用AddAdviceFragment(这是一个View)的showAdvicesList(),该方法用于关闭当前Activity,并重新刷新AdviceFragment实时显示新增的内容。AddAdviceFragment类的源码如下:

public classAddAdviceFragment extends Fragment implements AddAdviceContract.View {

    private AddAdviceContract.PresentermPresenter;

 

    @ViewInject(R.id.et_content)

    private EditText etContent;

 

    @ViewInject(R.id.btn_send)

    private Button btnSend;

 

    public static AddAdviceFragmentgetInstance(){

        return new AddAdviceFragment();

    }

 

    @Override

    public void onResume() {

        super.onResume();

        mPresenter.start();

    }

 

    @Nullable

    @Override

    public View onCreateView(LayoutInflaterinflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

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

        x.view().inject(this, root);

        this.setHasOptionsMenu(true);

        return root;

    }

 

    @Event(R.id.btn_send)

    private void send(View v){

       mPresenter.addAdvice(etContent.getText().toString());

    }

 

    @Override

    public void showAdvicesList() {

       getActivity().setResult(Activity.RESULT_OK);

        getActivity().finish();

    }

 

    @Override

    public voidsetPresenter(AddAdviceContract.Presenter presenter) {

        this.mPresenter = presenter;

    }

}

 

         嗯嗯,全是一个套路……

5.7 文件结构图

         为了让各位了解文件之间的关系,可以参考如图7所示的文件结构图。

图7:文件结构图

5.8 后记

         本案例是通过理解AndroidArchitecture Blueprints之后写的类似的案例,如有错误,请指教。如需转载,请注明“株洲新程IT教程李赞红”。

 

         欢迎下载本人的另两本原创教程:

A)《Android自定义组件开发详解》(http://download.youkuaiyun.com/detail/lifenote/9445898)

B)《轻松搞定ExtJS》(http://download.youkuaiyun.com/detail/lifenote/1405716)

 

         您可以发邮件至lifenote@21cn.com索取源码!

 

株洲新程IT教育 李赞红

2016年6月5日

 

2016年Android开发工具链与架构演进深度解析 Android开发工具链的演进是移动应用开发效率提升的关键。从基础组件到架构设计,技术选型直接影响工程实践的质量与效率。2016年Android生态迎来重要转折,Retrofit、Glide等框架通过模块化设计解决了网络通信和图片加载等核心问题,而Dagger 2等依赖注入工具则优化了代码结构。这些技术不仅提升了开发效率,更为后续Kotlin、React Native等技术的普及奠定了基础。在电商、社交等高频应用场景中,合理选择工具链组合能显著提升性能表现,如Glide的链式调用完美适配RecyclerVi 阅读详情

相关推荐

mvvm_architecture:一个简单的MVVM架构

mvvm_architecture:一个简单的MVVM架构

Android 官方示例:android-architecture 学习笔记(七)之todo-mvp-dagger

项目地址:https://github.com/googlesamples/android-architecture/tree/todo-mvp-dagger/项目结构Dagger2是完全静态的、编译时的依赖注入框架,用于Java和Android。 本项目基于MVP和Dagger2。 注:本文假设读者已基本了解Dagger2的使用 源码分析Application DI@ComponentToD

打渔还是晒网 —— stone 1821

cleanarchitectureblueprints:android项目的干净架构起点

Android 中的 Clean Architecture — 初学者方法 android项目的干净架构起点 查看我在 Medium 上的文章: : 图层 表示层:与 UI 交互的层。 用例:用户可以触发的操作(单击按钮、导航到目的地) 域:包含所有业务模型(数据类) 数据:包含不同数据源的抽象定义。 它使用存储库和数据源建模 框架:实现我们在其余层中提供的依赖项(适用于 android 框架)。 应包含特定于 android 框架的类 应用模块 App:包含了关于android框架的一切,所以Framework和Presentation层 域:包含应用程序业务逻辑的定义、服务器数据模型、存储库的抽象定义和用例的定义。 这是一个简单的纯Kotlin模块(与Android无关) 数据:包含领域层抽象定义的实现。 可以由任何应用程序重复使用而无需修改。 它包含存储库和数据源实现、数

一个简单的MVP模式案例

1. 问题背景 首先需要清楚的一点是MVP模式的设计初衷是:为了解决在MVC模式中,过于复杂的逻辑和界面之间的交互中Activity的职责不单一的问题,Activity既充当了View层,又充当了Controller层的角色。刨除问题的复杂度,直接谈MVP模式的优越性,都是耍流氓。 这也就是为什么我们很多人,为什么不愿意学习MVP的原因。但是如果遇到了一个比较复杂的问题,MVP的解耦能够让你更加轻...

weixin_34050005的博客 348

Android代码-Android Architecture_googlesamples

Android Architecture Blueprints The Android framework provides a lot of flexibility in deciding how to organize and architect an Android app. While this freedom is very valuable, it can also lead to apps with large classes, inconsistent naming schemes, as well as mismatching or missing architectures. These types of issues can make testing, maintaining and extending your apps difficult. The Android Architecture Blueprints project demonstrates strategies to help solve or avoid these common proble

flutter-architecture-blueprints:Flutter架构蓝图是一个项目,介绍了MVVM架构和项目结构方法来开发Flutter应用程序

Flutter架构蓝图 Flutter架构蓝图是一个项目,介绍了MVVM架构和项目结构方法来开发Flutter应用程序。 文献资料 要求 环境 的iOS iOS 13以上 安卓 Android 5.1以上minSdkVersion 22 targetSdkVersion 30 应用架构 基于 + 代码风格 资产,字体 如果添加了一些资产或字体 使用 楷模 如果为api结果添加了一些模型 使用 本地化 如果添加了一些本地化(例如ig编辑的 ) 使用 Git提交消息样式 代码集 项目设定 工作状态 类别 描述 代号 :check_mark_button: 镖 Dart版 :check_mark_button: Flutter版本管理 :check_mark_button: 镖 在开发和生产环境之间切换 , , :check_mark_button: 镖 皮棉/分析 :check_mark_button: 安卓 Kotlin版本 :check_mark_button: 安卓 APK属性 build.gradle( , , , ) :chec

学习Android Architecture Blueprints(一)-mvp分支

1. 前言在几个月前,google sample组织下,创建了一个叫做android-architecture的仓库,第一时间引爆了Android圈,这个项目在于指导使用不同的架构指导我们如何解决测试、维护以及扩展的难题,让我们一一学习下。项目地址2. todo-mvp分支在去年的时候,mvp火了起来,然而,在国内的文章当中,大多是的试讲m-v-p分开来写,我今年毕设使用这种模式,发现一个很严重的问

_StriveG的专栏 2191

CAN-do-paser:Java开发的CAN总线数据帧解析库

CAN总线数据帧使用DBC文件信息解析为物理量,基于Java开发的数据解析工具。

佳瑞的博客 1134

Blueprint简介-Android10.0编译系统(六)

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构 Android是怎么启动的 Android 10.0系统启动之init进程 Android10.0系统启动之Zygote进程 Android 10.0 系统启动之SystemServer进程 Android 10.0 系统服务之ActivityMnagerService Android10.0系统启动之Launcher(桌面)启动流程 And

纸上得来终觉浅,绝知此事要躬行 1209

PyTorch实战:手把手教你用QAT量化感知训练拯救模型精度(附避坑指南)

本文详细介绍了如何使用PyTorch进行QAT量化感知训练,以提升模型在边缘设备上的推理精度。通过核心原理讲解、实战代码示例和避坑指南,帮助开发者掌握从环境配置、模型训练到边缘部署的全流程,有效解决传统量化方法导致的精度损失问题。

c7d8e的博客 329

usb gadget uvc driver的code分析

uvc的linux driver分为host侧和gadget侧两个driver。host侧的driver在v4l2框架下,而设备侧的driver在usb/gadget文件夹下。gadget侧的uvc driver,利用v4l2框架去注册video_device,向应用层提供调用接口。同时,它完成v4l2 video buffer拷贝到usb request下的buffer下,然后利用uvc实现的协

lintwins的专栏 1万+

Android MVP开发模式 google 官方Mvp架构详解

Google官方MVP Sample代码解读关于Android程序的构架, 当前最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法.Repo地址:android-architecture.本文为阅读官方sample代码的阅读笔记和分析.官方Android Architecture Blueprints [beta]:Android在如何组织和构架一个app方...

Android Engineer. 3万+

22版本Altium Designer导出生产文件的规范化操作指南

本文详细介绍了PCB设计中的Gerber文件导出流程及相关文件生成方法。主要内容包括:1)Gerber文件导出的5个关键设置步骤;2)NC钻孔文件的导出方法;3)坐标文件的生成方式;4)文件打包规范要求必须包含Gerber文件、NC钻孔文件和坐标文件;5)文件提交的两种方式(新手推荐提交原始工程文件,量产推荐仅提交Gerber文件包)。文章还特别强调了单位一致性、精度验证等注意事项,为PCB设计文件的规范输出提供了完整指导。

qq_56506325的博客 1059

Android Architecture Blueprints----安卓架构详细分析

首先,让我们看一下google给出的框架: github : https://github.com/googlesamples/android-architecture首先本人也没有用过这么多框架,只是基于自己的经验和分析带领大家研究一下Android Architecture Blueprints。前言Android框架在决定如何组织和构建 Android应用程序时提供了很多灵活性。虽然这种自由

李彬博客专栏 3074

C#串口通信从入门到精通(11)——串口接收的数据存储到本地文件(txt、csv)

本文详细介绍如何将串口通信将数据进行本次存储,主要介绍常用的csv、txt格式文件,由多年实战经验总结,绝对干货!

qq_34059233的博客 7239

Agent应用实践之三 - 基础:AgentScope-ReActAgent源码解析

本章通过总结AgentScope的核心类,再通过分析ReActAgent调用大模型的底层源码原理。使我们基本了解了Agent的基本原理,这个实现方式与OpenClaw使用pi-mono的Agent基础框架是一致的。了解完ReActAgent的基本原理,下一章我们会将一下SpringBoot集成方式的代码原理,这两章是我们构建后续内容的基本框架。

代码让AI扣 470

学习Android Architecture Blueprints(二)-rxjava 和 loader 分支

1. 前言今天咱们继续,看下todo-mvp-rxjava分支与todo-mvp-loaders分支。rxjava分支引入了很火的rxjava。loaders分支引入了3.0就加入进来的Loader框架。2. rxjava分支不知道大家有没有用rxjava,rxjava用起来真的很不错,尤其是用rxandroid做线程调度的时候,配合上RxAndroid 全家桶,真的爽翻了。 RxJava Esse

_StriveG的专栏 1712

RK3588 Android12 添加HDMI和喇叭声音通道切换选项

rk3588 android12系统默认接hdmi out时,声音会切换到hdmi out输出,主板声卡喇叭输出会没有声音输出。现在添加一个选项设置hdmi out时,声音可以保持主板声卡喇叭输出。

Android系统开发笔记 4164

Android Architecture Blueprints(架构蓝图)

android项目架构蓝图:MVP,MVVM项目总纲

HeXinGen的博客 2058

【AI大模型应用开发】教你用AI轻松将你的知识库变为知识图谱(附完整代码,直接运行)

本文我们来介绍如何使用AI,将自己的知识库自动转换为知识图谱,并进行可视化展示。

同学小张的博客 3927

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Android官方mvp说明——Android架构蓝图——android Architecture Blueprints

Android框架在定义怎样组织和架构androidapp的时候,提供了很多的灵活性。 这个自由,非常的有价值,可以使得app及时使用了很大的类,可以让命名和架构不会使得测试,维护,和拓展造成困难。 Android Architecture Blueprints 展示了解决常见问题的可能方法。在这个项目中我们使用了不同的概念和工具来构架了同一个应用。 你可以把这些例子看做是参考或者你

WALLEZhe的博客 1918

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Google 的android-architecture,todo-mvp解析

对于 Android 上的软件架构,开发者不断地在自己造轮子,对于 MVC 、MVP、MVVC 有着各种各样的理解, 网上相关的文章也是大相径庭,所以我们的真主 Google 教父,在 GitHub 有一个开源项目 android-architecture,里面对 Android 的软件架构做出了一些官方解释,并在不同的分支上分别做了很多不同的微小贡献。Google 的这个开源项目 android-

Zdy的后花园 660

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

学习Android Architecture Blueprints(三)-contentproviders分支

1. 前言因为我项目中没用过Dagger2,所以todo-mvp-dagger分支就暂且放一放,先来看contentproviders分支.2. 架构图从中我们可以看到,比todo-mvp-loaders分支,在loader和model之间,多处了ContentProvider。而我们要用的就是Loader框架中的CursorLoader。CursorLoader 文档废话不多说,直接上代码。3.

_StriveG的专栏 1011

Android官方MVP架构解读

综述对于MVP (Model View Presenter)架构是从著名的MVC(Model View Controller)架构演变而来的。而对于Android应用的开发中本身可视为一种MVC架构。通常在开发中将XML文件视为MVC中的View角色,而将Activity则视为MVC中的Controller角色。不过更多情况下在实际应用开发中Activity不能够完全充当Controller,而是...

lvlei19911108的博客 115

Android官方MVP架构示例项目解读

相关资料参见:Android官方MVP架构示例项目解析 综述 对于MVP (Model View Presenter)架构是从著名的MVC(Model View Controller)架构演变而来的。而对于Android应用的开发中本身可视为一种MVC架构。通常在开发中将XML文件视为MVC中的View角色,而将Activity则视为MVC中的Controller角色。不过更多

Java和Android架构 1571
上一篇: 在ADT和Android Studio中配置和使用Git
李赞红
博客等级 码龄24年 53粉丝 3原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值