安卓MVP开发初探

发布于 / 技术 / 0 条评论

近期在制作一个安卓的Launcher,其中AppList页面需要读取另一个资源包的文件,而且采用了安卓官方的日夜切换会在切换时recreate当前的Activity,如果快速进行操作会导致该App的Fragment没有及时被回收,双倍吃内存从而OOM。

后来有一个朋友建议我试试MVP模式开发。于是就有了这篇文章。来记录一下开发过程中的突发奇想。

我也不懂,刚学,有问题就是你对。

先引用一下MVP的说明:

所谓MVP(Model-View-Presenter)模式。是将APP的结构分为三层:
view – UI显示层
提供UI交互
在presenter的控制下修改UI。
将业务事件交由presenter处理。
注意. View层不存储数据,不与Model层交互。
presenter – 逻辑处理层
对UI的各种业务事件进行相应处理。也许是与Model层交互,也许自己进行一些计算,也许控制后台Task,Servic
对各种订阅事件进行响应,修改UI。
临时存储页面相关数据。
注意. Presenter内不出现View引用。
model – 数据层
从网络,数据库,文件,传感器,第三方等数据源读写数据。
对外部的数据类型进行解析转换为APP内部数据交由上层处理。
对数据的临时存储,管理,协调上层数据请求。
将复杂的功能分割为各层内的小问题。各层内功能单一。这样易于功能修改拓展与Debug。
解耦的设计,独立的模块,更有利于分工开发与测试。

作者:Jude95
链接:https://www.jianshu.com/p/ed2aa9546c2c
来源:简书

另外,为了方便管理接口,我们还需要一个合约来对接口进行约定。下面开始开发:

1.创建合约类(MainContract)

合约是一个接口文件,用来管理、设计各个层之间的接口。

在这个类中,规定了Model\View\Presenter应该实现哪些功能。这次测试APP的作用是获取AppList并且在前端的ListView中显示出来。

public interface MainContract {
    interface Model{
        List<ResolveInfo> app_list(PackageManager pm);
    }

    interface View{
        void updateApps(List<ResolveInfo> applist);
        void showToast(int info);
    }

    interface Presenter{
        void getApps();
        void bindView(MainActivity mainActivity);
        void setNum(int initNum);
    }
}

2.View类

在安卓开发中,我把Activity作为View类进行处理。该类不再处理逻辑而是仅更新显示内容。

MainActivity中实现了一些方法,包括了显示和初始化。在这里会有一个initNum用于计算重置次数,每当MainActivity被重置就会给initNum加一,当V层初始化P层时会将该数值传递到P层,这样就知道P层是在哪一次被初始化的。

在这里将P层设置为静态,并且每次重启时重新绑定,销毁时解除绑定。

public class MainActivity extends AppCompatActivity implements MainContract.View {

    private static MainPresenter mainPresenter;
    private ListView appsListView;
    private MainActivity that;
    private int initNum = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        that = this;
        initView(savedInstanceState);
    }

    private void initView(Bundle savedInstanceState) {
        try {
            initNum = savedInstanceState.getInt("rec");
        }catch (Exception e){

        }

        initNum +=1;

        if(mainPresenter == null){
            mainPresenter = new MainPresenter();
            mainPresenter.setNum(initNum);
        }
        mainPresenter.bindView(this);

        appsListView = findViewById(R.id.AppsListView);
        
        Button button =findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                that.recreate();
            }
        });
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("rec",initNum);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mainPresenter.getApps();
    }

    @Override
    public void updateApps(List<ResolveInfo> applist) {
        appsListView.setAdapter(new CommonAdapter<ResolveInfo>(this,R.layout.adapter_apps_item,applist) {
            @Override
            protected void convert(ViewHolder viewHolder, ResolveInfo item, int position) {
                viewHolder.setText(R.id.title, String.valueOf(item.loadLabel(that.getPackageManager())));
                viewHolder.setText(R.id.packagename,item.activityInfo.name);
                viewHolder.setImageDrawable(R.id.icon,item.loadIcon(that.getPackageManager()));
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mainPresenter.bindView(null);
    }

    public void showToast(int intdd) {
        Toast.makeText(that,""+intdd,Toast.LENGTH_SHORT).show();
    }
}

3.Presenter类

P层负责初始化并获取M层信息并且回调V层函数将其更新到页面上。通过阅读代码可以看出每次更新应用列表时P层都会返回自己的初始化ID,这样就可以确定使用的P层是在V层的那一次更新中创建的。

public class MainPresenter implements MainContract.Presenter {

    private MainModel model;
    private MainActivity view;
    private int Intdd=0;

    public MainPresenter() {
        this.model = new MainModel();
    }

    @Override
    public void getApps() {
        PackageManager pm = view.getPackageManager();
        List<ResolveInfo> list = model.app_list(pm);
        if(list!=null){
            view.updateApps(list);
            view.showToast(Intdd);
        }
    }

    @Override
    public void bindView(MainActivity mainActivity) {
        this.view = mainActivity;
    }

    public void setNum(int initNum) {
        Intdd = initNum;
    }
}

4.Model类

M层负责获取app列表并提供列表给P层,在M层我们对数据进行了一次缓存,并在数据获取部分增加了5秒的等待,这样我们就可以知道系统是否有效的读取了缓存(当缓存存在时直接返回,减少系统调用次数。当缓存不存在时就会等待5秒后获取缓存。)

public class MainModel implements MainContract.Model {
    private List<ResolveInfo> all_apps_list = null;

    @Override
    public List<ResolveInfo> app_list(PackageManager pm) {
        if(pm==null)return null;
        if(all_apps_list!=null) return all_apps_list;
        SystemClock.sleep(5000);
        Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
        resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        List<ResolveInfo>  resolveinfoList = pm.queryIntentActivities(resolveIntent, 0);
        all_apps_list = resolveinfoList;
        return resolveinfoList;
    }

}

5.运行

通过运行上述代码,可以发现P层仅在V层第一次被创建时初始化,之后recreateV层不再重新初始化P层。M层在P层初始化时被初始化,因为后续没有重新初始化P层所以M层也得以保留,数据可以稳定的从缓存中读取而无需每次都重新获取。

但是这样的代码还有一个问题,就是每次程序运行的时候依旧需要通过5秒等待来初始化。所以我们可以将列表持久化到文件中去,每次启动时先从文件中快速读取到列表,同时异步在后台更新该文件并在获取成功后更新list。这样就可以做到不仅启动迅速,而且不会导致严重卡顿,节约了内存。

另外,出于MVP设计的优势,作出上述更改的过程完全不需要修改P层和V层,有效降低了开发人员的开发难度。是好的。

本网站在未特殊说明的情况下采用知识共享署名-非商业性使用-相同方式共享 3.0 协议进行许可。
<-数据丢失->