Logic-x 简单好用 MVP 框架

大家好,我是小新
https://github.com/racofix

MVP,顾名思义:
View 对应于Activity,fragment,负责View的绘制以及与用户交互
Model 依然是业务逻辑和实体模型
Presenter 负责完成View于Model间的交互

优点我就不介绍了,缺点可能都我一样的困扰:

  • M/P 层都需要定义接口和实现类,创建对象占用内存
  • 每一个页面都需要创建P实例并绑定View(重复代码)
  • P层逻辑比较多的时候,开发时不容易考虑其他页面调用,复用难
  • 页面销毁,P层执行异步操作,持有的View 引用造成内存泄露,程序崩溃

我们既然知道它的缺点了,那我们就想办法解决

  • 减少接口和类的定义,数据模型定义为Model,数据复杂的情况引入 Repository
  • 利用用注解和反射,自动实现 P层实例化和View绑定/解绑
  • P层逻辑范围减少,页面(Activity/Fragment)可以多次复用,低耦合高复用
  • 生成代理View,页面销毁不需要 getView()!=null 并不会造成内存泄露

Usage

Talk is cheap. Show me the code.

P层(逻辑)

public interface LoginI {
    interface Logic {
        void sign_in(String username, String password);
    }
    interface View {
        void sign_in_success();
    }
}
public class Login extends BaseLogicImpl<LoginI.View> implements LoginI.Logic {
    @Override
    public void sign_in(String username, String password) {
            if(successfully) getView().sign_in_success();
    }
}

V层(Activity/Fragment)

获取P实例 getLogic(Login.class)
缩小逻辑层的方法范围,降低到最小力度,比如说:登录页需要登录和注册功能,注册页需要注册功能。
那么登录和注册就可以作为逻辑层来实现,那登录页实现 登录/注册逻辑,注册页实现注册逻辑。

@LogicArr({Login.class, Register.class})
public class LoginActivity extends BaseLogicActivity implements
        LoginI.View, RegisterI.View {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLogic(Login.class).sign_in("用户名", "密码");
    }
    @Override
    public void sign_in_success() {
        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void sign_up_success() {
        Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
    }
}
@LogicArr(Register.class)
public class RegisterActivity extends BaseLogicActivity
        implements RegisterI.View {
    @Override
    public void sign_up_success() {
        Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
    }
}

这样,就实现一个使用简单,结构清晰的MVP结构。
解决这些缺点思考了很久,如果你觉得新颖、实用、可以帮到您的话,给一份关注和鼓励。
同样,欢迎各位大神相互指导改进。准备做一些常用的框架 Things2

更多示例:https://github.com/racofix/Basic
框架实现:https://github.com/Things2/Logic-x

设计思路

核心类 LogicProvider

利用注解 @LogicArr 返回逻辑数组类,利用反射初始化逻辑类,然后绑定/解绑View放入Map中
上层通过 getLogic(xxx.class) 从Map中获取逻辑实例,页面销毁逻辑实例会自动从Map中释放

class LogicProvider {
    private static volatile LogicProvider logicProvider;
    private Map<Object, HashSet<BaseLogic>> logicCaches = new LinkedHashMap<>();
    static LogicProvider getInstance() {
        if (logicProvider == null) {
            synchronized (LogicProvider.class) {
                if (logicProvider == null) {
                    logicProvider = new LogicProvider();
                }
            }
        }
        return logicProvider;
    }
    <V> void put(V view) {
        LogicArr logicArr = view.getClass().getAnnotation(LogicArr.class);
        if (logicArr == null) return;
        if (logicCaches.containsKey(view)) return;
        HashSet<BaseLogic> logics = new HashSet<>();
        Class[] classes = logicArr.value();
        for (Class clazz : classes) {
            try {
                BaseLogic<V> baseLogic = Util.castTo(clazz.newInstance());
                baseLogic.bindView(view);
                baseLogic.onLogicCreated();
                logics.add(baseLogic);
            } catch (IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
        }
        if (logics.isEmpty()) return;
        logicCaches.put(view, logics);
    }
    <V, T extends BaseLogic> T get(V view, Class<T> clazz) {
        HashSet<BaseLogic> logics = logicCaches.get(view);
        if (logics == null || logics.isEmpty()) {
            throw new NullPointerException(view.getClass().getName() + " @LogicArr is empty.");
        }
        T baseLogic = null;
        for (BaseLogic logic : logics) {
            String logicName = logic.getClass().getName();
            if (logicName.equals(clazz.getName())) {
                baseLogic = Util.castTo(logic);
                break;
            }
        }
        return baseLogic;
    }
    <V> void remove(V view) {
        HashSet<BaseLogic> logics = logicCaches.get(view);
        if (logics == null || logics.isEmpty()) {
            return;
        }
        for (BaseLogic logic : logics) {
            if (logic.isViewBind()) logic.unbindView();
            logic.onLogicDestroy();
        }
        logicCaches.remove(view);
    }
}

核心类 BaseLogicImpl

利用WeakReference存储View,便于View对象回收,使用动态代理 bindView(view) 生成代理View,
页面销毁时,代理对象还存在,调用方法时候发现View是空了, 代理对象就什么都不做了. 这样既不用判断 getView()!=null 并且不会内存泄露。

public class BaseLogicImpl<V> implements BaseLogic<V> {
    private V viewProxy;
    private WeakReference<V> weakReference;
    @Override
    public void bindView(final V view) {
        weakReference = new WeakReference<>(view);
        viewProxy = Util.castTo(
                Proxy.newProxyInstance(
                        view.getClass().getClassLoader(),
                        view.getClass().getInterfaces(),
                        new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                if (isViewBind()){
                                    return method.invoke(weakReference.get(), args);
                                }
                                return null;
                            }
                        }));
    }
    @Override
    public void unbindView() {
        if (isViewBind()) {
            weakReference.clear();
            weakReference = null;
        }
    }
    @Override
    public boolean isViewBind() {
        return weakReference != null && weakReference.get() != null;
    }
    @Override
    public V getView() {
        return viewProxy;
    }
}

最后

希望利用业余时间和工作的积累,贡献更多有意义、有价值的项目。
如果有待改进,请大神们多多指导。
感谢大家耐心的阅读,如果项目对你有所帮助,希望大家给个关注,大家一起进步。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335