Skip to content
wanglei edited this page Feb 7, 2017 · 13 revisions

XDroidMvp的最佳打开姿势

概述

XDroidMvp也许是目前最好的mvp框架,至少是使用难度最低、维护成本最小的mvp框架。

XdroidMvp概览

Get Started

使用,仅需四步:

step1

clone 'XDroid'库到本地:

git clone https://github.com/limedroid/XDroidMvp.git

step2

mvp作为依赖库,在您的app module 中 添加如下依赖:

compile project(':mvp')

step3

拷贝conf.gradle到您的项目根目录,并修改项目gradle文件下引入:

apply from: "conf.gradle"

并添加:

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

step4

修改XDroidConf配置类,主要针对log、cache、router、imageloader。若采用默认配置,此步骤可略过.

Mvp篇

mvp概览

简而言之:P充当V和M的中间人,当V中需要M时,才会有P.

XActivity、XFragment、XLazyFragment是对V的封装, XPresent是对P的封装。 可直接继承这些类,简化操作.

简单UI可以没有P

适合V中不需要M的情况,如网络api操作、数据缓存等.

此时getP()会返回null,换而言之,在v中不需要调用getP()方法

public class NoPActivity extends XActivity{

    @Override
    public Object newP() {
        return null;
    }
}

一个P对应一个V

适合绝大多数情况

此时在P中可通过getV()获取对应的V, 在V中可通过getP()获取对应的P

V的定义:

public class SinglePActivity extends XActivity<PSingle>{

    @Override
    public PSingle newP() {
        return new PSingle();
    }
}

P的定义:

public class PSingle extends XPresent<SinglePActivity>{

}

一个P对应多个V

此种情况也时常出现,和上面一样, 在P中可通过getV()获取对应的V, 在V中可通过getP()获取对应的P

step1 定义V的接口

public interface ICommonV extends IView<PMulti>{
    void showError(Exception e);		//这是示例方法
}

step2 定义V

BActivity的定义:

public class BActivity extends XActivity<PMulti> implements ICommonV {
    @Override
    public void initData(Bundle savedInstanceState) {
        getP().loadData();
    }

    @Override
    public int getLayoutId() {
        return 0;
    }

    @Override
    public PMulti newP() {
        return new PMulti();
    }


    @Override
    public void showError(Exception e) {

    }
}

CActivity的定义:

public class CActivity extends XActivity<PMulti> implements ICommonV{


    @Override
    public void initData(Bundle savedInstanceState) {
        getP().loadData();
    }

    @Override
    public int getLayoutId() {
        return 0;
    }

    @Override
    public PMulti newP() {
        return new PMulti();
    }

    @Override
    public void showError(Exception e) {

    }
}

step3 定义P

public class PMulti extends XPresent<ICommonV>{

	//这是示例方法
    public void loadData(){
        getV().showError(new IllegalStateException(""));
    }
}

RxPermissions的使用

在V中调用getRxPermissions()即可获取到RxPermissions实例。

getRxPermissions()
                .request(Manifest.permission.CAMERA)
                .subscribe(new Action1<Boolean>() {
                    @Override
                    public void call(Boolean granted) {
                        if (granted){
                            //TODO 许可
                            
                        }else{
                            //TODO 未许可
                            
                        }
                    }
                });

VDelegate的使用

VDelegate中放置一些常用的方法,如显示加载中对话框。。。

getvDelegate().toastShort("");
getvDelegate().gone(true,null);
getvDelegate().toastLong("");
...

Base篇

简单的ListView Adapter

即只有一个viewType的Adapter,这时只需要继承SimpleListAdapter

public class AListAdapter extends SimpleListAdapter<String, AListAdapter.ViewHolder> {


    public AListAdapter(Context context) {
        super(context);
    }

    @Override
    protected ViewHolder newViewHolder(View convertView) {
        return new ViewHolder(convertView);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.item_single;
    }

    @Override
    protected void convert(ViewHolder holder, String item, int position) {

    }

    public static class ViewHolder {

        public ViewHolder(View convertView) {
            KnifeKit.bind(this, convertView);
        }
    }
}

复杂的ListView Adapter

即具有多个viewType的Adapter,这时只需要继承XListAdapter

public class CListAdapter extends XListAdapter<String> {

    public CListAdapter(Context context) {
        super(context);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String item = data.get(position);
        CListAdapter.ViewHolder holder = null;

        if (convertView == null) {
            convertView = View.inflate(context, R.layout.item_single, null);
            holder = new ViewHolder(convertView);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        return convertView;
    }

    public static class ViewHolder {

        public ViewHolder(View convertView) {
            KnifeKit.bind(this, convertView);
        }
    }
}

简单的RecyclerView的Adapter

即具有一个viewType的Adapter,此时可继承SimpleRecAdapter

public class BRecAdapter extends SimpleRecAdapter<String, BRecAdapter.ViewHolder> {

    public BRecAdapter(Context context) {
        super(context);
    }

    @Override
    public ViewHolder newViewHolder(View itemView) {
        return new ViewHolder(itemView);
    }

    @Override
    public int getLayoutId() {
        return R.layout.item_single;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (getRecItemClick() != null) {
                    getRecItemClick().onItemClick(position, data.get(position), 0, holder);
                }
            }
        });
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(View itemView) {
            super(itemView);
            KnifeKit.bind(this, itemView);
        }
    }
}

复杂的RecyclerView的Adapter

即具有多个viewType的Adapter,此时可继承RecyclerAdapter

public class DRecAdapter extends RecyclerAdapter<String,DRecAdapter.ViewHolder> {


    public DRecAdapter(Context context) {
        super(context);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_single, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {

    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(View itemView) {
            super(itemView);
            KnifeKit.bind(this, itemView);
        }
    }
}

XRecyclerView的使用

XRecyclerView & XRecyclerContentLayout & XStateController 的使用详见:

https://github.com/limedroid/ARecyclerView

https://github.com/limedroid/XStateController

ImageLoader篇

简单使用

		 //初始化
        ILFactory.getLoader().init(context);
        //从Asset中加载
        ILFactory.getLoader().loadAssets(imageView, assetPath, null);
        //从file中加载
        ILFactory.getLoader().loadFile(imageView, new File(filePath), null);
        //加载网络图片
        ILFactory.getLoader().loadNet(imageView, urlPath, null);
        //加载网络图片,设置监听
        ILFactory.getLoader().loadNet(context, urlPath, null, new LoadCallback() {
            @Override
            public void onLoadReady(Bitmap bitmap) {
					//图片下载成功
					
            }

        });
        //从res中加载
        ILFactory.getLoader().loadResource(imageView, resIds, null);
        //清除内存缓存
        ILFactory.getLoader().clearMemoryCache(context);
        //继续加载
        ILFactory.getLoader().resume(context);
        //暂停加载
        ILFactory.getLoader().pause(context);

		 //清理磁盘文件缓存
        new Thread() {
            @Override
            public void run() {
                super.run();
                ILFactory.getLoader().clearDiskCache(context);
            }
        }.start();

需要注意的是:clearDiskCache()必须在线程中调用

扩展加载条件

扩展加载条件如动画、加载中图片、加载失败图片、缩放方式等,可以通过Options类进行扩展.

ILFactory.getLoader().loadNet(imageView, urlPath,
                new ILoader.Options(loadingResId, loadErrorResId).scaleType(ImageView.ScaleType.FIT_CENTER));

使用其他图片加载框架

框架中内置了Glide的实现,如果需要使用其他图片加载框架,如picasso等,按如下步骤进行:

step1 利用其他加载框架实现ILoader接口

如果想使用picasso,则可以写一个PicassoLoader,病实现ILoader接口。具体实现可参照 GlideLoader

public class GlideLoader implements ILoader{

    void init(Context context){}

    void loadNet(ImageView target, String url, Options options){}

    void loadNet(Context context, String url, Options options, LoadCallback callback{}

    void loadResource(ImageView target, int resId, Options options){}

    void loadAssets(ImageView target, String assetName, Options options){}

    void loadFile(ImageView target, File file, Options options){}

    void clearMemoryCache(Context context){}

    void clearDiskCache(Context context){}

    void resume(Context context){}

    void pause(Context context){}

}

step2 修改ILFactory # getLoader()的实现

public static ILoader getLoader() {
        if (loader == null) {
            synchronized (ILFactory.class) {
                if (loader == null) {
                    loader = new PicassoLoader();//此次修改为picassoloader
                }
            }
        }
        return loader;
    }

Net篇

必要说明:

  • 使用框架net部分有一个必要条件:实体需要实现IModel接口
  • 支持多个baseUrl,并可对每个url配置自己的provider
  • 可配置公共provider
  • 核心api类:XApi

使用示例:

Api.getGankService().getGankData(type, PAGE_SIZE, page)
                .compose(XApi.<GankResults>getApiTransformer()) //统一异常处理
                .compose(XApi.<GankResults>getScheduler()) //线程调度
                .compose(getV().<GankResults>bindToLifecycle()) //内存泄漏处理
                .subscribe(new ApiSubcriber<GankResults>() {
                    @Override
                    protected void onFail(NetError error) {
                        getV().showError(error);
                    }

                    @Override
                    public void onNext(GankResults gankResults) {
                        getV().showData(page, gankResults);
                    }
                });

啥是NetProvider

NetProvider是对一个网络请求的基本配置,一个baseUrl对应一个provider,多个baseUrl也可对应一个公共的provider.

public interface NetProvider {
    Interceptor[] configInterceptors();	//配置拦截器

    void configHttps(OkHttpClient.Builder builder); //配置https

    CookieJar configCookie();	//配置cookie

    RequestHandler configHandler();	//配置通用请求handler

    long configConnectTimeoutMills();	//配置连接超时时长,单位毫秒

    long configReadTimeoutMills();	//配置读超时时长,单位毫秒

    boolean configLogEnable();	//配置是否打印log

    boolean handleError(NetError error); //公共异常处理
}

怎么注册Provider

注册公共provider

XApi.registerProvider(provider);

为某个baseUrl注册Provider

XApi.registerProvider(gankUrl,provider);

Provider的处理顺序

  • 首先根据baseUrl,获取其对应的provider
  • 如果provider为空,则将通用provider赋值给provider
  • 若provider为空,则抛出异常

即优先级:局部provider > 公共provider

获取service对象

XApi.getInstance().getRetrofit(API_BASE_URL, true).create(GankService.class);

其他操作,可参照demo。

Router篇

普通的UI跳转

不带参数的UI跳转

Router.newIntent(context)		//context表示当前上下文
      .to(EndActivity.class)	//to()指定目标context
      .launch();

带参数的UI跳转

可使用put()方法指定跳转参数

Router.newIntent(context)
      .to(EndActivity.class)
      .putString("arg_name","xdroid")
      .launch();

StartActivityForResult类型的跳转

通过 requestCode()方法指定请求码

Router.newIntent(context)
      .to(EndActivity.class)
      .putString("arg_name","xdroid")
      .requestCode(100)
      .launch();

overridePendingTransition()类型的指定动画

通过anim()指定动画

Router.newIntent(context)
      .to(EndActivity.class)
      .putString("arg_name","xdroid")
      .anim(enterAnim,exitAnim)
      .launch();

设置flag类型跳转

通过addFlags()方法指定flag

Router.newIntent(context)
      .to(EndActivity.class)
      .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
      .launch();

设置场景动画跳转

通过options()方法指定场景动画

Router.newIntent(context)
      .to(EndActivity.class)
      .options(ActivityOptionsCompat.makeBasic())
      .launch();

RxBus篇

对于事件订阅,框架实现了最简单的rxBus,当然也可以使用EventBus.使用rxBus需要遵循如下步骤:

step1 在XActivity & XFragment & 'XLazyFragment' 中重写 useEventBus()方法,使之返回true

    @Override
    public boolean useEventBus() {
        return true;
    }

step2 创建实现IEvent接口的event

如创建一个登录事件LoginEvent

public class LoginEvent implements IBus.IEvent{

    @Override
    public int getTag() {
        return 10;		//事件唯一tag
    }
}

step3 编写事件订阅代码

 BusProvider.getBus().toObservable(LoginEvent.class)
            .subscribe(new Action1<LoginEvent>() {
                @Override
                public void call(LoginEvent loginEvent) {
                     //TODO 事件处理
                }

            });

step4 发送消息

BusProvider.getBus().post(new LoginEvent());

Cache篇

XDroid定义了ICache接口

public interface ICache {
    void put(String key, Object value);         //存

    Object get(String key);                     //取

    void remove(String key);                    //删除

    boolean contains(String key);               //检验是否存在

    void clear();                               //删除所有

}

并实现了DiskCacheMemoryCache,其内部分别借助 DiskLruCacheLruCache实现。

SharedPreferences

SharedPref是对SharedPreferences的封装,包含了一系列的get&put方法、remove等。 推荐实践:创建一个新类,将键名作为常量,并封装对应的put&get静态方法,方便全局使用。如:

public String getUserName(){
    return getString(KEY.USER_NAME , null);
}

可以在XDroidConf中配置缓存文件名称,默认是config

MemoryCache

MemoryCache内部采用LruCache实现,是ICache的实现类。 推荐实践:创建一个新类,将键名作为常量,并封装对应的put&get静态方法,方便全局使用。

DiskCache

DiskCache内部采用DiskLruCache实现,是文件缓存方式,其在基本缓存的基础上,增加了有效期,可以用于Api数据缓存。

void put(String key, String value);
void put(String key, String value, long expireMills);
String get(String key);
void remove(String key);
boolean contains(String key);
void clear();

实现了上述方法。

可以在XDroidConf中配置缓存目录名称,默认是cache

自定义缓存

自定义缓存只需要实现ICache即可。

Kit篇

Codec加密工具类

Codec加密工具类囊括了几乎所有常用的加密算法,包括MD5、SHA、AES、DES、RSA等,具体操作可以阅读源码发现,这里不再赘述。

Kits工具类

  • 框架不再采用常见的堆工具类的方式,而是将所有常用的方法进行分类,聚合为一个类文件,实现了最大程度的精简。
  • 工具类中包括Package(包管理)、Dimens(尺寸)、Random(随机数)、File(文件操作)、IO(IO操作)、Date(时间)、NetWork(网络)、Empty(空检测)等几个子分类。
  • 工具类中内置了很多很靠谱的实现,如文件的md5等,还有更多彩蛋可以阅读相关源码。

Log篇

框架对log进行了美化输出,目前支持:

  • text
  • json
  • xml
  • throwable

具体的可调用:

XLog.json(json)	//格式化json并输出

其他使用类似。

XDroidConf篇

XDroidConf目前支持log、cache、router、imageloader、devMode几个部分的配置。

log

public static final boolean LOG = true;	//配置是否显示log
public static final String LOG_TAG = "XDroid"; //配置log的tag

cache

public static final String CACHE_SP_NAME = "config";  //配置sharedPref的文件名
public static final String CACHE_DISK_DIR = "cache";  //配置缓存文件夹名称	

router

public static final int ROUTER_ANIM_ENTER = Router.RES_NONE; //配置enterAnimation
public static final int ROUTER_ANIM_EXIT = Router.RES_NONE;	//配置exitAnimation

imageloader

public static final int IL_LOADING_RES = ILoader.Options.RES_NONE; //配置图片加载loding占位图
public static final int IL_ERROR_RES = ILoader.Options.RES_NONE; //配置图片加载失败占位图

devMode

public static final boolean DEV = true;  //配置当前是否是开发阶段