# ScreenFit **Repository Path**: Donald/ScreenFit ## Basic Information - **Project Name**: ScreenFit - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2016-06-14 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #Android MVP 实例:Activity/Fragment/Adapter作为Presenter + MVP作为设计模型
 - 何为MVP:[wiki](https://en.wikipedia.org/wiki/Model–view–presenter) - 为什么要加入设计模型?如果你开发过企业级应用且没有使用任何设计模型约束(MVP/MVVM...)的话,一个View(Activity/Fragemnt...)中就可能包含了很多东西,如:视图组件、业务逻辑、数据交互等等,这样的类有可能一个就上400+行代码,而且还不包括自定义继承和Util工具类的使用。 你的代码可能是像下面这样组织的: ![Alt text](http://7xrmmo.com1.z0.glb.clouddn.com/2016-06-08-14653796671255.png) 图片来源最后的参考链接 如果这张图看上去还不是很复杂,那么请你想象一下以下情况:每一个View在任意一个时刻都有可能出现或者消失。但是我们却要确保某些组件在销毁之前的资源回收等,我在项目中碰到的一个bug就是因为fragment所属的activity被回收之后,之前执行的loading导致应用直接崩溃。 凭个人经验看,以上粗放的开发方式有几点坏处: 1. 代码体量大,难以维护(开发、BUG查找...) 2. 业务和视图混淆,难以测试 3. 没法重构 - 项目MVP概念和划分 ![Alt text](http://7xrmmo.com1.z0.glb.clouddn.com/2016-06-08-14653789741670.png) 图片来源最后的参考链接 1. View 自定义的一层,衍生自Vu接口 2. Presenter Activity/Fragment或者是Adapter 3. Model 可以是本地存储的数据、远程数据的模型映射 具体到项目本身,我们需要这样约束,activity等类作为组织者(Presenter),只有它们持有对View(抽象Vu的对应实现)和Mode(数据源或数据源访问访问)的引用,View和Mode互不可见。 如果是这样,那么复杂的任务被分成细小的任务,并且很容易解决。越小的东西,bug越少,越容易debug,更好测试。在MVP模式下的View层将会变得简单,所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。 这样设计的理由: 目前很多使用了MVP模式的android 项目,基本上都是将activity和fragment作为视图层来进行处理的.而presenters通常是通过继承自被视图层实例化或者注入的对象来得到的. 诚然,我同意说,这种方式可以节省掉很多让人厌烦的”import android..“语句, 并且将presenters从activity的生命周期中分割出来以后, 项目后续的维护会变得简便很多.这种思路是正确的, 但是,从另一个角度来说, activity 有一个很复杂的生命周期(fragment的生命周期可能会更复杂), 而这些生命周期很有可能对你项目的业务逻辑有非常重大的影响. Activity 可以获取上下文环境和多种android系统服务. Activity中发送Intent,启动Service和执行FragmentTransisitons等。而这些特性在我看来绝不应该是视图层应该涉及的领域(视图的功能就是现实数据和从用户那里获取输入数据,在理想的情况下,视图应该避免业务务逻辑),Model只关注基础数据. - 给个简单的实例 + 使用Activity和Fragment作为presenters的步骤 1. 去除所有的view将Activity和Fragment作为presenter最大的困难就是如何将关于UI的逻辑抽取出来.我的解决方案是: 让需要作为presenter的activity 或者 fragment来继承一个抽象的类(或者叫”基类--》BasePresenter xxx”), presenters就能持有Vu的引用,在ndroid对应的生命周期钩子中对View的初始化(各种组件的初始化)、销毁等工作进行控制,持有[EventBus](http://greenrobot.org/eventbus/documentation/how-to-get-started/),来控制整个应用的事件行为,下面是具体的代码: ```java public interface Vu { void init(LayoutInflater inflater, ViewGroup container); View getView(); } ``` 正如你所见,Vu定义了一个View的通用的初始化例程,我可以通过它来实现一个容器视图,它也有一个方法来获得一个View的实例,每一个presenter将会和它自己的Vu关联,这个presenter将会继承这个接口(直接或者间接的去继承一个来自Vu的接口,如:`public class MainActivity extends BasePresenterActivity `) 2. 创建一个presenter基类 (Activity)有了Vu接口,我们可以通过构建一系列的class来操纵很多不同的view组件,这些class 使用Vu接口来初始化View组件,并通过继承的方式给子类以操纵view组件的方法,以此来达到将ui 逻辑剥离出activity的目的。在下面的代码中,你可以看到,我覆写了activity的onCreate 、 onCreateView、onDestroy 、 onDestroyView,通过对这些方法的覆写,就可以对Vu的实例化和销毁进行精确的控制(vu.init()就是实例化一个view组件)。onBindVu() 和onDestoryVu()是控制view生命周期的两个方法。通过对actiivty中相关方法的覆写达到控制组件的生命周期的目的(具体看下面的代码,你就明白了), 这样做的好处就是无论是activity 还是 fragment, 其用与控制view组件创建和销毁的语句是一样的(尽量避免定义多余的函数)。这样的话,二者之间的切换也会减少一定的阻力。 ```java public abstract class BasePresenterActivity extends Activity { protected V vu; @Override protected final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { vu = getVuClass().newInstance(); vu.init(getLayoutInflater(), null); setContentView(vu.getView()); onBindVu(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override protected final void onDestroy() { onDestroyVu(); vu = null; super.onDestroy(); } protected abstract Class getVuClass(); protected void onBindVu(){}; protected void onDestroyVu() {}; } ``` 3. 创建一个基本的presenter(Fragment) ```java public abstract class BasePresenterFragment extends Fragment { protected V vu; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = null; try { vu = getVuClass().newInstance(); vu.init(inflater, container); onBindVu(); view = vu.getView(); } catch (java.lang.InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return view; } @Override public final void onDestroyView() { onDestroyVu(); vu = null; super.onDestroyView(); } protected void onDestroyVu() {}; protected void onBindVu(){}; protected abstract Class getVuClass(); } ``` 4. 具体实现: ```java public class HelloVu implements Vu { View view; TextView helloView; @Override public void init(LayoutInflater inflater, ViewGroup container) { view = inflater.inflate(R.layout.hello, container, false); helloView = (TextView) view.findViewById(R.id.hello); } @Override public View getView() { return view; } public void setHelloMessage(String msg){ helloView.setText(msg); } } ``` 下一步,创建一个presenter来操作这个TextView ```java public class HelloActivity extends BasePresenterActivity { @Override protected void onBindVu() { vu.setHelloMessage("Hello World!"); } @Override protected Class getVuClass() { return HelloVu.class; } } ``` OK,这样就大功告成了!!是不是很简便! 加入泛型之后,Presenter看起来应该是如下这样的 : ```java public class HelloActivity extends BasePresenterActivity { @Override protected void onBindVu() { vu.setHelloMessage("Hello World!"); } @Override protected Class getVuClass() { return HelloVuImpl.class; } } ``` 5. 如何进行测试 通过以上几步,我们可以发现,去除了UI逻辑之后,Activity变得非常简洁。并且,相关的测试 也变的非常异常的简单。请看如下的代码: ```java public class HelloActivityTest { HelloActivity activity; HelloVu vu; @Before public void setup() throws Exception { activity = new HelloActivity(); vu = Mockito.mock(HelloVu.class); activity.vu = vu; } @Test public void testOnBindVu(){ activity.onBindVu(); verify(vu).setHelloMessage("Hello World!"); } } ``` 以上代码是一段标准的jnuit单元测试的代码,不需要在android设备中部署运行,只需要在编译环境中即可测试。大幅度的提高了测试效率。但是,在测试某些方法的时候,你必须要使用android设备,例如当你想测试activity生命周期中的resume()方法。在缺乏设备环境的时候,super.resume()会报错。为了解决这个问题,可以借鉴一些工具,例如Robolectric、还有android gradle 1.1 插件中内置的testOptions { unitTests.returnDefaultValues = true }。此外,你仍然可以将这些生命周期也抽离出来。例如如下: ```java @Override protected final void onResume() { super.onResume(); afterResume(); } protected void afterResume(){} ``` 现在,你可以在没有android设备的情况下,快速的测试了! 另外:使用adapter作为presenter ```java public abstract class BasePresenterAdapter extends BaseAdapter { protected V vu; @Override public final View getView(int position, View convertView, ViewGroup parent) { if(convertView == null) { LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); try { vu = (V) getVuClass().newInstance(); vu.init(inflater, parent); convertView = vu.getView(); convertView.setTag(vu); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } else { vu = (V) convertView.getTag(); } if(convertView!=null) { onBindListItemVu(position); } return convertView; } protected abstract void onBindListItemVu(int position); protected abstract Class getVuClass(); } ``` 如上代码,使用adapter作为presenter其实和activity或者fragement几乎是一样的,只有一点明显的区别就是,我把onBingVu替换成了onBindListItemVu(接受int参数),其实我是借鉴了ViewHolder模式。 + 应用兼容手机和平板,并逐步结合[material design](https://developer.android.com/design/index.html) + 截图 ![手机](http://7xrmmo.com1.z0.glb.clouddn.com/2016-06-08-14653802841420.jpg) ![平板](http://7xrmmo.com1.z0.glb.clouddn.com/2016-06-08-14653803123702.jpg) + ide,使用官网最新(将会不断同步) + 支持的SDK即类库版本号 ```gradle ext {
 // Sdk and tools
 minSdkVersion = 10
 targetSdkVersion = 22
 compileSdkVersion = 23
 buildToolsVersion = '23.0.2'

 // App dependencies
 supportLibraryVersion = '23.4.0'
 guavaVersion = '18.0'
 evenbusVersion = '3.0.0'
 junitVersion = '4.12'
 mockitoVersion = '1.10.19'
 powerMockito = '1.6.2'
 hamcrestVersion = '1.3'
 runnerVersion = '0.4.1'
 rulesVersion = '0.4.1'
 espressoVersion = '2.2.1'
} ``` > 参考资料(以上理论出处) > http://www.devtf.cn/?p=567 > http://www.devtf.cn/?p=27 > http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=403539764&idx=1&sn=d30d89e6848a8e13d4da0f5639100e5f&scene=2&srcid=060658bgM5lRZDHNrlJxxIwO&from=timeline&isappinstalled=0#wechat_redirect > 项目在码云的地址:[ScreenFit](http://git.oschina.net/bankscene/ScreenFit/) > 目前项目还在开发,之后将间断更新,很久没有接触Android新知识之后,上周突然发现很多新奇的东西,如[EventBus](http://greenrobot.org/eventbus/)/[RxAndroid](https://github.com/ReactiveX/RxAndroid/wiki)/[RoboGuice](https://github.com/roboguice/roboguice/wiki)等等,这些东西随后如果个人能hold住,都会考虑引入到这个案例,最后如果觉得对你有所帮助,请给个star❤