├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── lsj
│ │ └── mvpdemo
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── lsj
│ │ │ └── mvpdemo
│ │ │ ├── api
│ │ │ └── Api.java
│ │ │ ├── base
│ │ │ ├── BaseMVPActivity.java
│ │ │ ├── BasePresenter.java
│ │ │ └── MultiplePresenter.java
│ │ │ ├── bean
│ │ │ ├── ArticleListBean.java
│ │ │ └── BannerBean.java
│ │ │ ├── contract
│ │ │ ├── MultipleInterfaceContract.java
│ │ │ └── SingleInterfaceContract.java
│ │ │ ├── interfaces
│ │ │ └── Callback.java
│ │ │ ├── model
│ │ │ ├── IModel.java
│ │ │ ├── IMultipleInterfaceModel.java
│ │ │ ├── ISingleInterfaceModel.java
│ │ │ ├── MultipleInterfaceModel.java
│ │ │ └── SingleInterfaceModel.java
│ │ │ ├── presenter
│ │ │ ├── IPresenter.java
│ │ │ ├── MultipleInterfacePresenter.java
│ │ │ └── SingleInterfacePresenter.java
│ │ │ ├── utils
│ │ │ ├── LP.java
│ │ │ └── NetUtils.java
│ │ │ └── view
│ │ │ ├── IView.java
│ │ │ ├── MainActivity.java
│ │ │ ├── MultipleInterfaceActivity.java
│ │ │ └── SingleInterfaceActivity.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_multiple_interface.xml
│ │ └── activity_single_interface.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── lsj
│ └── mvpdemo
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshot
├── mvc.jpg
├── mvp.jpg
├── 单界面单网络请求接口.gif
├── 单界面多网络请求接口.gif
├── 文章目录.png
└── 目录.jpg
└── settings.gradle
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # IntelliJ
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/assetWizardSettings.xml
41 | .idea/dictionaries
42 | .idea/libraries
43 | .idea/caches
44 |
45 | # Keystore files
46 | # Uncomment the following line if you do not want to check your keystore files in.
47 | #*.jks
48 |
49 | # External native build folder generated in Android Studio 2.2 and later
50 | .externalNativeBuild
51 |
52 | # Google Services (e.g. APIs or Firebase)
53 | google-services.json
54 |
55 | # Freeline
56 | freeline.py
57 | freeline/
58 | freeline_project_description.json
59 |
60 | # fastlane
61 | fastlane/report.xml
62 | fastlane/Preview.html
63 | fastlane/screenshots
64 | fastlane/test_output
65 | fastlane/readme.md
66 | .idea/codeStyles/
67 | .idea/inspectionProfiles/
68 | .idea/markdown-navigator/
69 | .idea/misc.xml
70 | .idea/modules.xml
71 | .idea/runConfigurations.xml
72 | .idea/vcs.xml
73 |
74 | .idea/markdown-navigator.xml
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Android MVP架构从入门到精通-真枪实弹
4 |
5 | 
6 |
7 | #### 一. 前言
8 |
9 | 你是否遇到过Activity/Fragment中成百上千行代码,完全无法维护,看着头疼?
10 |
11 | 你是否遇到过因后台接口还未写而你不能先写代码逻辑的情况?
12 |
13 | 你是否遇到过用MVC架构写的项目进行单元测试时的深深无奈?
14 |
15 | 如果你现在还是用MVC架构模式在写项目,请先转到MVP模式!
16 |
17 | #### 二. MVC架构
18 |
19 | MVC架构模式最初生根于服务器端的Web开发,后来渐渐能够胜任客户端Web开发,再后来因Android项目由XML和Activity/Fragment组成,慢慢的Android开发者开始使用类似MVC的架构模式开发应用.
20 |
21 | 
22 |
23 | M层:模型层(model),主要是实体类,数据库,网络等存在的层面,model将新的数据发送到view层,用户得到数据响应.
24 |
25 | V层:视图层(view),一般指XML为代表的视图界面.显示来源于model层的数据.用户的点击操作等事件从view层传递到controller层.
26 |
27 | C层:控制层(controller),一般以Activity/Fragment为代表.C层主要是连接V层和M层的,C层收到V层发送过来的事件请求,从M层获取数据,展示给V层.
28 |
29 | 从上图可以看出M层和V层有连接关系,而Activity有时候既充当了控制层又充当了视图层,导致项目维护比较麻烦.
30 |
31 | ##### 1. MVC架构优缺点
32 |
33 | ###### A. 缺点
34 |
35 | 1. M层和V层有连接关系,没有解耦,导致维护困难.
36 |
37 | 2. Activity/Fragment中的代码过多,难以维护.
38 |
39 | Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,当Activity类业务过多的时候,会变得难以管理和维护.尤其是当UI的状态数据,跟持久化的数据混杂在一起,变得极为混乱.
40 |
41 | ###### B. 优点
42 |
43 | 1. 控制层和View层都在Activity中进行操作,数据操作方便.
44 |
45 | 2. 模块职责划分明确.主要划分层M,V,C三个模块.
46 |
47 | #### 三. MVP架构
48 |
49 | 
50 |
51 | MVP,即是Model,View,Presenter架构模式.看起来类似MVC,其实不然.从上图能看到Model层和View层没有相连接,完全解耦.
52 |
53 | 用户触碰界面触发事件,View层把事件通知Presenter层,Presenter层通知Model层处理这个事件,Model层处理后把结果发送到Presenter层,Presenter层再通知View层,最后View层做出改变.这是一整套流程.
54 |
55 | M层:模型层(Model),此层和MVC中的M层作用类似.
56 |
57 | V层:视图层(View),在MVC中V层只包含XML文件,而MVP中V层包含XML,Activity和Fragment三者.理论上V层不涉及任何逻辑,只负责界面的改变,尽量把逻辑处理放到M层.
58 |
59 | P层:通知层(Presenter),P层的主要作用就是连接V层和M层,起到一个通知传递数据的作用.
60 |
61 | ##### 1. MVP架构优缺点
62 |
63 | ###### A. 缺点
64 |
65 | 1. MVP中接口过多.
66 |
67 | 2. 每一个功能,相比于MVC要多写好几个文件.
68 |
69 | 3. 如果某一个界面中需要请求多个服务器接口,这个界面文件中会实现很多的回调接口,导致代码繁杂.
70 |
71 | 4. 如果更改了数据源和请求中参数,会导致更多的代码修改.
72 |
73 | 5. 额外的代码复杂度及学习成本.
74 |
75 | ###### B. 优点
76 |
77 | 1. 模块职责划分明显,层次清晰,接口功能清晰.
78 |
79 | 2. Model层和View层分离,解耦.修改View而不影响Model.
80 |
81 | 3. 功能复用度高,方便.一个Presenter可以复用于多个View,而不用更改Presenter的逻辑.
82 |
83 | 4. 有利于测试驱动开发,以前的Android开发是难以进行单元测试.
84 |
85 | 5. 如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能.
86 |
87 | #### 四. MVP架构实战(真枪实弹)
88 |
89 | ##### 1. MVP三层代码简单书写
90 |
91 | 接下来笔者从简到繁,一点一点的堆砌MVP的整个架构.先看一下XML布局,布局中一个Button按钮和一个TextView控件,用户点击按钮后,Presenter层通知Model层请求处理网络数据,处理后Model层把结果数据发送给Presenter层,Presenter层再通知View层,然后View层改变TextView显示的内容.
92 |
93 |
94 | 
95 |
96 | ```
97 |
98 |
106 |
107 |
112 |
113 |
119 |
120 | ```
121 | 接下来是Activity代码,里面就是获取Button和TextView控件,然后对Button做监听,先简单的这样写,一会慢慢的增加代码.
122 |
123 | ```
124 | public class SingleInterfaceActivity extends AppCompatActivity {
125 |
126 | private Button button;
127 | private TextView textView;
128 |
129 | @Override
130 | protected void onCreate(Bundle savedInstanceState) {
131 | super.onCreate(savedInstanceState);
132 | setContentView(R.layout.activity_single_interface);
133 | button = findViewById(R.id.button);
134 | textView = findViewById(R.id.textView);
135 |
136 | button.setOnClickListener(new View.OnClickListener() {
137 | @Override
138 | public void onClick(View v) {
139 |
140 | }
141 | });
142 |
143 | }
144 | }
145 | ```
146 | 下面是Model层代码.本次网络请求用的是wanandroid网站的开放api,其中的文章首页列表接口.SingleInterfaceModel文件里面有一个方法getData,第一个参数curPage意思是获取第几页的数据,第二个参数callback是Model层通知Presenter层的回调.
147 |
148 | ```
149 | public class SingleInterfaceModel {
150 |
151 | public void getData(int curPage, final Callback callback) {
152 | NetUtils.getRetrofit()
153 | .create(Api.class)
154 | .getData(curPage)
155 | .subscribeOn(Schedulers.io())
156 | .observeOn(AndroidSchedulers.mainThread())
157 | .subscribe(new Subscriber() {
158 | @Override
159 | public void onCompleted() {
160 | LP.w("completed");
161 | }
162 |
163 | @Override
164 | public void onError(Throwable e) {
165 | callback.onFail("出现错误");
166 | }
167 |
168 | @Override
169 | public void onNext(ArticleListBean bean) {
170 | if (null == bean) {
171 | callback.onFail("出现错误");
172 | } else if (bean.errorCode != 0) {
173 | callback.onFail(bean.errorMsg);
174 | } else {
175 | callback.onSuccess(bean);
176 | }
177 | }
178 | });
179 | }
180 | }
181 | ```
182 | Callback文件内容如下.里面一个成功一个失败的回调接口,参数全是泛型,为啥使用泛型笔者就不用说了吧.
183 |
184 | ```
185 | public interface Callback {
186 | void onSuccess(K data);
187 |
188 | void onFail(V data);
189 | }
190 | ```
191 | 再接下来是Presenter层的代码.SingleInterfacePresenter类构造函数中直接new了一个Model层对象,用于Presenter层对Model层的调用.然后SingleInterfacePresenter类的方法getData用于与Model的互相连接.
192 |
193 | ```
194 | public class SingleInterfacePresenter {
195 | private final SingleInterfaceModel singleInterfaceModel;
196 |
197 | public SingleInterfacePresenter() {
198 | this.singleInterfaceModel = new SingleInterfaceModel();
199 | }
200 |
201 | public void getData(int curPage) {
202 | singleInterfaceModel.getData(curPage, new Callback() {
203 | @Override
204 | public void onSuccess(ArticleListBean loginResultBean) {
205 | //如果Model层请求数据成功,则此处应执行通知View层的代码
206 |
207 | }
208 |
209 | @Override
210 | public void onFail(String errorMsg) {
211 | //如果Model层请求数据失败,则此处应执行通知View层的代码
212 |
213 | }
214 | });
215 | }
216 | }
217 | ```
218 | 至此,MVP三层简单的部分代码算是完成.那么怎样进行整个流程的相互调用呢.我们把刚开始的SingleInterfaceActivity代码改一下,让SingleInterfaceActivity持有Presenter层的对象,这样View层就可以调用Presenter层了.修改后代码如下.
219 |
220 | ```
221 | public class SingleInterfaceActivity extends AppCompatActivity {
222 |
223 | private Button button;
224 | private TextView textView;
225 | private SingleInterfacePresenter singleInterfacePresenter;
226 |
227 | @Override
228 | protected void onCreate(Bundle savedInstanceState) {
229 | super.onCreate(savedInstanceState);
230 | setContentView(R.layout.activity_single_interface);
231 | button = findViewById(R.id.button);
232 | textView = findViewById(R.id.textView);
233 |
234 | singleInterfacePresenter = new SingleInterfacePresenter();
235 | button.setOnClickListener(new View.OnClickListener() {
236 | @Override
237 | public void onClick(View v) {
238 | singleInterfacePresenter.getData(0);
239 | }
240 | });
241 |
242 | }
243 | }
244 | ```
245 |
246 | 从以上所有代码可以看出,当用户点击按钮后,View层按钮的监听事件执行调用了Presenter层对象的getData方法,此时,Presenter层对象的getData方法调用了Model层对象的getData方法,Model层对象的getData方法中执行了网络请求和逻辑处理,把成功或失败的结果通过Callback接口回调给了Presenter层,然后Presenter层再通知View层改变界面.但此时SingleInterfacePresenter类中收到Model层的结果后无法通知View层,因为SingleInterfacePresenter未持有View层的对象.如下代码的注释中有说明.(如果此时点击按钮,下方代码LP.w()处会打印出网络请求成功的log)
247 |
248 | ```
249 | public class SingleInterfacePresenter {
250 | private final SingleInterfaceModel singleInterfaceModel;
251 |
252 | public SingleInterfacePresenter() {
253 | this.singleInterfaceModel = new SingleInterfaceModel();
254 | }
255 |
256 | public void getData(int curPage) {
257 | singleInterfaceModel.getData(curPage, new Callback() {
258 | @Override
259 | public void onSuccess(ArticleListBean loginResultBean) {
260 | //如果Model层请求数据成功,则此处应执行通知View层的代码
261 | //LP.w()是一个简单的log打印
262 | LP.w(loginResultBean.toString());
263 | }
264 |
265 | @Override
266 | public void onFail(String errorMsg) {
267 | //如果Model层请求数据失败,则此处应执行通知View层的代码
268 |
269 | }
270 | });
271 | }
272 | }
273 | ```
274 | 代码写到这里,笔者先把这些代码提交到github([https://github.com/serge66/MVPDemo](https://github.com/serge66/MVPDemo)),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"*第一次修改*"克隆此时的代码.
275 |
276 |
277 | ##### 2. P层V层沟通桥梁
278 |
279 | 现在P层未持有V层对象,不能通知V层改变界面,那么就继续演变MVP架构.
280 | 在MVP架构中,我们要为每个Activity/Fragment写一个接口,这个接口需要让Presenter层持有,P层通过这个接口去通知V层更改界面.接口中包含了成功和失败的回调,这个接口Activity/Fragment要去实现,最终P层才能通知V层.
281 |
282 | ```
283 | public interface SingleInterfaceIView {
284 | void showArticleSuccess(ArticleListBean bean);
285 |
286 | void showArticleFail(String errorMsg);
287 | }
288 | ```
289 |
290 | 一个完整的项目以后肯定会有许多功能界面,那么我们应该抽出一个IView公共接口,让所有的Activity/Fragment都间接实现它.IVew公共接口是用于给View层的接口继承的,注意,不是View本身继承.因为它定义的是接口的规范, 而其他接口才是定义的类的规范(这句话请仔细理解).
291 |
292 | ```
293 | /**
294 | * @Description: 公共接口 是用于给View的接口继承的,注意,不是View本身继承。
295 | * 因为它定义的是接口的规范, 而其他接口才是定义的类的规范
296 | * @Author: lishengjiejob@163.com
297 | * @Time: 2018/11/22 17:26
298 | */
299 | public interface IView {
300 | }
301 | ```
302 | 这个接口中可以写一些所有Activigy/Fragment共用的方法,我们把SingleInterfaceIView继承IView接口.
303 |
304 | ```
305 | public interface SingleInterfaceIView extends IView {
306 | void showArticleSuccess(ArticleListBean bean);
307 |
308 | void showArticleFail(String errorMsg);
309 | }
310 | ```
311 | 同理Model层和Presenter层也是如此.
312 |
313 | ```
314 | public interface IModel {
315 | }
316 | ```
317 |
318 | ```
319 | public interface IPresenter {
320 | }
321 | ```
322 | 现在项目中Model层是一个SingleInterfaceModel类,这个类对象被P层持有,对于面向对象设计来讲,利用接口达到解耦目的已经人尽皆知,那我们就要对SingleInterfaceModel类再写一个可继承的接口.代码如下.
323 |
324 | ```
325 | public interface ISingleInterfaceModel extends IModel {
326 | void getData(int curPage, final Callback callback);
327 | }
328 | ```
329 | 如此,SingleInterfaceModel类的修改如下.
330 |
331 | ```
332 | public class SingleInterfaceModel implements ISingleInterfaceModel {
333 |
334 | @Override
335 | public void getData(int curPage, final Callback callback) {
336 | NetUtils.getRetrofit()
337 | .create(Api.class)
338 | .getData(curPage)
339 | .subscribeOn(Schedulers.io())
340 | .observeOn(AndroidSchedulers.mainThread())
341 | .subscribe(new Subscriber() {
342 | @Override
343 | public void onCompleted() {
344 | LP.w("completed");
345 | }
346 |
347 | @Override
348 | public void onError(Throwable e) {
349 | callback.onFail("出现错误");
350 | }
351 |
352 | @Override
353 | public void onNext(ArticleListBean bean) {
354 | if (null == bean) {
355 | callback.onFail("出现错误");
356 | } else if (bean.errorCode != 0) {
357 | callback.onFail(bean.errorMsg);
358 | } else {
359 | callback.onSuccess(bean);
360 | }
361 | }
362 | });
363 | }
364 | }
365 | ```
366 |
367 | 同理,View层持有P层对象,我们也需要对P层进行改造.但是下面的代码却没有像ISingleInterfaceModel接口继承IModel一样继承IPresenter,这点需要注意,笔者把IPresenter的继承放在了其他处,后面会讲解.
368 |
369 | ```
370 | public interface ISingleInterfacePresenter {
371 | void getData(int curPage);
372 | }
373 |
374 | ```
375 |
376 | 然后SingleInterfacePresenter类的修改如下:
377 |
378 | ```
379 | public class SingleInterfacePresenter implements ISingleInterfacePresenter {
380 | private final ISingleInterfaceModel singleInterfaceModel;
381 |
382 | public SingleInterfacePresenter() {
383 | this.singleInterfaceModel = new SingleInterfaceModel();
384 | }
385 |
386 | @Override
387 | public void getData(int curPage) {
388 | singleInterfaceModel.getData(curPage, new Callback() {
389 | @Override
390 | public void onSuccess(ArticleListBean loginResultBean) {
391 | //如果Model层请求数据成功,则此处应执行通知View层的代码
392 | //LP.w()是一个简单的log打印
393 | LP.w(loginResultBean.toString());
394 | }
395 |
396 | @Override
397 | public void onFail(String errorMsg) {
398 | //如果Model层请求数据失败,则此处应执行通知View层的代码
399 | LP.w(errorMsg);
400 | }
401 | });
402 | }
403 | }
404 | ```
405 | ##### 3. 生命周期适配
406 | 至此,MVP三层每层的接口都写好了.但是P层连接V层的桥梁还没有搭建好,这个慢慢来,一个好的高楼大厦都是一步一步建造的.上面IPresenter接口我们没有让其他类继承,接下来就讲下这个.P层和V层相连接,V层的生命周期也要适配到P层,P层的每个功能都要适配生命周期,这里可以把生命周期的适配放在IPresenter接口中.P层持有V层对象,这里把它放到泛型中.代码如下.
407 |
408 | ```
409 | public interface IPresenter {
410 |
411 | /**
412 | * 依附生命view
413 | *
414 | * @param view
415 | */
416 | void attachView(T view);
417 |
418 | /**
419 | * 分离View
420 | */
421 | void detachView();
422 |
423 | /**
424 | * 判断View是否已经销毁
425 | *
426 | * @return
427 | */
428 | boolean isViewAttached();
429 |
430 | }
431 | ```
432 |
433 | 这个IPresenter接口需要所有的P层实现类继承,对于生命周期这部分功能都是通用的,那么就可以抽出来一个抽象基类BasePresenter,去实现IPresenter的接口.
434 |
435 | ```
436 | public abstract class BasePresenter implements IPresenter {
437 | protected T mView;
438 |
439 | @Override
440 | public void attachView(T view) {
441 | mView = view;
442 | }
443 |
444 | @Override
445 | public void detachView() {
446 | mView = null;
447 | }
448 |
449 | @Override
450 | public boolean isViewAttached() {
451 | return mView != null;
452 | }
453 | }
454 |
455 | ```
456 |
457 | 此时,SingleInterfacePresenter类的代码修改如下.泛型中的SingleInterfaceIView可以理解成对应的Activity,P层此时完成了对V层的通信.
458 |
459 | ```
460 | public class SingleInterfacePresenter extends BasePresenter implements ISingleInterfacePresenter {
461 | private final ISingleInterfaceModel singleInterfaceModel;
462 |
463 | public SingleInterfacePresenter() {
464 | this.singleInterfaceModel = new SingleInterfaceModel();
465 | }
466 |
467 | @Override
468 | public void getData(int curPage) {
469 | singleInterfaceModel.getData(curPage, new Callback() {
470 | @Override
471 | public void onSuccess(ArticleListBean loginResultBean) {
472 | //如果Model层请求数据成功,则此处应执行通知View层的代码
473 | //LP.w()是一个简单的log打印
474 | LP.w(loginResultBean.toString());
475 | if (isViewAttached()) {
476 | mView.showArticleSuccess(loginResultBean);
477 | }
478 | }
479 |
480 | @Override
481 | public void onFail(String errorMsg) {
482 | //如果Model层请求数据失败,则此处应执行通知View层的代码
483 | LP.w(errorMsg);
484 | if (isViewAttached()) {
485 | mView.showArticleFail(errorMsg);
486 | }
487 | }
488 | });
489 | }
490 | }
491 | ```
492 |
493 |
494 |
495 | 此时,P层和V层的连接桥梁已经搭建,但还未搭建完成,我们需要写个BaseMVPActvity让所有的Activity继承,统一处理Activity相同逻辑.在BaseMVPActvity中使用IPresenter的泛型,因为每个Activity中需要持有P层对象,这里把P层对象抽出来也放在BaseMVPActvity中.同时BaseMVPActvity中也需要继承IView,用于P层对V层的生命周期中.代码如下.
496 |
497 | ```
498 | public abstract class BaseMVPActivity extends AppCompatActivity implements IView {
499 |
500 | protected T mPresenter;
501 |
502 | @Override
503 | protected void onCreate(@Nullable Bundle savedInstanceState) {
504 | super.onCreate(savedInstanceState);
505 | initPresenter();
506 | init();
507 | }
508 |
509 | protected void initPresenter() {
510 | mPresenter = createPresenter();
511 | //绑定生命周期
512 | if (mPresenter != null) {
513 | mPresenter.attachView(this);
514 | }
515 | }
516 |
517 | @Override
518 | protected void onDestroy() {
519 | if (mPresenter != null) {
520 | mPresenter.detachView();
521 | }
522 | super.onDestroy();
523 | }
524 |
525 | /**
526 | * 创建一个Presenter
527 | *
528 | * @return
529 | */
530 | protected abstract T createPresenter();
531 |
532 | protected abstract void init();
533 |
534 | }
535 | ```
536 |
537 | 接下来让SingleInterfaceActivity实现这个BaseMVPActivity.
538 |
539 | ```
540 | public class SingleInterfaceActivity extends BaseMVPActivity implements SingleInterfaceIView {
541 |
542 | private Button button;
543 | private TextView textView;
544 |
545 | @Override
546 | protected void init() {
547 | setContentView(R.layout.activity_single_interface);
548 | button = findViewById(R.id.button);
549 | textView = findViewById(R.id.textView);
550 |
551 | button.setOnClickListener(new View.OnClickListener() {
552 | @Override
553 | public void onClick(View v) {
554 | mPresenter.getData(0);
555 | }
556 | });
557 | }
558 |
559 | @Override
560 | protected SingleInterfacePresenter createPresenter() {
561 | return new SingleInterfacePresenter();
562 | }
563 |
564 |
565 | @Override
566 | public void showArticleSuccess(ArticleListBean bean) {
567 | textView.setText(bean.data.datas.get(0).title);
568 | }
569 |
570 | @Override
571 | public void showArticleFail(String errorMsg) {
572 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
573 | }
574 | }
575 |
576 | ```
577 |
578 | 到此,MVP架构的整个简易流程完成.
579 |
580 | 代码写到这里,笔者先把这些代码提交到github([https://github.com/serge66/MVPDemo](https://github.com/serge66/MVPDemo)),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"*第二次修改*"克隆此时的代码.
581 |
582 |
583 | ##### 4. 优化MVP架构
584 |
585 |
586 | 
587 |
588 | 上面是MVP的目录,从目录中我们可以看到一个功能点(网络请求)MVP三层各有两个文件需要写,相对于MVC来说写起来确实麻烦,这也是一些人不愿意写MVP,宁愿用MVC的原因.
589 |
590 | 这里我们可以对此优化一下.MVP架构中有个Contract的概念,Contract有统一管理接口的作用,目的是为了统一管理一个页面的View和Presenter接口,用Contract可以减少部分文件的创建,比如P层和V层的接口文件.
591 |
592 | 那我们就把P层的ISingleInterfacePresenter接口和V层的SingleInterfaceIView接口文件删除掉,放入SingleInterfaceContract文件中.代码如下.
593 |
594 |
595 | ```
596 | public interface SingleInterfaceContract {
597 |
598 |
599 | interface View extends IView {
600 | void showArticleSuccess(ArticleListBean bean);
601 |
602 | void showArticleFail(String errorMsg);
603 | }
604 |
605 | interface Presenter {
606 | void getData(int curPage);
607 | }
608 |
609 |
610 | }
611 | ```
612 | 此时,SingleInterfacePresenter和SingleInterfaceActivity的代码修改如下.
613 |
614 | ```
615 | public class SingleInterfacePresenter extends BasePresenter
616 | implements SingleInterfaceContract.Presenter {
617 |
618 | private final ISingleInterfaceModel singleInterfaceModel;
619 |
620 | public SingleInterfacePresenter() {
621 | this.singleInterfaceModel = new SingleInterfaceModel();
622 | }
623 |
624 | @Override
625 | public void getData(int curPage) {
626 | singleInterfaceModel.getData(curPage, new Callback() {
627 | @Override
628 | public void onSuccess(ArticleListBean loginResultBean) {
629 | //如果Model层请求数据成功,则此处应执行通知View层的代码
630 | //LP.w()是一个简单的log打印
631 | LP.w(loginResultBean.toString());
632 | if (isViewAttached()) {
633 | mView.showArticleSuccess(loginResultBean);
634 | }
635 | }
636 |
637 | @Override
638 | public void onFail(String errorMsg) {
639 | //如果Model层请求数据失败,则此处应执行通知View层的代码
640 | LP.w(errorMsg);
641 | if (isViewAttached()) {
642 | mView.showArticleFail(errorMsg);
643 | }
644 | }
645 | });
646 | }
647 | }
648 | ```
649 |
650 | ```
651 | public class SingleInterfaceActivity extends BaseMVPActivity
652 | implements SingleInterfaceContract.View {
653 |
654 | private Button button;
655 | private TextView textView;
656 |
657 | @Override
658 | protected void init() {
659 | setContentView(R.layout.activity_single_interface);
660 | button = findViewById(R.id.button);
661 | textView = findViewById(R.id.textView);
662 |
663 | button.setOnClickListener(new View.OnClickListener() {
664 | @Override
665 | public void onClick(View v) {
666 | mPresenter.getData(0);
667 | }
668 | });
669 | }
670 |
671 | @Override
672 | protected SingleInterfacePresenter createPresenter() {
673 | return new SingleInterfacePresenter();
674 | }
675 |
676 |
677 | @Override
678 | public void showArticleSuccess(ArticleListBean bean) {
679 | textView.setText(bean.data.datas.get(0).title);
680 | }
681 |
682 | @Override
683 | public void showArticleFail(String errorMsg) {
684 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
685 | }
686 | }
687 |
688 | ```
689 | 代码写到这里,笔者先把这些代码提交到github([https://github.com/serge66/MVPDemo](https://github.com/serge66/MVPDemo)),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"*第三次修改*"克隆此时的代码.
690 |
691 | ##### 5. 单页面多网络请求(P层复用)
692 |
693 | 上面的MVP封装只适用于单页面一个网络请求的情况,当一个界面有两个网络请求时,此封装已不适合.为此,我们再次新建一个MultipleInterfaceActivity来进行说明.XML中布局是两个按钮两个Textview,点击则可以进行网络请求.
694 |
695 | 
696 |
697 | ```
698 |
699 |
707 |
708 |
713 |
714 |
720 |
721 |
727 |
728 |
734 |
735 | ```
736 |
737 | MultipleInterfaceActivity类代码暂时如下.
738 |
739 | ```
740 | public class MultipleInterfaceActivity extends BaseMVPActivity {
741 |
742 | private Button button;
743 | private TextView textView;
744 | private Button btn;
745 | private TextView tv;
746 |
747 |
748 | @Override
749 | protected void init() {
750 | setContentView(R.layout.activity_multiple_interface);
751 |
752 | button = findViewById(R.id.button);
753 | textView = findViewById(R.id.textView);
754 |
755 | button.setOnClickListener(new View.OnClickListener() {
756 | @Override
757 | public void onClick(View v) {
758 |
759 | }
760 | });
761 |
762 |
763 | btn = findViewById(R.id.btn);
764 | tv = findViewById(R.id.tv);
765 |
766 | btn.setOnClickListener(new View.OnClickListener() {
767 | @Override
768 | public void onClick(View v) {
769 |
770 | }
771 | });
772 | }
773 |
774 | @Override
775 | protected IPresenter createPresenter() {
776 | return null;
777 | }
778 |
779 | }
780 | ```
781 |
782 | 此时我们可以想下,当一个页面中有多个网络请求时,Activity所继承的BaseMVPActivity的泛型中要写多个参数,那有没有上面代码的框架不变的情况下实现这个需求呢?答案必须有的.我们可以把多个网络请求的功能当做一个网络请求来看待,封装成一个MultiplePresenter,其继承至BasePresenter实现生命周期的适配.此MultiplePresenter类的作用就是容纳多个Presenter,连接同一个View.代码如下.
783 |
784 | ```
785 | public class MultiplePresenter extends BasePresenter {
786 | private T mView;
787 |
788 | private List presenters = new ArrayList<>();
789 |
790 | @SafeVarargs
791 | public final > void addPresenter(K... addPresenter) {
792 | for (K ap : addPresenter) {
793 | ap.attachView(mView);
794 | presenters.add(ap);
795 | }
796 | }
797 |
798 | public MultiplePresenter(T mView) {
799 | this.mView = mView;
800 | }
801 |
802 | @Override
803 | public void detachView() {
804 | for (IPresenter presenter : presenters) {
805 | presenter.detachView();
806 | }
807 | }
808 |
809 | }
810 | ```
811 | 因MultiplePresenter类中需要有多个网络请求,现在举例说明时,暂时用两个网络请求接口.MultipleInterfaceActivity类中代码改造如下.
812 |
813 |
814 | ```
815 | public class MultipleInterfaceActivity extends BaseMVPActivity
816 | implements SingleInterfaceContract.View, MultipleInterfaceContract.View {
817 |
818 | private Button button;
819 | private TextView textView;
820 | private Button btn;
821 | private TextView tv;
822 | private SingleInterfacePresenter singleInterfacePresenter;
823 | private MultipleInterfacePresenter multipleInterfacePresenter;
824 |
825 |
826 | @Override
827 | protected void init() {
828 | setContentView(R.layout.activity_multiple_interface);
829 |
830 | button = findViewById(R.id.button);
831 | textView = findViewById(R.id.textView);
832 |
833 | button.setOnClickListener(new View.OnClickListener() {
834 | @Override
835 | public void onClick(View v) {
836 | singleInterfacePresenter.getData(0);
837 | }
838 | });
839 |
840 |
841 | btn = findViewById(R.id.btn);
842 | tv = findViewById(R.id.tv);
843 |
844 | btn.setOnClickListener(new View.OnClickListener() {
845 | @Override
846 | public void onClick(View v) {
847 | multipleInterfacePresenter.getBanner();
848 | }
849 | });
850 | }
851 |
852 | @Override
853 | protected MultiplePresenter createPresenter() {
854 | MultiplePresenter multiplePresenter = new MultiplePresenter(this);
855 |
856 | singleInterfacePresenter = new SingleInterfacePresenter();
857 | multipleInterfacePresenter = new MultipleInterfacePresenter();
858 |
859 | multiplePresenter.addPresenter(singleInterfacePresenter);
860 | multiplePresenter.addPresenter(multipleInterfacePresenter);
861 | return multiplePresenter;
862 | }
863 |
864 | @Override
865 | public void showArticleSuccess(ArticleListBean bean) {
866 | textView.setText(bean.data.datas.get(0).title);
867 | }
868 |
869 | @Override
870 | public void showArticleFail(String errorMsg) {
871 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
872 | }
873 |
874 | @Override
875 | public void showMultipleSuccess(BannerBean bean) {
876 | tv.setText(bean.data.get(0).title);
877 | }
878 |
879 | @Override
880 | public void showMultipleFail(String errorMsg) {
881 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
882 | }
883 | }
884 |
885 | ```
886 | 写到这里,MVP框架基本算是完成.如果想再次优化,其实还是有可优化的地方,比如当View销毁时,现在只是让P层中的View对象置为null,并没有继续对M层通知.如果View销毁时,M层还在请求网络中呢,可以为此再加入一个取消网络请求的通用功能.这里只是举一个例子,每个人对MVP的理解不一样,而MVP架构也并不是一成不变,适合自己项目的才是最好的.
887 |
888 | ##### 6. 完整项目地址
889 |
890 | 完整项目已提交到github([https://github.com/serge66/MVPDemo](https://github.com/serge66/MVPDemo)),若需要敬请查看.
891 |
892 |
893 |
894 | #### 五. 参考资料
895 |
896 | [一步步带你精通MVP](https://mp.weixin.qq.com/s/DuNbl3V4gZY-ZCETbhZGug)
897 | [从0到1搭建MVP框架](https://mp.weixin.qq.com/s/QFpHhC-5JkAb4IlMP0nKug)
898 | [Presenter层如何高度的复用](https://juejin.im/post/599ce8016fb9a0247e4255f4)
899 |
900 | #### 六. 后续
901 |
902 | <> 敬请期待~~~
903 |
904 | [原创文章,来自于Vitamio(http://blog.csdn.net/vitamio),转载请注明出处。](http://blog.csdn.net/vitamio)
905 |
906 | 文章同步发布于:
907 |
908 | CSDN https://blog.csdn.net/vitamio/article/details/84069427
909 |
910 | 掘金 https://juejin.im/post/5bf787d5e51d450c487d06dd
911 |
912 | 简书 https://www.jianshu.com/p/76c098652dba
913 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | # Built application files
3 | *.apk
4 | *.ap_
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 | # Android Studio Navigation editor temp files
31 | .navigation/
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # IntelliJ
37 | *.iml
38 | .idea/workspace.xml
39 | .idea/tasks.xml
40 | .idea/gradle.xml
41 | .idea/assetWizardSettings.xml
42 | .idea/dictionaries
43 | .idea/libraries
44 | .idea/caches
45 |
46 | # Keystore files
47 | # Uncomment the following line if you do not want to check your keystore files in.
48 | #*.jks
49 |
50 | # External native build folder generated in Android Studio 2.2 and later
51 | .externalNativeBuild
52 |
53 | # Google Services (e.g. APIs or Firebase)
54 | google-services.json
55 |
56 | # Freeline
57 | freeline.py
58 | freeline/
59 | freeline_project_description.json
60 |
61 | # fastlane
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 | fastlane/readme.md
67 | .idea/codeStyles/
68 | .idea/inspectionProfiles/
69 | .idea/markdown-navigator/
70 | .idea/misc.xml
71 | .idea/modules.xml
72 | .idea/runConfigurations.xml
73 | .idea/vcs.xml
74 |
75 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.example.lsj.mvpdemo"
7 | minSdkVersion 15
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | packagingOptions {
13 | exclude 'META-INF/rxjava.properties'
14 | }
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation fileTree(dir: 'libs', include: ['*.jar'])
26 | implementation 'com.android.support:appcompat-v7:28.0.0'
27 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
28 | testImplementation 'junit:junit:4.12'
29 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
30 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
31 |
32 | implementation 'com.squareup.retrofit2:retrofit:2.1.0'
33 | implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
34 | implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
35 | // implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
36 | implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
37 | implementation 'io.reactivex:rxandroid:1.2.1'
38 | implementation 'io.reactivex:rxjava:1.2.1'
39 | implementation('com.squareup.retrofit2:adapter-rxjava:2.1.0') {
40 | exclude group: 'io.reactivex'
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/lsj/mvpdemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.lsj.mvpdemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/api/Api.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.api;
2 |
3 | import com.example.lsj.mvpdemo.bean.ArticleListBean;
4 | import com.example.lsj.mvpdemo.bean.BannerBean;
5 |
6 | import retrofit2.http.GET;
7 | import retrofit2.http.Path;
8 | import rx.Observable;
9 |
10 | public interface Api {
11 |
12 | /**
13 | * wanandroid 首页文章列表
14 | *
15 | * @param curPage 当前第几页
16 | * @return
17 | */
18 | @GET("article/list/{curPage}/json")
19 | Observable getData(@Path("curPage") int curPage);
20 |
21 | /**
22 | * 获取首页banner数据
23 | *
24 | * @return
25 | */
26 | @GET("banner/json")
27 | Observable getBanner();
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/base/BaseMVPActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.base;
2 |
3 |
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.app.AppCompatActivity;
7 |
8 | import com.example.lsj.mvpdemo.presenter.IPresenter;
9 | import com.example.lsj.mvpdemo.view.IView;
10 |
11 | public abstract class BaseMVPActivity extends AppCompatActivity implements IView {
12 |
13 | protected T mPresenter;
14 |
15 | @Override
16 | protected void onCreate(@Nullable Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | initPresenter();
19 | init();
20 | }
21 |
22 | protected void initPresenter() {
23 | mPresenter = createPresenter();
24 | //绑定生命周期
25 | if (mPresenter != null) {
26 | mPresenter.attachView(this);
27 | }
28 | }
29 |
30 | @Override
31 | protected void onDestroy() {
32 | if (mPresenter != null) {
33 | mPresenter.detachView();
34 | }
35 | super.onDestroy();
36 | }
37 |
38 | /**
39 | * 创建一个Presenter
40 | *
41 | * @return
42 | */
43 | protected abstract T createPresenter();
44 |
45 | protected abstract void init();
46 |
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/base/BasePresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.base;
2 |
3 |
4 | import com.example.lsj.mvpdemo.presenter.IPresenter;
5 | import com.example.lsj.mvpdemo.view.IView;
6 |
7 | public abstract class BasePresenter implements IPresenter {
8 | protected T mView;
9 |
10 | @Override
11 | public void attachView(T view) {
12 | mView = view;
13 | }
14 |
15 | @Override
16 | public void detachView() {
17 | mView = null;
18 | }
19 |
20 | @Override
21 | public boolean isViewAttached() {
22 | return mView != null;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/base/MultiplePresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.base;
2 |
3 | import com.example.lsj.mvpdemo.presenter.IPresenter;
4 | import com.example.lsj.mvpdemo.view.IView;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * @Description: 单页面多网络请求时 presenter容器
11 | * @Author: lishengjiejob@163.com
12 | * @Time: 2018/11/22 21:14
13 | */
14 | public class MultiplePresenter extends BasePresenter {
15 | private T mView;
16 |
17 | private List presenters = new ArrayList<>();
18 |
19 | @SafeVarargs
20 | public final > void addPresenter(K... addPresenter) {
21 | for (K ap : addPresenter) {
22 | ap.attachView(mView);
23 | presenters.add(ap);
24 | }
25 | }
26 |
27 | public MultiplePresenter(T mView) {
28 | this.mView = mView;
29 | }
30 |
31 | @Override
32 | public void detachView() {
33 | for (IPresenter presenter : presenters) {
34 | presenter.detachView();
35 | }
36 | mView = null;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/bean/ArticleListBean.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.bean;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * @Description: wanandroid 首页文章列表
7 | * @Author: lishengjiejob@163.com
8 | * @Time: 2018/11/22 14:58
9 | */
10 | public class ArticleListBean {
11 |
12 | /**
13 | * data : {"curPage":1,"datas":[{"apkLink":"","author":"鸿洋","chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7545,"link":"https://mp.weixin.qq.com/s/1ua0geFnrbQbyHi8KG2VJQ","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542729600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"换肤、全局字体替换、无需编写shape、selector 的原理Factory小结","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7546,"link":"https://mp.weixin.qq.com/s/cnG66TKaiSbnAFShjOzQfw","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542729600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"自定义View:实现RecyclerView的item添加悬浮层的效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"code小生","chapterId":414,"chapterName":"code小生","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7547,"link":"https://mp.weixin.qq.com/s/gb2S1IMKTQFfPVqtzqT8rQ","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542729600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/414/1"}],"title":"Walle —— Android多渠道打包神器","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"旧城别爱","chapterId":93,"chapterName":"基础知识","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7543,"link":"https://www.jianshu.com/p/036bbe13eca9","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725771000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"仿饿了么商品列表页面","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"张风捷特烈","chapterId":93,"chapterName":"基础知识","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7542,"link":"https://www.jianshu.com/p/8308e72a57fe","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725749000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"Android绘图最终篇之大战贝塞尔三次曲线","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"JackMeGo","chapterId":268,"chapterName":"优秀的设计","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7541,"link":"https://www.jianshu.com/p/fda5cf1d3e6a","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725707000,"superChapterId":135,"superChapterName":"项目必备","tags":[],"title":"Android版结巴分词:原理、接入和启动优化","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"黄铭1991","chapterId":160,"chapterName":"热修复","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7540,"link":"https://www.jianshu.com/p/8cc516b9b932","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725644000,"superChapterId":79,"superChapterName":"热门专题","tags":[],"title":"2018 深入解析Android热修复技术","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"天星技术团队","chapterId":26,"chapterName":"基础UI控件","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7539,"link":"https://juejin.im/post/5be45876f265da616413820b","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725592000,"superChapterId":26,"superChapterName":"常用控件","tags":[],"title":"Android 最简单的限制输入方式之一","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"鸿洋","chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7537,"link":"https://mp.weixin.qq.com/s/jw3dme6GJ5V1YVu_cdeR3A","niceDate":"2天前","origin":"","projectLink":"","publishTime":1542643200000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"这效果炸了 图片粒子“爆炸”效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7538,"link":"https://mp.weixin.qq.com/s/l7Dg5TwfQCHZjjxMPJuAFw","niceDate":"2天前","origin":"","projectLink":"","publishTime":1542643200000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"Java 8,HashMap中的红黑树操作","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"JohnnyShieh","chapterId":424,"chapterName":"协程","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7527,"link":"https://www.jianshu.com/p/2979732fb6fb","niceDate":"2天前","origin":"","projectLink":"","publishTime":1542614335000,"superChapterId":232,"superChapterName":"Kotlin","tags":[],"title":"Kotlin Coroutines(协程) 完全解析(一),协程简介","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"谷歌开发者","chapterId":415,"chapterName":"谷歌开发者","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7528,"link":"https://mp.weixin.qq.com/s/yrD8zUcnUs3LmNNhtWn4fw","niceDate":"2018-11-19","origin":"","projectLink":"","publishTime":1542556800000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/415/1"}],"title":"App 生存与壮大的五条原则","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7531,"link":"https://mp.weixin.qq.com/s/XjyH7nYqccNSZOKrvdAyLw","niceDate":"2018-11-19","origin":"","projectLink":"","publishTime":1542556800000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"Ubuntu搭建Jenkins+Android自动化打包","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"code小生","chapterId":414,"chapterName":"code小生","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7533,"link":"https://mp.weixin.qq.com/s/rI_whEgWt88foGYwiBHG9Q","niceDate":"2018-11-19","origin":"","projectLink":"","publishTime":1542556800000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/414/1"}],"title":"Android开发之图像处理那点事——滤镜","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"xlh__","chapterId":99,"chapterName":"具体案例","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7526,"link":"https://www.jianshu.com/p/e94a444054b4","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542546987000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"自定义RecycleView下拉刷新","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"honglei92","chapterId":99,"chapterName":"具体案例","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7525,"link":"https://www.jianshu.com/p/76a8a5e88460","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542546693000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"TabLayout源码解析和仿简书首页TabLayout效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"simplehych ","chapterId":375,"chapterName":"Flutter","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7524,"link":"https://www.jianshu.com/p/1317aed6cd8c","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542546297000,"superChapterId":375,"superChapterName":"跨平台","tags":[],"title":"Flutter与Android混合开发及Platform Channel的使用","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"互联网侦察","chapterId":421,"chapterName":"互联网侦察","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7535,"link":"https://mp.weixin.qq.com/s/uPoruWUD-v_1YQf2KPO45w","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542470400000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/421/1"}],"title":"【面试现场】如何编程解决朋友圈个数问题?","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"鸿洋","chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7529,"link":"https://mp.weixin.qq.com/s/8yaGzZ2VFSDO9A5rwUrM-g","niceDate":"2018-11-16","origin":"","projectLink":"","publishTime":1542297600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"分享一下三年来公众号的小经验","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7530,"link":"https://mp.weixin.qq.com/s/rq3gTxASebJxW_6WcSa-GQ","niceDate":"2018-11-16","origin":"","projectLink":"","publishTime":1542297600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"插件化之代码调用与加载资源","type":0,"userId":-1,"visible":1,"zan":0}],"offset":0,"over":false,"pageCount":286,"size":20,"total":5702}
14 | * errorCode : 0
15 | * errorMsg :
16 | */
17 |
18 | public DataBean data;
19 | public int errorCode;
20 | public String errorMsg;
21 |
22 |
23 | public static class DataBean {
24 | /**
25 | * curPage : 1
26 | * datas : [{"apkLink":"","author":"鸿洋","chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7545,"link":"https://mp.weixin.qq.com/s/1ua0geFnrbQbyHi8KG2VJQ","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542729600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"换肤、全局字体替换、无需编写shape、selector 的原理Factory小结","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7546,"link":"https://mp.weixin.qq.com/s/cnG66TKaiSbnAFShjOzQfw","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542729600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"自定义View:实现RecyclerView的item添加悬浮层的效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"code小生","chapterId":414,"chapterName":"code小生","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7547,"link":"https://mp.weixin.qq.com/s/gb2S1IMKTQFfPVqtzqT8rQ","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542729600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/414/1"}],"title":"Walle —— Android多渠道打包神器","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"旧城别爱","chapterId":93,"chapterName":"基础知识","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7543,"link":"https://www.jianshu.com/p/036bbe13eca9","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725771000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"仿饿了么商品列表页面","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"张风捷特烈","chapterId":93,"chapterName":"基础知识","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7542,"link":"https://www.jianshu.com/p/8308e72a57fe","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725749000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"Android绘图最终篇之大战贝塞尔三次曲线","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"JackMeGo","chapterId":268,"chapterName":"优秀的设计","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7541,"link":"https://www.jianshu.com/p/fda5cf1d3e6a","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725707000,"superChapterId":135,"superChapterName":"项目必备","tags":[],"title":"Android版结巴分词:原理、接入和启动优化","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"黄铭1991","chapterId":160,"chapterName":"热修复","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7540,"link":"https://www.jianshu.com/p/8cc516b9b932","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725644000,"superChapterId":79,"superChapterName":"热门专题","tags":[],"title":"2018 深入解析Android热修复技术","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"天星技术团队","chapterId":26,"chapterName":"基础UI控件","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7539,"link":"https://juejin.im/post/5be45876f265da616413820b","niceDate":"1天前","origin":"","projectLink":"","publishTime":1542725592000,"superChapterId":26,"superChapterName":"常用控件","tags":[],"title":"Android 最简单的限制输入方式之一","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"鸿洋","chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7537,"link":"https://mp.weixin.qq.com/s/jw3dme6GJ5V1YVu_cdeR3A","niceDate":"2天前","origin":"","projectLink":"","publishTime":1542643200000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"这效果炸了 图片粒子“爆炸”效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7538,"link":"https://mp.weixin.qq.com/s/l7Dg5TwfQCHZjjxMPJuAFw","niceDate":"2天前","origin":"","projectLink":"","publishTime":1542643200000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"Java 8,HashMap中的红黑树操作","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"JohnnyShieh","chapterId":424,"chapterName":"协程","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7527,"link":"https://www.jianshu.com/p/2979732fb6fb","niceDate":"2天前","origin":"","projectLink":"","publishTime":1542614335000,"superChapterId":232,"superChapterName":"Kotlin","tags":[],"title":"Kotlin Coroutines(协程) 完全解析(一),协程简介","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"谷歌开发者","chapterId":415,"chapterName":"谷歌开发者","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7528,"link":"https://mp.weixin.qq.com/s/yrD8zUcnUs3LmNNhtWn4fw","niceDate":"2018-11-19","origin":"","projectLink":"","publishTime":1542556800000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/415/1"}],"title":"App 生存与壮大的五条原则","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7531,"link":"https://mp.weixin.qq.com/s/XjyH7nYqccNSZOKrvdAyLw","niceDate":"2018-11-19","origin":"","projectLink":"","publishTime":1542556800000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"Ubuntu搭建Jenkins+Android自动化打包","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"code小生","chapterId":414,"chapterName":"code小生","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7533,"link":"https://mp.weixin.qq.com/s/rI_whEgWt88foGYwiBHG9Q","niceDate":"2018-11-19","origin":"","projectLink":"","publishTime":1542556800000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/414/1"}],"title":"Android开发之图像处理那点事——滤镜","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"xlh__","chapterId":99,"chapterName":"具体案例","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7526,"link":"https://www.jianshu.com/p/e94a444054b4","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542546987000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"自定义RecycleView下拉刷新","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"honglei92","chapterId":99,"chapterName":"具体案例","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7525,"link":"https://www.jianshu.com/p/76a8a5e88460","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542546693000,"superChapterId":94,"superChapterName":"自定义控件","tags":[],"title":"TabLayout源码解析和仿简书首页TabLayout效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"simplehych ","chapterId":375,"chapterName":"Flutter","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7524,"link":"https://www.jianshu.com/p/1317aed6cd8c","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542546297000,"superChapterId":375,"superChapterName":"跨平台","tags":[],"title":"Flutter与Android混合开发及Platform Channel的使用","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"互联网侦察","chapterId":421,"chapterName":"互联网侦察","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7535,"link":"https://mp.weixin.qq.com/s/uPoruWUD-v_1YQf2KPO45w","niceDate":"2018-11-18","origin":"","projectLink":"","publishTime":1542470400000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/421/1"}],"title":"【面试现场】如何编程解决朋友圈个数问题?","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"鸿洋","chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7529,"link":"https://mp.weixin.qq.com/s/8yaGzZ2VFSDO9A5rwUrM-g","niceDate":"2018-11-16","origin":"","projectLink":"","publishTime":1542297600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"分享一下三年来公众号的小经验","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","author":"郭霖","chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","envelopePic":"","fresh":false,"id":7530,"link":"https://mp.weixin.qq.com/s/rq3gTxASebJxW_6WcSa-GQ","niceDate":"2018-11-16","origin":"","projectLink":"","publishTime":1542297600000,"superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"插件化之代码调用与加载资源","type":0,"userId":-1,"visible":1,"zan":0}]
27 | * offset : 0
28 | * over : false
29 | * pageCount : 286
30 | * size : 20
31 | * total : 5702
32 | */
33 |
34 | public int curPage;
35 | public int offset;
36 | public boolean over;
37 | public int pageCount;
38 | public int size;
39 | public int total;
40 | public List datas;
41 |
42 |
43 | public static class DatasBean {
44 | /**
45 | * apkLink :
46 | * author : 鸿洋
47 | * chapterId : 408
48 | * chapterName : 鸿洋
49 | * collect : false
50 | * courseId : 13
51 | * desc :
52 | * envelopePic :
53 | * fresh : false
54 | * id : 7545
55 | * link : https://mp.weixin.qq.com/s/1ua0geFnrbQbyHi8KG2VJQ
56 | * niceDate : 1天前
57 | * origin :
58 | * projectLink :
59 | * publishTime : 1542729600000
60 | * superChapterId : 408
61 | * superChapterName : 公众号
62 | * tags : [{"name":"公众号","url":"/wxarticle/list/408/1"}]
63 | * title : 换肤、全局字体替换、无需编写shape、selector 的原理Factory小结
64 | * type : 0
65 | * userId : -1
66 | * visible : 1
67 | * zan : 0
68 | */
69 |
70 | public String apkLink;
71 | public String author;
72 | public int chapterId;
73 | public String chapterName;
74 | public boolean collect;
75 | public int courseId;
76 | public String desc;
77 | public String envelopePic;
78 | public boolean fresh;
79 | public int id;
80 | public String link;
81 | public String niceDate;
82 | public String origin;
83 | public String projectLink;
84 | public long publishTime;
85 | public int superChapterId;
86 | public String superChapterName;
87 | public String title;
88 | public int type;
89 | public int userId;
90 | public int visible;
91 | public int zan;
92 | public List tags;
93 |
94 |
95 | public static class TagsBean {
96 | /**
97 | * name : 公众号
98 | * url : /wxarticle/list/408/1
99 | */
100 |
101 | public String name;
102 | public String url;
103 |
104 | @Override
105 | public String toString() {
106 | return "TagsBean{" +
107 | "name='" + name + '\'' +
108 | ", url='" + url + '\'' +
109 | '}';
110 | }
111 | }
112 |
113 | @Override
114 | public String toString() {
115 | return "DatasBean{" +
116 | "apkLink='" + apkLink + '\'' +
117 | ", author='" + author + '\'' +
118 | ", chapterId=" + chapterId +
119 | ", chapterName='" + chapterName + '\'' +
120 | ", collect=" + collect +
121 | ", courseId=" + courseId +
122 | ", desc='" + desc + '\'' +
123 | ", envelopePic='" + envelopePic + '\'' +
124 | ", fresh=" + fresh +
125 | ", id=" + id +
126 | ", link='" + link + '\'' +
127 | ", niceDate='" + niceDate + '\'' +
128 | ", origin='" + origin + '\'' +
129 | ", projectLink='" + projectLink + '\'' +
130 | ", publishTime=" + publishTime +
131 | ", superChapterId=" + superChapterId +
132 | ", superChapterName='" + superChapterName + '\'' +
133 | ", title='" + title + '\'' +
134 | ", type=" + type +
135 | ", userId=" + userId +
136 | ", visible=" + visible +
137 | ", zan=" + zan +
138 | ", tags=" + tags +
139 | '}';
140 | }
141 | }
142 |
143 | @Override
144 | public String toString() {
145 | return "DataBean{" +
146 | "curPage=" + curPage +
147 | ", offset=" + offset +
148 | ", over=" + over +
149 | ", pageCount=" + pageCount +
150 | ", size=" + size +
151 | ", total=" + total +
152 | ", datas=" + datas +
153 | '}';
154 | }
155 | }
156 |
157 | @Override
158 | public String toString() {
159 | return "ArticleListBean{" +
160 | "data=" + data +
161 | ", errorCode=" + errorCode +
162 | ", errorMsg='" + errorMsg + '\'' +
163 | '}';
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/bean/BannerBean.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.bean;
2 |
3 | import java.util.List;
4 |
5 | public class BannerBean {
6 |
7 | /**
8 | * data : [{"desc":"一起来做个App吧","id":10,"imagePath":"http://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":3,"title":"一起来做个App吧","type":0,"url":"http://www.wanandroid.com/blog/show/2"},{"desc":"","id":4,"imagePath":"http://www.wanandroid.com/blogimgs/ab17e8f9-6b79-450b-8079-0f2287eb6f0f.png","isVisible":1,"order":0,"title":"看看别人的面经,搞定面试~","type":1,"url":"http://www.wanandroid.com/article/list/0?cid=73"},{"desc":"","id":3,"imagePath":"http://www.wanandroid.com/blogimgs/fb0ea461-e00a-482b-814f-4faca5761427.png","isVisible":1,"order":1,"title":"兄弟,要不要挑个项目学习下?","type":1,"url":"http://www.wanandroid.com/project"},{"desc":"","id":6,"imagePath":"http://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1,"order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"http://www.wanandroid.com/navi"},{"desc":"","id":18,"imagePath":"http://www.wanandroid.com/blogimgs/00f83f1d-3c50-439f-b705-54a49fc3d90d.jpg","isVisible":1,"order":1,"title":"公众号文章列表强势上线","type":1,"url":"http://www.wanandroid.com/wxarticle/list/408/1"},{"desc":"","id":2,"imagePath":"http://www.wanandroid.com/blogimgs/90cf8c40-9489-4f9d-8936-02c9ebae31f0.png","isVisible":1,"order":2,"title":"JSON工具","type":1,"url":"http://www.wanandroid.com/tools/bejson"},{"desc":"","id":5,"imagePath":"http://www.wanandroid.com/blogimgs/acc23063-1884-4925-bdf8-0b0364a7243e.png","isVisible":1,"order":3,"title":"微信文章合集","type":1,"url":"http://www.wanandroid.com/blog/show/6"}]
9 | * errorCode : 0
10 | * errorMsg :
11 | */
12 |
13 | public int errorCode;
14 | public String errorMsg;
15 | public List data;
16 |
17 |
18 | public static class DataBean {
19 | /**
20 | * desc : 一起来做个App吧
21 | * id : 10
22 | * imagePath : http://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png
23 | * isVisible : 1
24 | * order : 3
25 | * title : 一起来做个App吧
26 | * type : 0
27 | * url : http://www.wanandroid.com/blog/show/2
28 | */
29 |
30 | public String desc;
31 | public int id;
32 | public String imagePath;
33 | public int isVisible;
34 | public int order;
35 | public String title;
36 | public int type;
37 | public String url;
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/contract/MultipleInterfaceContract.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.contract;
2 |
3 | import com.example.lsj.mvpdemo.bean.BannerBean;
4 | import com.example.lsj.mvpdemo.view.IView;
5 |
6 | public interface MultipleInterfaceContract {
7 |
8 |
9 | interface View extends IView {
10 | void showMultipleSuccess(BannerBean bean);
11 |
12 | void showMultipleFail(String errorMsg);
13 | }
14 |
15 | interface Presenter {
16 | void getBanner();
17 | }
18 |
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/contract/SingleInterfaceContract.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.contract;
2 |
3 | import com.example.lsj.mvpdemo.bean.ArticleListBean;
4 | import com.example.lsj.mvpdemo.view.IView;
5 |
6 | public interface SingleInterfaceContract {
7 |
8 |
9 | interface View extends IView {
10 | void showArticleSuccess(ArticleListBean bean);
11 |
12 | void showArticleFail(String errorMsg);
13 | }
14 |
15 | interface Presenter {
16 | void getData(int curPage);
17 | }
18 |
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/interfaces/Callback.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.interfaces;
2 |
3 | /**
4 | * @Description: 所有model中的回调
5 | * @Author: lishengjiejob@163.com
6 | * @Time: 2018/11/22 14:53
7 | */
8 | public interface Callback {
9 | void onSuccess(K data);
10 |
11 | void onFail(V data);
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/model/IModel.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.model;
2 |
3 | public interface IModel {
4 | }
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/model/IMultipleInterfaceModel.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.model;
2 |
3 | import com.example.lsj.mvpdemo.interfaces.Callback;
4 |
5 | public interface IMultipleInterfaceModel extends IModel {
6 | void getBanner(final Callback callback);
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/model/ISingleInterfaceModel.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.model;
2 |
3 | import com.example.lsj.mvpdemo.interfaces.Callback;
4 |
5 | public interface ISingleInterfaceModel extends IModel {
6 | void getData(int curPage, final Callback callback);
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/model/MultipleInterfaceModel.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.model;
2 |
3 | import com.example.lsj.mvpdemo.api.Api;
4 | import com.example.lsj.mvpdemo.bean.BannerBean;
5 | import com.example.lsj.mvpdemo.interfaces.Callback;
6 | import com.example.lsj.mvpdemo.utils.LP;
7 | import com.example.lsj.mvpdemo.utils.NetUtils;
8 |
9 | import rx.Subscriber;
10 | import rx.android.schedulers.AndroidSchedulers;
11 | import rx.schedulers.Schedulers;
12 |
13 |
14 | public class MultipleInterfaceModel implements IMultipleInterfaceModel {
15 |
16 | @Override
17 | public void getBanner(final Callback callback) {
18 | NetUtils.getRetrofit()
19 | .create(Api.class)
20 | .getBanner()
21 | .subscribeOn(Schedulers.io())
22 | .observeOn(AndroidSchedulers.mainThread())
23 | .subscribe(new Subscriber() {
24 | @Override
25 | public void onCompleted() {
26 | LP.w("completed");
27 | }
28 |
29 | @Override
30 | public void onError(Throwable e) {
31 | callback.onFail("出现错误");
32 | }
33 |
34 | @Override
35 | public void onNext(BannerBean bean) {
36 | if (null == bean) {
37 | callback.onFail("出现错误");
38 | } else if (bean.errorCode != 0) {
39 | callback.onFail(bean.errorMsg);
40 | } else {
41 | callback.onSuccess(bean);
42 | }
43 | }
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/model/SingleInterfaceModel.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.model;
2 |
3 | import com.example.lsj.mvpdemo.api.Api;
4 | import com.example.lsj.mvpdemo.bean.ArticleListBean;
5 | import com.example.lsj.mvpdemo.interfaces.Callback;
6 | import com.example.lsj.mvpdemo.utils.LP;
7 | import com.example.lsj.mvpdemo.utils.NetUtils;
8 |
9 | import rx.Subscriber;
10 | import rx.android.schedulers.AndroidSchedulers;
11 | import rx.schedulers.Schedulers;
12 |
13 |
14 | public class SingleInterfaceModel implements ISingleInterfaceModel {
15 |
16 | @Override
17 | public void getData(int curPage, final Callback callback) {
18 | NetUtils.getRetrofit()
19 | .create(Api.class)
20 | .getData(curPage)
21 | .subscribeOn(Schedulers.io())
22 | .observeOn(AndroidSchedulers.mainThread())
23 | .subscribe(new Subscriber() {
24 | @Override
25 | public void onCompleted() {
26 | LP.w("completed");
27 | }
28 |
29 | @Override
30 | public void onError(Throwable e) {
31 | callback.onFail("出现错误");
32 | }
33 |
34 | @Override
35 | public void onNext(ArticleListBean bean) {
36 | if (null == bean) {
37 | callback.onFail("出现错误");
38 | } else if (bean.errorCode != 0) {
39 | callback.onFail(bean.errorMsg);
40 | } else {
41 | callback.onSuccess(bean);
42 | }
43 | }
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/presenter/IPresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.presenter;
2 |
3 | import com.example.lsj.mvpdemo.view.IView;
4 |
5 | public interface IPresenter {
6 |
7 | /**
8 | * 依附生命view
9 | *
10 | * @param view
11 | */
12 | void attachView(T view);
13 |
14 | /**
15 | * 分离View
16 | */
17 | void detachView();
18 |
19 | /**
20 | * 判断View是否已经销毁
21 | *
22 | * @return
23 | */
24 | boolean isViewAttached();
25 |
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/presenter/MultipleInterfacePresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.presenter;
2 |
3 | import com.example.lsj.mvpdemo.base.BasePresenter;
4 | import com.example.lsj.mvpdemo.bean.BannerBean;
5 | import com.example.lsj.mvpdemo.contract.MultipleInterfaceContract;
6 | import com.example.lsj.mvpdemo.interfaces.Callback;
7 | import com.example.lsj.mvpdemo.model.IMultipleInterfaceModel;
8 | import com.example.lsj.mvpdemo.model.MultipleInterfaceModel;
9 | import com.example.lsj.mvpdemo.utils.LP;
10 |
11 | /**
12 | * @Description: Presenter层代码
13 | * @Author: lishengjiejob@163.com
14 | * @Time: 2018/11/22 15:14
15 | */
16 | public class MultipleInterfacePresenter extends BasePresenter
17 | implements MultipleInterfaceContract.Presenter {
18 |
19 | private final IMultipleInterfaceModel multipleInterfaceModel;
20 |
21 | public MultipleInterfacePresenter() {
22 | this.multipleInterfaceModel = new MultipleInterfaceModel();
23 | }
24 |
25 | @Override
26 | public void getBanner() {
27 | multipleInterfaceModel.getBanner(new Callback() {
28 | @Override
29 | public void onSuccess(BannerBean bannerBean) {
30 | //如果Model层请求数据成功,则此处应执行通知View层的代码
31 | //LP.w()是一个简单的log打印
32 | LP.w(bannerBean.toString());
33 | if (isViewAttached()) {
34 | mView.showMultipleSuccess(bannerBean);
35 | }
36 | }
37 |
38 | @Override
39 | public void onFail(String errorMsg) {
40 | //如果Model层请求数据失败,则此处应执行通知View层的代码
41 | LP.w(errorMsg);
42 | if (isViewAttached()) {
43 | mView.showMultipleFail(errorMsg);
44 | }
45 | }
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/presenter/SingleInterfacePresenter.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.presenter;
2 |
3 | import com.example.lsj.mvpdemo.base.BasePresenter;
4 | import com.example.lsj.mvpdemo.bean.ArticleListBean;
5 | import com.example.lsj.mvpdemo.contract.SingleInterfaceContract;
6 | import com.example.lsj.mvpdemo.interfaces.Callback;
7 | import com.example.lsj.mvpdemo.model.ISingleInterfaceModel;
8 | import com.example.lsj.mvpdemo.model.SingleInterfaceModel;
9 | import com.example.lsj.mvpdemo.utils.LP;
10 |
11 | /**
12 | * @Description: Presenter层代码
13 | * @Author: lishengjiejob@163.com
14 | * @Time: 2018/11/22 15:14
15 | */
16 | public class SingleInterfacePresenter extends BasePresenter
17 | implements SingleInterfaceContract.Presenter {
18 |
19 | private final ISingleInterfaceModel singleInterfaceModel;
20 |
21 | public SingleInterfacePresenter() {
22 | this.singleInterfaceModel = new SingleInterfaceModel();
23 | }
24 |
25 | @Override
26 | public void getData(int curPage) {
27 | singleInterfaceModel.getData(curPage, new Callback() {
28 | @Override
29 | public void onSuccess(ArticleListBean loginResultBean) {
30 | //如果Model层请求数据成功,则此处应执行通知View层的代码
31 | //LP.w()是一个简单的log打印
32 | LP.w(loginResultBean.toString());
33 | if (isViewAttached()) {
34 | mView.showArticleSuccess(loginResultBean);
35 | }
36 | }
37 |
38 | @Override
39 | public void onFail(String errorMsg) {
40 | //如果Model层请求数据失败,则此处应执行通知View层的代码
41 | LP.w(errorMsg);
42 | if (isViewAttached()) {
43 | mView.showArticleFail(errorMsg);
44 | }
45 | }
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/utils/LP.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.utils;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import com.example.lsj.mvpdemo.BuildConfig;
7 |
8 |
9 | /**
10 | * @Description: 自定义log
11 | * @Author: lishengjiejob@163.com
12 | * @Time: 2018/11/22 10:07
13 | */
14 | public class LP {
15 |
16 | //是否开启debug,默认根据运行方式选择
17 | private static boolean isBug = BuildConfig.DEBUG;
18 | //分隔符
19 | private static String separator = ",";
20 | /**
21 | * 是否显示log附加信息,包括线程名+方法名+行数
22 | */
23 | private static boolean showInfo = true;
24 |
25 | /**
26 | * 构造方法私有化,防止多次实例化。
27 | */
28 | private LP() {
29 | }
30 |
31 | /**
32 | * 公共日志打印方法
33 | *
34 | * @param tag
35 | * @param msg
36 | */
37 | public static void logI(String tag, String msg) {
38 | if (isBug && !TextUtils.isEmpty(tag) && !TextUtils.isEmpty(msg)) {
39 | Log.i(tag, msg);
40 | }
41 | }
42 |
43 | public static void w(String msg) {
44 | if (isBug && !TextUtils.isEmpty(msg)) {
45 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
46 | String tag = getDefaultTag(stackTraceElement);
47 | Log.w("lsj--" + tag, "<<<<<<<------------------------");
48 | Log.w("lsj--" + tag, msg);
49 | Log.w("lsj--" + tag, getLogInfo(stackTraceElement));
50 | }
51 | }
52 |
53 | /**
54 | * 获取默认的TAG名称.
55 | * 比如在MainActivity.java中调用了日志输出.
56 | * 则TAG为MainActivity
57 | */
58 | public static String getDefaultTag(StackTraceElement stackTraceElement) {
59 | String fileName = stackTraceElement.getFileName();
60 | String stringArray[] = fileName.split("\\.");
61 | String tag = stringArray[0];
62 | return tag;
63 | }
64 |
65 | /**
66 | * 输出日志所包含的信息
67 | */
68 | public static String getLogInfo(StackTraceElement stackTraceElement) {
69 | if (!showInfo) {
70 | return "";
71 | }
72 | StringBuilder logInfoStringBuilder = new StringBuilder();
73 | // 获取线程名
74 | String threadName = Thread.currentThread().getName();
75 | // 获取线程ID
76 | long threadID = Thread.currentThread().getId();
77 | // 获取文件名.即xxx.java
78 | String fileName = stackTraceElement.getFileName();
79 | // 获取类名.即包名+类名
80 | String className = stackTraceElement.getClassName();
81 | // 获取方法名称
82 | String methodName = stackTraceElement.getMethodName();
83 | // 获取生日输出行数
84 | int lineNumber = stackTraceElement.getLineNumber();
85 |
86 | logInfoStringBuilder.append(" ==>[ ");
87 | logInfoStringBuilder.append(" threadID=" + threadID).append(separator);
88 | logInfoStringBuilder.append(" threadName=" + threadName).append(separator);
89 | logInfoStringBuilder.append(" fileName=" + fileName).append(separator);
90 | logInfoStringBuilder.append(" className=" + className).append(separator);
91 | logInfoStringBuilder.append(" methodName=" + methodName).append(separator);
92 | logInfoStringBuilder.append(" lineNumber=" + lineNumber);
93 | logInfoStringBuilder.append(" ] ");
94 | return logInfoStringBuilder.toString();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/utils/NetUtils.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.utils;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.EOFException;
6 | import java.io.IOException;
7 | import java.nio.charset.Charset;
8 | import java.util.Locale;
9 | import java.util.concurrent.TimeUnit;
10 |
11 | import okhttp3.Interceptor;
12 | import okhttp3.MediaType;
13 | import okhttp3.OkHttpClient;
14 | import okhttp3.Request;
15 | import okhttp3.RequestBody;
16 | import okhttp3.Response;
17 | import okhttp3.ResponseBody;
18 | import okio.Buffer;
19 | import okio.BufferedSource;
20 | import retrofit2.Retrofit;
21 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
22 | import retrofit2.converter.gson.GsonConverterFactory;
23 | import retrofit2.converter.scalars.ScalarsConverterFactory;
24 |
25 | /**
26 | * @Description: 网络框架
27 | * @Author: lishengjiejob@163.com
28 | * @Time: 2018/11/22 10:00
29 | */
30 | public class NetUtils {
31 |
32 | public static Retrofit retrofit = null;
33 | //可改变baseurl
34 | public static Retrofit mRetrofit = null;
35 | private static int DEFAULT_TIMEOUT = 60;
36 | private static int DEFAULT_TIMEOUT_WRITE = 60;
37 | public static OkHttpClient mOkHttpClient;
38 | private static String mBaseUrl = "http://www.wanandroid.com";
39 |
40 | private NetUtils() {
41 |
42 | }
43 |
44 | public static OkHttpClient getOkHttpClient() {
45 | if (mOkHttpClient == null) {
46 | synchronized (NetUtils.class) {
47 | try {
48 | OkHttpClient.Builder builder = new OkHttpClient.Builder()
49 | .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
50 | .writeTimeout(DEFAULT_TIMEOUT_WRITE, TimeUnit.SECONDS)
51 | .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
52 | .addNetworkInterceptor(new LogInterceptor())
53 | .retryOnConnectionFailure(true);
54 |
55 | mOkHttpClient = builder.build();
56 |
57 | } catch (Exception e) {
58 | e.printStackTrace();
59 | Log.d("lsj", "okhttp try catch");
60 | }
61 | }
62 | }
63 | return mOkHttpClient;
64 | }
65 |
66 | public static Retrofit getRetrofit() {
67 | if (retrofit == null) {
68 | synchronized (NetUtils.class) {
69 | retrofit = new Retrofit.Builder()
70 | .baseUrl(mBaseUrl)
71 | //增加返回值为String的支持
72 | .addConverterFactory(ScalarsConverterFactory.create())
73 | //增加返回值为Gson的支持(以实体类返回)
74 | .addConverterFactory(GsonConverterFactory.create())
75 | //增加返回值为Oservable的支持
76 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
77 | .client(getOkHttpClient())
78 | .build();
79 | }
80 | }
81 | return retrofit;
82 | }
83 |
84 | public static Retrofit getRetrofit(String baseUrl) {
85 | mRetrofit = new Retrofit.Builder()
86 | .baseUrl(baseUrl)
87 | //增加返回值为String的支持
88 | .addConverterFactory(ScalarsConverterFactory.create())
89 | //增加返回值为Gson的支持(以实体类返回)
90 | .addConverterFactory(GsonConverterFactory.create())
91 | //增加返回值为Oservable的支持
92 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
93 | .client(getOkHttpClient())
94 | .build();
95 | return mRetrofit;
96 | }
97 |
98 |
99 | public static class LogInterceptor implements Interceptor {
100 |
101 | @Override
102 | public Response intercept(Chain chain) throws IOException {
103 | Request request = chain.request();
104 | //the request url
105 | String url = request.url().toString();
106 | //the request method
107 | String method = request.method();
108 | long t1 = System.nanoTime();
109 | // LogPrint.w(String.format(Locale.getDefault(), "1. Sending %s request [url = %s]", method, url));
110 | LP.w(String.format(Locale.getDefault(), "1. Sending %s request [url = %s]", method, url));
111 | //the request body
112 | RequestBody requestBody = request.body();
113 | if (requestBody != null) {
114 | StringBuilder sb = new StringBuilder("Request Body [");
115 | Buffer buffer = new Buffer();
116 | requestBody.writeTo(buffer);
117 | Charset charset = Charset.forName("UTF-8");
118 | MediaType contentType = requestBody.contentType();
119 | if (contentType != null) {
120 | charset = contentType.charset(charset);
121 | }
122 | if (contentType != null && isPlaintext(buffer)) {
123 | sb.append(buffer.readString(charset));
124 | sb.append(" (Content-Type = ").append(contentType.toString()).append(",")
125 | .append(requestBody.contentLength()).append("-byte body)");
126 | } else {
127 | sb.append(" (Content-Type = ").append(contentType.toString())
128 | .append(",binary ").append(requestBody.contentLength()).append("-byte body omitted)");
129 | }
130 | sb.append("]");
131 | LP.w(String.format(Locale.getDefault(), "2. %s %s", method, sb.toString()));
132 | }
133 | Response response = chain.proceed(request);
134 | long t2 = System.nanoTime();
135 | //the response time
136 | LP.w(String.format(Locale.getDefault(), "3. Received response for [url = %s] in %.1fms",
137 | url, (t2 - t1) / 1e6d));
138 | LP.w(String.format(Locale.CHINA, "4. Received response is %s ,message[%s],code[%d]",
139 | response.isSuccessful() ? "success" : "fail", response.message(), response.code()));
140 | //the response data
141 | ResponseBody body = response.body();
142 |
143 | BufferedSource source = body.source();
144 | source.request(Long.MAX_VALUE); // Buffer the entire body.
145 | Buffer buffer = source.buffer();
146 | Charset charset = Charset.defaultCharset();
147 | MediaType contentType = body.contentType();
148 | if (contentType != null) {
149 | charset = contentType.charset(charset);
150 | }
151 | String bodyString = buffer.clone().readString(charset);
152 | LP.w(String.format("5. Received response json string [%s]", bodyString));
153 | return response;
154 | }
155 |
156 | static boolean isPlaintext(Buffer buffer) {
157 | try {
158 | Buffer prefix = new Buffer();
159 | long byteCount = buffer.size() < 64 ? buffer.size() : 64;
160 | buffer.copyTo(prefix, 0, byteCount);
161 | for (int i = 0; i < 16; i++) {
162 | if (prefix.exhausted()) {
163 | break;
164 | }
165 | int codePoint = prefix.readUtf8CodePoint();
166 | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
167 | return false;
168 | }
169 | }
170 | return true;
171 | } catch (EOFException e) {
172 | return false; // Truncated UTF-8 sequence.
173 | }
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/view/IView.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.view;
2 |
3 | /**
4 | * @Description: 公共接口 是用于给View的接口继承的,注意,不是View本身继承。
5 | * 因为它定义的是接口的规范, 而接口才是定义的类的规范
6 | * @Author: lishengjiejob@163.com
7 | * @Time: 2018/11/22 17:26
8 | */
9 | public interface IView {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/view/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.view;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 |
8 | import com.example.lsj.mvpdemo.R;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_main);
16 | findViewById(R.id.btn_singl).setOnClickListener(new View.OnClickListener() {
17 | @Override
18 | public void onClick(View v) {
19 | startActivity(new Intent(MainActivity.this, SingleInterfaceActivity.class));
20 | }
21 | });
22 |
23 | findViewById(R.id.btn_mul).setOnClickListener(new View.OnClickListener() {
24 | @Override
25 | public void onClick(View v) {
26 | startActivity(new Intent(MainActivity.this, MultipleInterfaceActivity.class));
27 | }
28 | });
29 |
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/view/MultipleInterfaceActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.view;
2 |
3 | import android.view.View;
4 | import android.widget.Button;
5 | import android.widget.TextView;
6 | import android.widget.Toast;
7 |
8 | import com.example.lsj.mvpdemo.R;
9 | import com.example.lsj.mvpdemo.base.BaseMVPActivity;
10 | import com.example.lsj.mvpdemo.base.MultiplePresenter;
11 | import com.example.lsj.mvpdemo.bean.ArticleListBean;
12 | import com.example.lsj.mvpdemo.bean.BannerBean;
13 | import com.example.lsj.mvpdemo.contract.MultipleInterfaceContract;
14 | import com.example.lsj.mvpdemo.contract.SingleInterfaceContract;
15 | import com.example.lsj.mvpdemo.presenter.MultipleInterfacePresenter;
16 | import com.example.lsj.mvpdemo.presenter.SingleInterfacePresenter;
17 |
18 | /**
19 | * @Description: 单页面多网络请求
20 | * @Author: lishengjiejob@163.com
21 | * @Time: 2018/11/22 21:02
22 | */
23 | public class MultipleInterfaceActivity extends BaseMVPActivity
24 | implements SingleInterfaceContract.View, MultipleInterfaceContract.View {
25 |
26 | private Button button;
27 | private TextView textView;
28 | private Button btn;
29 | private TextView tv;
30 | private SingleInterfacePresenter singleInterfacePresenter;
31 | private MultipleInterfacePresenter multipleInterfacePresenter;
32 |
33 |
34 | @Override
35 | protected void init() {
36 | setContentView(R.layout.activity_multiple_interface);
37 |
38 | button = findViewById(R.id.button);
39 | textView = findViewById(R.id.textView);
40 |
41 | button.setOnClickListener(new View.OnClickListener() {
42 | @Override
43 | public void onClick(View v) {
44 | singleInterfacePresenter.getData(0);
45 | }
46 | });
47 |
48 |
49 | btn = findViewById(R.id.btn);
50 | tv = findViewById(R.id.tv);
51 |
52 | btn.setOnClickListener(new View.OnClickListener() {
53 | @Override
54 | public void onClick(View v) {
55 | multipleInterfacePresenter.getBanner();
56 | }
57 | });
58 | }
59 |
60 | @Override
61 | protected MultiplePresenter createPresenter() {
62 | MultiplePresenter multiplePresenter = new MultiplePresenter(this);
63 |
64 | singleInterfacePresenter = new SingleInterfacePresenter();
65 | multipleInterfacePresenter = new MultipleInterfacePresenter();
66 |
67 | multiplePresenter.addPresenter(singleInterfacePresenter);
68 | multiplePresenter.addPresenter(multipleInterfacePresenter);
69 | return multiplePresenter;
70 | }
71 |
72 | @Override
73 | public void showArticleSuccess(ArticleListBean bean) {
74 | textView.setText(bean.data.datas.get(0).title);
75 | }
76 |
77 | @Override
78 | public void showArticleFail(String errorMsg) {
79 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
80 | }
81 |
82 | @Override
83 | public void showMultipleSuccess(BannerBean bean) {
84 | tv.setText(bean.data.get(0).title);
85 | }
86 |
87 | @Override
88 | public void showMultipleFail(String errorMsg) {
89 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/lsj/mvpdemo/view/SingleInterfaceActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo.view;
2 |
3 | import android.view.View;
4 | import android.widget.Button;
5 | import android.widget.TextView;
6 | import android.widget.Toast;
7 |
8 | import com.example.lsj.mvpdemo.R;
9 | import com.example.lsj.mvpdemo.base.BaseMVPActivity;
10 | import com.example.lsj.mvpdemo.bean.ArticleListBean;
11 | import com.example.lsj.mvpdemo.contract.SingleInterfaceContract;
12 | import com.example.lsj.mvpdemo.presenter.SingleInterfacePresenter;
13 |
14 | /**
15 | * @Description: 单个网络接口请求示例
16 | * @Author: lishengjiejob@163.com
17 | * @Time: 2018/11/22 09:36
18 | */
19 | public class SingleInterfaceActivity extends BaseMVPActivity
20 | implements SingleInterfaceContract.View {
21 |
22 | private Button button;
23 | private TextView textView;
24 |
25 | @Override
26 | protected void init() {
27 | setContentView(R.layout.activity_single_interface);
28 | button = findViewById(R.id.button);
29 | textView = findViewById(R.id.textView);
30 |
31 | button.setOnClickListener(new View.OnClickListener() {
32 | @Override
33 | public void onClick(View v) {
34 | mPresenter.getData(0);
35 | }
36 | });
37 | }
38 |
39 | @Override
40 | protected SingleInterfacePresenter createPresenter() {
41 | return new SingleInterfacePresenter();
42 | }
43 |
44 |
45 | @Override
46 | public void showArticleSuccess(ArticleListBean bean) {
47 | textView.setText(bean.data.datas.get(0).title);
48 | }
49 |
50 | @Override
51 | public void showArticleFail(String errorMsg) {
52 | Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_multiple_interface.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
30 |
31 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_single_interface.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MVP Demo
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/lsj/mvpdemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.lsj.mvpdemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }//阿里的源
7 | google()
8 | jcenter()
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.1.4'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }//阿里的源
23 | mavenCentral()
24 | maven { url "https://jitpack.io" }
25 | jcenter {
26 | url "http://jcenter.bintray.com/"
27 | }
28 | }
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 22 11:10:55 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshot/mvc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/screenshot/mvc.jpg
--------------------------------------------------------------------------------
/screenshot/mvp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/screenshot/mvp.jpg
--------------------------------------------------------------------------------
/screenshot/单界面单网络请求接口.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/screenshot/单界面单网络请求接口.gif
--------------------------------------------------------------------------------
/screenshot/单界面多网络请求接口.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/screenshot/单界面多网络请求接口.gif
--------------------------------------------------------------------------------
/screenshot/文章目录.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/screenshot/文章目录.png
--------------------------------------------------------------------------------
/screenshot/目录.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serge66/MVPDemo/7be1d261b0b83241d7824d7daa4651031b3ff473/screenshot/目录.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------