0
评论

(客服云)知识库添加了附件,怎么发送的时候附件没有发送出去? 知识库附件不显示 知识库附件

kijieoeew 发表了文章 • 221 次浏览 • 2019-03-29 15:36 • 来自相关话题

附件需要单独发送给访客。发送知识时,图文消息仅包含知识标题和知识内容,不包含知识的附件。若发送知识中的附件,需打开知识详情,并点击附件右上角的“发送”按钮。
 
附件需要单独发送给访客。发送知识时,图文消息仅包含知识标题和知识内容,不包含知识的附件。若发送知识中的附件,需打开知识详情,并点击附件右上角的“发送”按钮。
 
0
评论

(客服云)iOS访客端插入企业欢迎语 企业欢迎语

kijieoeew 发表了文章 • 275 次浏览 • 2019-03-29 15:15 • 来自相关话题

0、前提配置
(1)在客服系统内 管理员模式--设置--系统开关--企业欢迎语 处设置欢迎语,并开启开关




(2)联系商务开通会话创建、接起、结束的透传事件功能

1、在HDMessageViewController.m中添加企业欢迎语属性



// 企业欢迎语
@property (nonatomic, strong) NSString *companyWelcome;
2、在HDMessageViewController.m中的 viewdidload 方法的最后调用 [self judge]; 







// 这个方法是属于逻辑判断,用UD去记录参数,只有第一次进入聊天页面和会话结束之后再进入到聊天页面才插入消息。
- (void)judge
{
// 判断会话中是否有消息
if (self.conversation.latestMessage == nil) {
// 如果会话中没有消息,那么UD的值变成NO
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isGetWelcome"];
}
// 根据以前的判断,如果UD值为NO则插入消息欢迎语
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"isGetWelcome"]) {
// 插入企业欢迎语
[self welcome];
}

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isGetWelcome"];
}

// 这个方法是构建消息以及向UI页面以及本地数据库插入消息
- (void)welcome
{
// 此方法是SDK获取企业欢迎语的
__weak typeof(self) weakself = self;
[[HDClient sharedClient].chatManager getEnterpriseWelcomeWithCompletion:^(NSString *welcome, HDError *error) {
// 判断客服系统中是否设置了企业欢迎语并且开关有没有打开,兼容误操作客服关闭了企业欢迎语开关,会插入一条空消息,那么自己选择是否插入一条默认的欢迎语,自行修改 ‘您好,欢迎光临!’ 的内容
if (![welcome isEqualToString:@""]) {
weakself.companyWelcome = welcome;
} else {
weakself.companyWelcome = @"您好,欢迎光临!";
}
// 构建消息
EMTextMessageBody *bdy = [[EMTextMessageBody alloc] initWithText:weakself.companyWelcome];
NSString *from = [[HDClient sharedClient] currentUsername];
// _converID就是IM服务号,可以自己替换下
HDMessage *message = [[HDMessage alloc] initWithConversationID:weakself.conversation.conversationId from:from to:weakself.conversation.conversationId body:bdy];
// 构建的消息用ext来标记此条消息是插入的欢迎语,然后在cell里面根据消息的ext来修改插入消息的昵称和头像
NSDictionary *welcomeExt = @{@"insertWelcome":@"插入的欢迎语"};
[message addAttributeDictionary:welcomeExt];
message.direction = 1;
message.status = HDMessageStatusSuccessed;
// 更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 消息添加到数据源,刷新UI
[weakself addMessageToDataSource:message progress:nil];
});
// 消息插入到会话
HDError *pError;
[weakself.conversation addMessage:message error:&pError];

}];

3、在HDMessageViewController.m中的cmdMessagesDidReceive方法中调用 messageExtwithEventName



// 这个方法是判断接收到客服消息的ext里面有没有ServiceSessionClosedEvent,如果有就把UD记录改变参数,下次进入聊天页面的时候插入欢迎语
// 如果是ServiceSessionOpenedEvent,客服系统接入了会话,那么就不插入欢迎语

// 这个方法是接收透传消息里面调用,客服系统会话被接入会给app端发送透传消息通知,如果没有接收到透传消息,那么找对接的环信商务开通这个功能
[self messageExtwithEventName:message];

- (void)messageExtwithEventName:(HDMessage *)message
{
if (![[[message.ext objectForKey:@"weichat"] objectForKey:@"event"] isKindOfClass:[NSNull class]]) {
NSDictionary *dict = [[message.ext objectForKey:@"weichat"] objectForKey:@"event"];
if ([[dict objectForKey:@"eventName"] isEqualToString:@"ServiceSessionClosedEvent"]) {
//如果接收到客服的消息ext中有 ServiceSessionClosedEvent,表示会话已经结束,那么UD的值变成NO
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isGetWelcome"];
}
if ([[dict objectForKey:@"eventName"] isEqualToString:@"ServiceSessionOpenedEvent"]) {
//如果接收到客服的消息ext中有 ServiceSessionClosedEvent,表示会话已经结束,那么UD的值变成NO
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isGetWelcome"];
}

}

4、在cell中设置插入消息的昵称头像,根据插入消息ext中的key去判断。



if (!self.model.isSender) {
if (![model.nickname isKindOfClass:[NSNull class]]) {
// 判断此条消息是否为插入的企业欢迎语,是的话赋值昵称和头像
if ([self.model.message.ext objectForKey:@"insertWelcome"]) {
_nameLabel.text = @"系统消息";
self.avatarView.image = [UIImage imageNamed:@"测试图片"];
}
}
}
效果图




  查看全部
0、前提配置
(1)在客服系统内 管理员模式--设置--系统开关--企业欢迎语 处设置欢迎语,并开启开关
企业欢迎语0.png

(2)联系商务开通会话创建、接起、结束的透传事件功能

1、在HDMessageViewController.m中添加企业欢迎语属性
企业欢迎语1.png
// 企业欢迎语
@property (nonatomic, strong) NSString *companyWelcome;

2、在HDMessageViewController.m中的 viewdidload 方法的最后调用 [self judge]; 
企业欢迎语2.png

企业欢迎语3.png
// 这个方法是属于逻辑判断,用UD去记录参数,只有第一次进入聊天页面和会话结束之后再进入到聊天页面才插入消息。
- (void)judge
{
// 判断会话中是否有消息
if (self.conversation.latestMessage == nil) {
// 如果会话中没有消息,那么UD的值变成NO
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isGetWelcome"];
}
// 根据以前的判断,如果UD值为NO则插入消息欢迎语
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"isGetWelcome"]) {
// 插入企业欢迎语
[self welcome];
}

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isGetWelcome"];
}

// 这个方法是构建消息以及向UI页面以及本地数据库插入消息
- (void)welcome
{
// 此方法是SDK获取企业欢迎语的
__weak typeof(self) weakself = self;
[[HDClient sharedClient].chatManager getEnterpriseWelcomeWithCompletion:^(NSString *welcome, HDError *error) {
// 判断客服系统中是否设置了企业欢迎语并且开关有没有打开,兼容误操作客服关闭了企业欢迎语开关,会插入一条空消息,那么自己选择是否插入一条默认的欢迎语,自行修改 ‘您好,欢迎光临!’ 的内容
if (![welcome isEqualToString:@""]) {
weakself.companyWelcome = welcome;
} else {
weakself.companyWelcome = @"您好,欢迎光临!";
}
// 构建消息
EMTextMessageBody *bdy = [[EMTextMessageBody alloc] initWithText:weakself.companyWelcome];
NSString *from = [[HDClient sharedClient] currentUsername];
// _converID就是IM服务号,可以自己替换下
HDMessage *message = [[HDMessage alloc] initWithConversationID:weakself.conversation.conversationId from:from to:weakself.conversation.conversationId body:bdy];
// 构建的消息用ext来标记此条消息是插入的欢迎语,然后在cell里面根据消息的ext来修改插入消息的昵称和头像
NSDictionary *welcomeExt = @{@"insertWelcome":@"插入的欢迎语"};
[message addAttributeDictionary:welcomeExt];
message.direction = 1;
message.status = HDMessageStatusSuccessed;
// 更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 消息添加到数据源,刷新UI
[weakself addMessageToDataSource:message progress:nil];
});
// 消息插入到会话
HDError *pError;
[weakself.conversation addMessage:message error:&pError];

}];
}
 
3、在HDMessageViewController.m中的cmdMessagesDidReceive方法中调用 messageExtwithEventName
企业欢迎语4.png
// 这个方法是判断接收到客服消息的ext里面有没有ServiceSessionClosedEvent,如果有就把UD记录改变参数,下次进入聊天页面的时候插入欢迎语
// 如果是ServiceSessionOpenedEvent,客服系统接入了会话,那么就不插入欢迎语

// 这个方法是接收透传消息里面调用,客服系统会话被接入会给app端发送透传消息通知,如果没有接收到透传消息,那么找对接的环信商务开通这个功能
[self messageExtwithEventName:message];

- (void)messageExtwithEventName:(HDMessage *)message
{
if (![[[message.ext objectForKey:@"weichat"] objectForKey:@"event"] isKindOfClass:[NSNull class]]) {
NSDictionary *dict = [[message.ext objectForKey:@"weichat"] objectForKey:@"event"];
if ([[dict objectForKey:@"eventName"] isEqualToString:@"ServiceSessionClosedEvent"]) {
//如果接收到客服的消息ext中有 ServiceSessionClosedEvent,表示会话已经结束,那么UD的值变成NO
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isGetWelcome"];
}
if ([[dict objectForKey:@"eventName"] isEqualToString:@"ServiceSessionOpenedEvent"]) {
//如果接收到客服的消息ext中有 ServiceSessionClosedEvent,表示会话已经结束,那么UD的值变成NO
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isGetWelcome"];
}

}
}
 
4、在cell中设置插入消息的昵称头像,根据插入消息ext中的key去判断。
企业欢迎语5.png
if (!self.model.isSender) {
if (![model.nickname isKindOfClass:[NSNull class]]) {
// 判断此条消息是否为插入的企业欢迎语,是的话赋值昵称和头像
if ([self.model.message.ext objectForKey:@"insertWelcome"]) {
_nameLabel.text = @"系统消息";
self.avatarView.image = [UIImage imageNamed:@"测试图片"];
}
}
}

效果图
企业欢迎语效果图.png

 
2
回复

环信3.1版本在Android9.0上面登录返回304错误 环信_Android

lizg 回复了问题 • 3 人关注 • 8374 次浏览 • 2019-03-28 17:39 • 来自相关话题

6
回复

最新版本放进羡项目有跟多警告。 EaseUI 环信_iOS 环信 ios 环信 iOS集成

su酥酥 回复了问题 • 3 人关注 • 717 次浏览 • 2019-03-26 09:39 • 来自相关话题

2
回复

android 撤回问题 环信_Android

lizg 回复了问题 • 3 人关注 • 1983 次浏览 • 2019-03-25 18:05 • 来自相关话题

1
回复

环信IOS关于iPhoneX空白问题 环信_iOS

kijieoeew 回复了问题 • 2 人关注 • 711 次浏览 • 2019-03-22 17:54 • 来自相关话题

1
回复

Android studio中的环信提示为303,怎么解决在线等 环信_Android

lizg 回复了问题 • 2 人关注 • 605 次浏览 • 2019-03-22 17:20 • 来自相关话题

0
评论

Android MVP架构从入门到精通-真枪实弹 Android MVP MVP Android IT大前端

serge 发表了文章 • 324 次浏览 • 2019-03-22 16:42 • 来自相关话题

一. 前言

你是否遇到过Activity/Fragment中成百上千行代码,完全无法维护,看着头疼?

你是否遇到过因后台接口还未写而你不能先写代码逻辑的情况?

你是否遇到过用MVC架构写的项目进行单元测试时的深深无奈?

如果你现在还是用MVC架构模式在写项目,请先转到MVP模式!

二. MVC架构

MVC架构模式最初生根于服务器端的Web开发,后来渐渐能够胜任客户端Web开发,再后来因Android项目由XML和Activity/Fragment组成,慢慢的Android开发者开始使用类似MVC的架构模式开发应用.





 
M层:模型层(model),主要是实体类,数据库,网络等存在的层面,model将新的数据发送到view层,用户得到数据响应.

V层:视图层(view),一般指XML为代表的视图界面.显示来源于model层的数据.用户的点击操作等事件从view层传递到controller层.

C层:控制层(controller),一般以Activity/Fragment为代表.C层主要是连接V层和M层的,C层收到V层发送过来的事件请求,从M层获取数据,展示给V层.

从上图可以看出M层和V层有连接关系,而Activity有时候既充当了控制层又充当了视图层,导致项目维护比较麻烦.
 
1. MVC架构优缺点
A. 缺点

M层和V层有连接关系,没有解耦,导致维护困难.

Activity/Fragment中的代码过多,难以维护.

Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,当Activity类业务过多的时候,会变得难以管理和维护.尤其是当UI的状态数据,跟持久化的数据混杂在一起,变得极为混乱.

B. 优点

控制层和View层都在Activity中进行操作,数据操作方便.

模块职责划分明确.主要划分层M,V,C三个模块.

三. MVP架构





 
MVP,即是Model,View,Presenter架构模式.看起来类似MVC,其实不然.从上图能看到Model层和View层没有相连接,完全解耦.

用户触碰界面触发事件,View层把事件通知Presenter层,Presenter层通知Model层处理这个事件,Model层处理后把结果发送到Presenter层,Presenter层再通知View层,最后View层做出改变.这是一整套流程.

M层:模型层(Model),此层和MVC中的M层作用类似.

V层:视图层(View),在MVC中V层只包含XML文件,而MVP中V层包含XML,Activity和Fragment三者.理论上V层不涉及任何逻辑,只负责界面的改变,尽量把逻辑处理放到M层.

P层:通知层(Presenter),P层的主要作用就是连接V层和M层,起到一个通知传递数据的作用.

1. MVP架构优缺点
A. 缺点

MVP中接口过多.

每一个功能,相比于MVC要多写好几个文件.

如果某一个界面中需要请求多个服务器接口,这个界面文件中会实现很多的回调接口,导致代码繁杂.

如果更改了数据源和请求中参数,会导致更多的代码修改.

额外的代码复杂度及学习成本.

B. 优点

模块职责划分明显,层次清晰,接口功能清晰.

Model层和View层分离,解耦.修改View而不影响Model.

功能复用度高,方便.一个Presenter可以复用于多个View,而不用更改Presenter的逻辑.

有利于测试驱动开发,以前的Android开发是难以进行单元测试.

如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能.

四. MVP架构实战(真枪实弹)
1. MVP三层代码简单书写

接下来笔者从简到繁,一点一点的堆砌MVP的整个架构.先看一下XML布局,布局中一个Button按钮和一个TextView控件,用户点击按钮后,Presenter层通知Model层请求处理网络数据,处理后Model层把结果数据发送给Presenter层,Presenter层再通知View层,然后View层改变TextView显示的内容.
 





 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:context=".view.SingleInterfaceActivity">

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100px"
android:text="请点击上方按钮获取数据" />
</LinearLayout>
接下来是Activity代码,里面就是获取Button和TextView控件,然后对Button做监听,先简单的这样写,一会慢慢的增加代码.
 
public class SingleInterfaceActivity extends AppCompatActivity {

private Button button;
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});

}
}
下面是Model层代码.本次网络请求用的是wanandroid网站的开放api,其中的文章首页列表接口.SingleInterfaceModel文件里面有一个方法getData,第一个参数curPage意思是获取第几页的数据,第二个参数callback是Model层通知Presenter层的回调.
 
public class SingleInterfaceModel {

public void getData(int curPage, final Callback callback) {
NetUtils.getRetrofit()
.create(Api.class)
.getData(curPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArticleListBean>() {
@Override
public void onCompleted() {
LP.w("completed");
}

@Override
public void onError(Throwable e) {
callback.onFail("出现错误");
}

@Override
public void onNext(ArticleListBean bean) {
if (null == bean) {
callback.onFail("出现错误");
} else if (bean.errorCode != 0) {
callback.onFail(bean.errorMsg);
} else {
callback.onSuccess(bean);
}
}
});
}
}Callback文件内容如下.里面一个成功一个失败的回调接口,参数全是泛型,为啥使用泛型笔者就不用说了吧.
 
public interface Callback<K, V> {
void onSuccess(K data);

void onFail(V data);
}再接下来是Presenter层的代码.SingleInterfacePresenter类构造函数中直接new了一个Model层对象,用于Presenter层对Model层的调用.然后SingleInterfacePresenter类的方法getData用于与Model的互相连接.
 
public class SingleInterfacePresenter {
private final SingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码

}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码

}
});
}
}至此,MVP三层简单的部分代码算是完成.那么怎样进行整个流程的相互调用呢.我们把刚开始的SingleInterfaceActivity代码改一下,让SingleInterfaceActivity持有Presenter层的对象,这样View层就可以调用Presenter层了.修改后代码如下.
 
public class SingleInterfaceActivity extends AppCompatActivity {

private Button button;
private TextView textView;
private SingleInterfacePresenter singleInterfacePresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

singleInterfacePresenter = new SingleInterfacePresenter();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
singleInterfacePresenter.getData(0);
}
});

}
}从以上所有代码可以看出,当用户点击按钮后,View层按钮的监听事件执行调用了Presenter层对象的getData方法,此时,Presenter层对象的getData方法调用了Model层对象的getData方法,Model层对象的getData方法中执行了网络请求和逻辑处理,把成功或失败的结果通过Callback接口回调给了Presenter层,然后Presenter层再通知View层改变界面.但此时SingleInterfacePresenter类中收到Model层的结果后无法通知View层,因为SingleInterfacePresenter未持有View层的对象.如下代码的注释中有说明.(如果此时点击按钮,下方代码LP.w()处会打印出网络请求成功的log)
 
public class SingleInterfacePresenter {
private final SingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码

}
});
}
}代码写到这里,笔者先把这些代码提交到github(https://github.com/serge66/MVPDemo),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"第一次修改"克隆此时的代码.

2. P层V层沟通桥梁

现在P层未持有V层对象,不能通知V层改变界面,那么就继续演变MVP架构.
在MVP架构中,我们要为每个Activity/Fragment写一个接口,这个接口需要让Presenter层持有,P层通过这个接口去通知V层更改界面.接口中包含了成功和失败的回调,这个接口Activity/Fragment要去实现,最终P层才能通知V层.
 
public interface SingleInterfaceIView {
void showArticleSuccess(ArticleListBean bean);

void showArticleFail(String errorMsg);
}一个完整的项目以后肯定会有许多功能界面,那么我们应该抽出一个IView公共接口,让所有的Activity/Fragment都间接实现它.IVew公共接口是用于给View层的接口继承的,注意,不是View本身继承.因为它定义的是接口的规范, 而其他接口才是定义的类的规范(这句话请仔细理解).
public interface IView {
}这个接口中可以写一些所有Activigy/Fragment共用的方法,我们把SingleInterfaceIView继承IView接口.
public interface SingleInterfaceIView extends IView {
void showArticleSuccess(ArticleListBean bean);

void showArticleFail(String errorMsg);
}同理Model层和Presenter层也是如此.
public interface IModel {
}
public interface IPresenter {
}现在项目中Model层是一个SingleInterfaceModel类,这个类对象被P层持有,对于面向对象设计来讲,利用接口达到解耦目的已经人尽皆知,那我们就要对SingleInterfaceModel类再写一个可继承的接口.代码如下.
public interface ISingleInterfaceModel extends IModel {
void getData(int curPage, final Callback callback);
}如此,SingleInterfaceModel类的修改如下.
 
public class SingleInterfaceModel implements ISingleInterfaceModel {

@Override
public void getData(int curPage, final Callback callback) {
NetUtils.getRetrofit()
.create(Api.class)
.getData(curPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArticleListBean>() {
@Override
public void onCompleted() {
LP.w("completed");
}

@Override
public void onError(Throwable e) {
callback.onFail("出现错误");
}

@Override
public void onNext(ArticleListBean bean) {
if (null == bean) {
callback.onFail("出现错误");
} else if (bean.errorCode != 0) {
callback.onFail(bean.errorMsg);
} else {
callback.onSuccess(bean);
}
}
});
}
}同理,View层持有P层对象,我们也需要对P层进行改造.但是下面的代码却没有像ISingleInterfaceModel接口继承IModel一样继承IPresenter,这点需要注意,笔者把IPresenter的继承放在了其他处,后面会讲解.
 
public interface ISingleInterfacePresenter {
void getData(int curPage);
}
然后SingleInterfacePresenter类的修改如下:
 
public class SingleInterfacePresenter implements ISingleInterfacePresenter {
private final ISingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

@Override
public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码
LP.w(errorMsg);
}
});
}
}3. 生命周期适配

至此,MVP三层每层的接口都写好了.但是P层连接V层的桥梁还没有搭建好,这个慢慢来,一个好的高楼大厦都是一步一步建造的.上面IPresenter接口我们没有让其他类继承,接下来就讲下这个.P层和V层相连接,V层的生命周期也要适配到P层,P层的每个功能都要适配生命周期,这里可以把生命周期的适配放在IPresenter接口中.P层持有V层对象,这里把它放到泛型中.代码如下.
 
public interface IPresenter<T extends IView> {

    /**
     * 依附生命view
     *
     * @param view
     */
    void attachView(T view);

    /**
     * 分离View
     */
    void detachView();

    /**
     * 判断View是否已经销毁
     *
     * @return
     */
    boolean isViewAttached();

}
 
这个IPresenter接口需要所有的P层实现类继承,对于生命周期这部分功能都是通用的,那么就可以抽出来一个抽象基类BasePresenter,去实现IPresenter的接口.
public abstract class BasePresenter<T extends IView> implements IPresenter<T> {
protected T mView;

@Override
public void attachView(T view) {
mView = view;
}

@Override
public void detachView() {
mView = null;
}

@Override
public boolean isViewAttached() {
return mView != null;
}
}此时,SingleInterfacePresenter类的代码修改如下.泛型中的SingleInterfaceIView可以理解成对应的Activity,P层此时完成了对V层的通信.
public class SingleInterfacePresenter extends BasePresenter<SingleInterfaceIView> implements ISingleInterfacePresenter {
private final ISingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

@Override
public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
if (isViewAttached()) {
mView.showArticleSuccess(loginResultBean);
}
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码
LP.w(errorMsg);
if (isViewAttached()) {
mView.showArticleFail(errorMsg);
}
}
});
}
}此时,P层和V层的连接桥梁已经搭建,但还未搭建完成,我们需要写个BaseMVPActvity让所有的Activity继承,统一处理Activity相同逻辑.在BaseMVPActvity中使用IPresenter的泛型,因为每个Activity中需要持有P层对象,这里把P层对象抽出来也放在BaseMVPActvity中.同时BaseMVPActvity中也需要继承IView,用于P层对V层的生命周期中.代码如下.
 
public abstract class BaseMVPActivity<T extends IPresenter> extends AppCompatActivity implements IView {

protected T mPresenter;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initPresenter();
init();
}

protected void initPresenter() {
mPresenter = createPresenter();
//绑定生命周期
if (mPresenter != null) {
mPresenter.attachView(this);
}
}

@Override
protected void onDestroy() {
if (mPresenter != null) {
mPresenter.detachView();
}
super.onDestroy();
}

/**
* 创建一个Presenter
*
* @return
*/
protected abstract T createPresenter();

protected abstract void init();

}接下来让SingleInterfaceActivity实现这个BaseMVPActivity.
public class SingleInterfaceActivity extends BaseMVPActivity<SingleInterfacePresenter> implements SingleInterfaceIView {

private Button button;
private TextView textView;

@Override
protected void init() {
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData(0);
}
});
}

@Override
protected SingleInterfacePresenter createPresenter() {
return new SingleInterfacePresenter();
}


@Override
public void showArticleSuccess(ArticleListBean bean) {
textView.setText(bean.data.datas.get(0).title);
}

@Override
public void showArticleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}到此,MVP架构的整个简易流程完成.

代码写到这里,笔者先把这些代码提交到github(https://github.com/serge66/MVPDemo),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"第二次修改"克隆此时的代码.

4. 优化MVP架构





 
 
上面是MVP的目录,从目录中我们可以看到一个功能点(网络请求)MVP三层各有两个文件需要写,相对于MVC来说写起来确实麻烦,这也是一些人不愿意写MVP,宁愿用MVC的原因.

这里我们可以对此优化一下.MVP架构中有个Contract的概念,Contract有统一管理接口的作用,目的是为了统一管理一个页面的View和Presenter接口,用Contract可以减少部分文件的创建,比如P层和V层的接口文件.

那我们就把P层的ISingleInterfacePresenter接口和V层的SingleInterfaceIView接口文件删除掉,放入SingleInterfaceContract文件中.代码如下.
 
public interface SingleInterfaceContract {


interface View extends IView {
void showArticleSuccess(ArticleListBean bean);

void showArticleFail(String errorMsg);
}

interface Presenter {
void getData(int curPage);
}


}此时,SingleInterfacePresenter和SingleInterfaceActivity的代码修改如下.
 
public class SingleInterfacePresenter extends BasePresenter<SingleInterfaceContract.View>
implements SingleInterfaceContract.Presenter {

private final ISingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

@Override
public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
if (isViewAttached()) {
mView.showArticleSuccess(loginResultBean);
}
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码
LP.w(errorMsg);
if (isViewAttached()) {
mView.showArticleFail(errorMsg);
}
}
});
}
}
public class SingleInterfaceActivity extends BaseMVPActivity<SingleInterfacePresenter>
implements SingleInterfaceContract.View {

private Button button;
private TextView textView;

@Override
protected void init() {
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData(0);
}
});
}

@Override
protected SingleInterfacePresenter createPresenter() {
return new SingleInterfacePresenter();
}


@Override
public void showArticleSuccess(ArticleListBean bean) {
textView.setText(bean.data.datas.get(0).title);
}

@Override
public void showArticleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
代码写到这里,笔者先把这些代码提交到github(https://github.com/serge66/MVPDemo),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"第三次修改"克隆此时的代码.

5. 单页面多网络请求以及P层复用

上面的MVP封装只适用于单页面一个网络请求的情况,当一个界面有两个网络请求时,此封装已不适合.以及考虑到P层的复用.为此,我们再次新建一个MultipleInterfaceActivity来进行说明.XML中布局是两个按钮两个Textview,点击则可以进行网络请求.





 
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:context=".view.MultipleInterfaceActivity">

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50px"
android:text="请点击上方按钮获取数据" />

<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100px"
android:text="点击" />

<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50px"
android:text="请点击上方按钮获取数据" />
</LinearLayout>MultipleInterfaceActivity类代码暂时如下.
 
public class MultipleInterfaceActivity extends BaseMVPActivity {

private Button button;
private TextView textView;
private Button btn;
private TextView tv;


@Override
protected void init() {
setContentView(R.layout.activity_multiple_interface);

button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});


btn = findViewById(R.id.btn);
tv = findViewById(R.id.tv);

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});
}

@Override
protected IPresenter createPresenter() {
return null;
}

}此时我们可以想下,当一个页面中有多个网络请求时,Activity所继承的BaseMVPActivity的泛型中要写多个参数,那有没有上面代码的框架不变的情况下实现这个需求呢?答案必须有的.我们可以把多个网络请求的功能当做一个网络请求来看待,封装成一个MultiplePresenter,其继承至BasePresenter实现生命周期的适配.此MultiplePresenter类的作用就是容纳多个Presenter,连接同一个View.代码如下.
 
public class MultiplePresenter<T extends IView> extends BasePresenter<T> {
private T mView;

private List<IPresenter> presenters = new ArrayList<>();

@SafeVarargs
public final <K extends IPresenter<T>> void addPresenter(K... addPresenter) {
for (K ap : addPresenter) {
ap.attachView(mView);
presenters.add(ap);
}
}

public MultiplePresenter(T mView) {
this.mView = mView;
}

@Override
public void detachView() {
for (IPresenter presenter : presenters) {
presenter.detachView();
}
}

}因MultiplePresenter类中需要有多个网络请求,现在举例说明时,暂时用两个网络请求接口.MultipleInterfaceActivity类中代码改造如下.
 
public class MultipleInterfaceActivity extends BaseMVPActivity<MultiplePresenter>
implements SingleInterfaceContract.View, MultipleInterfaceContract.View {

private Button button;
private TextView textView;
private Button btn;
private TextView tv;
private SingleInterfacePresenter singleInterfacePresenter;
private MultipleInterfacePresenter multipleInterfacePresenter;


@Override
protected void init() {
setContentView(R.layout.activity_multiple_interface);

button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
singleInterfacePresenter.getData(0);
}
});


btn = findViewById(R.id.btn);
tv = findViewById(R.id.tv);

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
multipleInterfacePresenter.getBanner();
}
});
}

@Override
protected MultiplePresenter createPresenter() {
MultiplePresenter multiplePresenter = new MultiplePresenter(this);

singleInterfacePresenter = new SingleInterfacePresenter();
multipleInterfacePresenter = new MultipleInterfacePresenter();

multiplePresenter.addPresenter(singleInterfacePresenter);
multiplePresenter.addPresenter(multipleInterfacePresenter);
return multiplePresenter;
}

@Override
public void showArticleSuccess(ArticleListBean bean) {
textView.setText(bean.data.datas.get(0).title);
}

@Override
public void showArticleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}

@Override
public void showMultipleSuccess(BannerBean bean) {
tv.setText(bean.data.get(0).title);
}

@Override
public void showMultipleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}写到这里,MVP框架基本算是完成.如果想再次优化,其实还是有可优化的地方,比如当View销毁时,现在只是让P层中的View对象置为null,并没有继续对M层通知.如果View销毁时,M层还在请求网络中呢,可以为此再加入一个取消网络请求的通用功能.这里只是举一个例子,每个人对MVP的理解不一样,而MVP架构也并不是一成不变,适合自己项目的才是最好的.

6. 完整项目地址

完整项目已提交到github(https://github.com/serge66/MVPDemo).点击下方阅读原文即可访问.

五. 参考资料

[一步步带你精通MVP](https://mp.weixin.qq.com/s/DuNbl3V4gZY-ZCETbhZGug)

[从0到1搭建MVP框架](https://mp.weixin.qq.com/s/QFpHhC-5JkAb4IlMP0nKug)

[Presenter层如何高度的复用](https://juejin.im/post/599ce8016fb9a0247e4255f4)
 
六. 后续

MVVM架构从入门到精通-真枪实弹 敬请期待~~~
 





微信公众号:IT大前端
关注可了解更多的大前端领域技术
  查看全部

0.jpeg

一. 前言

你是否遇到过Activity/Fragment中成百上千行代码,完全无法维护,看着头疼?

你是否遇到过因后台接口还未写而你不能先写代码逻辑的情况?

你是否遇到过用MVC架构写的项目进行单元测试时的深深无奈?

如果你现在还是用MVC架构模式在写项目,请先转到MVP模式!

二. MVC架构

MVC架构模式最初生根于服务器端的Web开发,后来渐渐能够胜任客户端Web开发,再后来因Android项目由XML和Activity/Fragment组成,慢慢的Android开发者开始使用类似MVC的架构模式开发应用.

0_(1).jpeg

 
M层:模型层(model),主要是实体类,数据库,网络等存在的层面,model将新的数据发送到view层,用户得到数据响应.

V层:视图层(view),一般指XML为代表的视图界面.显示来源于model层的数据.用户的点击操作等事件从view层传递到controller层.

C层:控制层(controller),一般以Activity/Fragment为代表.C层主要是连接V层和M层的,C层收到V层发送过来的事件请求,从M层获取数据,展示给V层.

从上图可以看出M层和V层有连接关系,而Activity有时候既充当了控制层又充当了视图层,导致项目维护比较麻烦.
 
1. MVC架构优缺点
A. 缺点

M层和V层有连接关系,没有解耦,导致维护困难.

Activity/Fragment中的代码过多,难以维护.

Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,当Activity类业务过多的时候,会变得难以管理和维护.尤其是当UI的状态数据,跟持久化的数据混杂在一起,变得极为混乱.

B. 优点

控制层和View层都在Activity中进行操作,数据操作方便.

模块职责划分明确.主要划分层M,V,C三个模块.

三. MVP架构

0_(2).jpeg

 
MVP,即是Model,View,Presenter架构模式.看起来类似MVC,其实不然.从上图能看到Model层和View层没有相连接,完全解耦.

用户触碰界面触发事件,View层把事件通知Presenter层,Presenter层通知Model层处理这个事件,Model层处理后把结果发送到Presenter层,Presenter层再通知View层,最后View层做出改变.这是一整套流程.

M层:模型层(Model),此层和MVC中的M层作用类似.

V层:视图层(View),在MVC中V层只包含XML文件,而MVP中V层包含XML,Activity和Fragment三者.理论上V层不涉及任何逻辑,只负责界面的改变,尽量把逻辑处理放到M层.

P层:通知层(Presenter),P层的主要作用就是连接V层和M层,起到一个通知传递数据的作用.

1. MVP架构优缺点
A. 缺点

MVP中接口过多.

每一个功能,相比于MVC要多写好几个文件.

如果某一个界面中需要请求多个服务器接口,这个界面文件中会实现很多的回调接口,导致代码繁杂.

如果更改了数据源和请求中参数,会导致更多的代码修改.

额外的代码复杂度及学习成本.

B. 优点

模块职责划分明显,层次清晰,接口功能清晰.

Model层和View层分离,解耦.修改View而不影响Model.

功能复用度高,方便.一个Presenter可以复用于多个View,而不用更改Presenter的逻辑.

有利于测试驱动开发,以前的Android开发是难以进行单元测试.

如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能.

四. MVP架构实战(真枪实弹)
1. MVP三层代码简单书写

接下来笔者从简到繁,一点一点的堆砌MVP的整个架构.先看一下XML布局,布局中一个Button按钮和一个TextView控件,用户点击按钮后,Presenter层通知Model层请求处理网络数据,处理后Model层把结果数据发送给Presenter层,Presenter层再通知View层,然后View层改变TextView显示的内容.
 

0.gif

 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:context=".view.SingleInterfaceActivity">

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100px"
android:text="请点击上方按钮获取数据" />
</LinearLayout>

接下来是Activity代码,里面就是获取Button和TextView控件,然后对Button做监听,先简单的这样写,一会慢慢的增加代码.
 
public class SingleInterfaceActivity extends AppCompatActivity {

private Button button;
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});

}
}

下面是Model层代码.本次网络请求用的是wanandroid网站的开放api,其中的文章首页列表接口.SingleInterfaceModel文件里面有一个方法getData,第一个参数curPage意思是获取第几页的数据,第二个参数callback是Model层通知Presenter层的回调.
 
public class SingleInterfaceModel {

public void getData(int curPage, final Callback callback) {
NetUtils.getRetrofit()
.create(Api.class)
.getData(curPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArticleListBean>() {
@Override
public void onCompleted() {
LP.w("completed");
}

@Override
public void onError(Throwable e) {
callback.onFail("出现错误");
}

@Override
public void onNext(ArticleListBean bean) {
if (null == bean) {
callback.onFail("出现错误");
} else if (bean.errorCode != 0) {
callback.onFail(bean.errorMsg);
} else {
callback.onSuccess(bean);
}
}
});
}
}
Callback文件内容如下.里面一个成功一个失败的回调接口,参数全是泛型,为啥使用泛型笔者就不用说了吧.
 
public interface Callback<K, V> {
void onSuccess(K data);

void onFail(V data);
}
再接下来是Presenter层的代码.SingleInterfacePresenter类构造函数中直接new了一个Model层对象,用于Presenter层对Model层的调用.然后SingleInterfacePresenter类的方法getData用于与Model的互相连接.
 
public class SingleInterfacePresenter {
private final SingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码

}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码

}
});
}
}
至此,MVP三层简单的部分代码算是完成.那么怎样进行整个流程的相互调用呢.我们把刚开始的SingleInterfaceActivity代码改一下,让SingleInterfaceActivity持有Presenter层的对象,这样View层就可以调用Presenter层了.修改后代码如下.
 
public class SingleInterfaceActivity extends AppCompatActivity {

private Button button;
private TextView textView;
private SingleInterfacePresenter singleInterfacePresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

singleInterfacePresenter = new SingleInterfacePresenter();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
singleInterfacePresenter.getData(0);
}
});

}
}
从以上所有代码可以看出,当用户点击按钮后,View层按钮的监听事件执行调用了Presenter层对象的getData方法,此时,Presenter层对象的getData方法调用了Model层对象的getData方法,Model层对象的getData方法中执行了网络请求和逻辑处理,把成功或失败的结果通过Callback接口回调给了Presenter层,然后Presenter层再通知View层改变界面.但此时SingleInterfacePresenter类中收到Model层的结果后无法通知View层,因为SingleInterfacePresenter未持有View层的对象.如下代码的注释中有说明.(如果此时点击按钮,下方代码LP.w()处会打印出网络请求成功的log)
 
public class SingleInterfacePresenter {
private final SingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码

}
});
}
}
代码写到这里,笔者先把这些代码提交到github(https://github.com/serge66/MVPDemo),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"第一次修改"克隆此时的代码.

2. P层V层沟通桥梁

现在P层未持有V层对象,不能通知V层改变界面,那么就继续演变MVP架构.
在MVP架构中,我们要为每个Activity/Fragment写一个接口,这个接口需要让Presenter层持有,P层通过这个接口去通知V层更改界面.接口中包含了成功和失败的回调,这个接口Activity/Fragment要去实现,最终P层才能通知V层.
 
public interface SingleInterfaceIView {
void showArticleSuccess(ArticleListBean bean);

void showArticleFail(String errorMsg);
}
一个完整的项目以后肯定会有许多功能界面,那么我们应该抽出一个IView公共接口,让所有的Activity/Fragment都间接实现它.IVew公共接口是用于给View层的接口继承的,注意,不是View本身继承.因为它定义的是接口的规范, 而其他接口才是定义的类的规范(这句话请仔细理解).
public interface IView {
}
这个接口中可以写一些所有Activigy/Fragment共用的方法,我们把SingleInterfaceIView继承IView接口.
public interface SingleInterfaceIView extends IView {
void showArticleSuccess(ArticleListBean bean);

void showArticleFail(String errorMsg);
}
同理Model层和Presenter层也是如此.
public interface IModel {
}
public interface IPresenter {
}
现在项目中Model层是一个SingleInterfaceModel类,这个类对象被P层持有,对于面向对象设计来讲,利用接口达到解耦目的已经人尽皆知,那我们就要对SingleInterfaceModel类再写一个可继承的接口.代码如下.
public interface ISingleInterfaceModel extends IModel {
void getData(int curPage, final Callback callback);
}
如此,SingleInterfaceModel类的修改如下.
 
public class SingleInterfaceModel implements ISingleInterfaceModel {

@Override
public void getData(int curPage, final Callback callback) {
NetUtils.getRetrofit()
.create(Api.class)
.getData(curPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ArticleListBean>() {
@Override
public void onCompleted() {
LP.w("completed");
}

@Override
public void onError(Throwable e) {
callback.onFail("出现错误");
}

@Override
public void onNext(ArticleListBean bean) {
if (null == bean) {
callback.onFail("出现错误");
} else if (bean.errorCode != 0) {
callback.onFail(bean.errorMsg);
} else {
callback.onSuccess(bean);
}
}
});
}
}
同理,View层持有P层对象,我们也需要对P层进行改造.但是下面的代码却没有像ISingleInterfaceModel接口继承IModel一样继承IPresenter,这点需要注意,笔者把IPresenter的继承放在了其他处,后面会讲解.
 
public interface ISingleInterfacePresenter {
void getData(int curPage);
}

然后SingleInterfacePresenter类的修改如下:
 
public class SingleInterfacePresenter implements ISingleInterfacePresenter {
private final ISingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

@Override
public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码
LP.w(errorMsg);
}
});
}
}
3. 生命周期适配

至此,MVP三层每层的接口都写好了.但是P层连接V层的桥梁还没有搭建好,这个慢慢来,一个好的高楼大厦都是一步一步建造的.上面IPresenter接口我们没有让其他类继承,接下来就讲下这个.P层和V层相连接,V层的生命周期也要适配到P层,P层的每个功能都要适配生命周期,这里可以把生命周期的适配放在IPresenter接口中.P层持有V层对象,这里把它放到泛型中.代码如下.
 
public interface IPresenter<T extends IView> {

    /**
     * 依附生命view
     *
     * @param view
     */
    void attachView(T view);

    /**
     * 分离View
     */
    void detachView();

    /**
     * 判断View是否已经销毁
     *
     * @return
     */
    boolean isViewAttached();

}
 
这个IPresenter接口需要所有的P层实现类继承,对于生命周期这部分功能都是通用的,那么就可以抽出来一个抽象基类BasePresenter,去实现IPresenter的接口.
public abstract class BasePresenter<T extends IView> implements IPresenter<T> {
protected T mView;

@Override
public void attachView(T view) {
mView = view;
}

@Override
public void detachView() {
mView = null;
}

@Override
public boolean isViewAttached() {
return mView != null;
}
}
此时,SingleInterfacePresenter类的代码修改如下.泛型中的SingleInterfaceIView可以理解成对应的Activity,P层此时完成了对V层的通信.
public class SingleInterfacePresenter extends BasePresenter<SingleInterfaceIView> implements ISingleInterfacePresenter {
private final ISingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

@Override
public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
if (isViewAttached()) {
mView.showArticleSuccess(loginResultBean);
}
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码
LP.w(errorMsg);
if (isViewAttached()) {
mView.showArticleFail(errorMsg);
}
}
});
}
}
此时,P层和V层的连接桥梁已经搭建,但还未搭建完成,我们需要写个BaseMVPActvity让所有的Activity继承,统一处理Activity相同逻辑.在BaseMVPActvity中使用IPresenter的泛型,因为每个Activity中需要持有P层对象,这里把P层对象抽出来也放在BaseMVPActvity中.同时BaseMVPActvity中也需要继承IView,用于P层对V层的生命周期中.代码如下.
 
public abstract class BaseMVPActivity<T extends IPresenter> extends AppCompatActivity implements IView {

protected T mPresenter;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initPresenter();
init();
}

protected void initPresenter() {
mPresenter = createPresenter();
//绑定生命周期
if (mPresenter != null) {
mPresenter.attachView(this);
}
}

@Override
protected void onDestroy() {
if (mPresenter != null) {
mPresenter.detachView();
}
super.onDestroy();
}

/**
* 创建一个Presenter
*
* @return
*/
protected abstract T createPresenter();

protected abstract void init();

}
接下来让SingleInterfaceActivity实现这个BaseMVPActivity.
public class SingleInterfaceActivity extends BaseMVPActivity<SingleInterfacePresenter> implements SingleInterfaceIView {

private Button button;
private TextView textView;

@Override
protected void init() {
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData(0);
}
});
}

@Override
protected SingleInterfacePresenter createPresenter() {
return new SingleInterfacePresenter();
}


@Override
public void showArticleSuccess(ArticleListBean bean) {
textView.setText(bean.data.datas.get(0).title);
}

@Override
public void showArticleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
到此,MVP架构的整个简易流程完成.

代码写到这里,笔者先把这些代码提交到github(https://github.com/serge66/MVPDemo),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"第二次修改"克隆此时的代码.

4. 优化MVP架构

0_(3).jpeg

 
 
上面是MVP的目录,从目录中我们可以看到一个功能点(网络请求)MVP三层各有两个文件需要写,相对于MVC来说写起来确实麻烦,这也是一些人不愿意写MVP,宁愿用MVC的原因.

这里我们可以对此优化一下.MVP架构中有个Contract的概念,Contract有统一管理接口的作用,目的是为了统一管理一个页面的View和Presenter接口,用Contract可以减少部分文件的创建,比如P层和V层的接口文件.

那我们就把P层的ISingleInterfacePresenter接口和V层的SingleInterfaceIView接口文件删除掉,放入SingleInterfaceContract文件中.代码如下.
 
public interface SingleInterfaceContract {


interface View extends IView {
void showArticleSuccess(ArticleListBean bean);

void showArticleFail(String errorMsg);
}

interface Presenter {
void getData(int curPage);
}


}
此时,SingleInterfacePresenter和SingleInterfaceActivity的代码修改如下.
 
public class SingleInterfacePresenter extends BasePresenter<SingleInterfaceContract.View>
implements SingleInterfaceContract.Presenter {

private final ISingleInterfaceModel singleInterfaceModel;

public SingleInterfacePresenter() {
this.singleInterfaceModel = new SingleInterfaceModel();
}

@Override
public void getData(int curPage) {
singleInterfaceModel.getData(curPage, new Callback<ArticleListBean, String>() {
@Override
public void onSuccess(ArticleListBean loginResultBean) {
//如果Model层请求数据成功,则此处应执行通知View层的代码
//LP.w()是一个简单的log打印
LP.w(loginResultBean.toString());
if (isViewAttached()) {
mView.showArticleSuccess(loginResultBean);
}
}

@Override
public void onFail(String errorMsg) {
//如果Model层请求数据失败,则此处应执行通知View层的代码
LP.w(errorMsg);
if (isViewAttached()) {
mView.showArticleFail(errorMsg);
}
}
});
}
}

public class SingleInterfaceActivity extends BaseMVPActivity<SingleInterfacePresenter>
implements SingleInterfaceContract.View {

private Button button;
private TextView textView;

@Override
protected void init() {
setContentView(R.layout.activity_single_interface);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.getData(0);
}
});
}

@Override
protected SingleInterfacePresenter createPresenter() {
return new SingleInterfacePresenter();
}


@Override
public void showArticleSuccess(ArticleListBean bean) {
textView.setText(bean.data.datas.get(0).title);
}

@Override
public void showArticleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}

代码写到这里,笔者先把这些代码提交到github(https://github.com/serge66/MVPDemo),github上会有一次提交记录,如果想看此时的代码,可以根据提交记录"第三次修改"克隆此时的代码.

5. 单页面多网络请求以及P层复用

上面的MVP封装只适用于单页面一个网络请求的情况,当一个界面有两个网络请求时,此封装已不适合.以及考虑到P层的复用.为此,我们再次新建一个MultipleInterfaceActivity来进行说明.XML中布局是两个按钮两个Textview,点击则可以进行网络请求.

0_(1).gif

 
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:context=".view.MultipleInterfaceActivity">

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50px"
android:text="请点击上方按钮获取数据" />

<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100px"
android:text="点击" />

<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50px"
android:text="请点击上方按钮获取数据" />
</LinearLayout>
MultipleInterfaceActivity类代码暂时如下.
 
public class MultipleInterfaceActivity extends BaseMVPActivity {

private Button button;
private TextView textView;
private Button btn;
private TextView tv;


@Override
protected void init() {
setContentView(R.layout.activity_multiple_interface);

button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});


btn = findViewById(R.id.btn);
tv = findViewById(R.id.tv);

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});
}

@Override
protected IPresenter createPresenter() {
return null;
}

}
此时我们可以想下,当一个页面中有多个网络请求时,Activity所继承的BaseMVPActivity的泛型中要写多个参数,那有没有上面代码的框架不变的情况下实现这个需求呢?答案必须有的.我们可以把多个网络请求的功能当做一个网络请求来看待,封装成一个MultiplePresenter,其继承至BasePresenter实现生命周期的适配.此MultiplePresenter类的作用就是容纳多个Presenter,连接同一个View.代码如下.
 
public class MultiplePresenter<T extends IView> extends BasePresenter<T> {
private T mView;

private List<IPresenter> presenters = new ArrayList<>();

@SafeVarargs
public final <K extends IPresenter<T>> void addPresenter(K... addPresenter) {
for (K ap : addPresenter) {
ap.attachView(mView);
presenters.add(ap);
}
}

public MultiplePresenter(T mView) {
this.mView = mView;
}

@Override
public void detachView() {
for (IPresenter presenter : presenters) {
presenter.detachView();
}
}

}
因MultiplePresenter类中需要有多个网络请求,现在举例说明时,暂时用两个网络请求接口.MultipleInterfaceActivity类中代码改造如下.
 
public class MultipleInterfaceActivity extends BaseMVPActivity<MultiplePresenter>
implements SingleInterfaceContract.View, MultipleInterfaceContract.View {

private Button button;
private TextView textView;
private Button btn;
private TextView tv;
private SingleInterfacePresenter singleInterfacePresenter;
private MultipleInterfacePresenter multipleInterfacePresenter;


@Override
protected void init() {
setContentView(R.layout.activity_multiple_interface);

button = findViewById(R.id.button);
textView = findViewById(R.id.textView);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
singleInterfacePresenter.getData(0);
}
});


btn = findViewById(R.id.btn);
tv = findViewById(R.id.tv);

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
multipleInterfacePresenter.getBanner();
}
});
}

@Override
protected MultiplePresenter createPresenter() {
MultiplePresenter multiplePresenter = new MultiplePresenter(this);

singleInterfacePresenter = new SingleInterfacePresenter();
multipleInterfacePresenter = new MultipleInterfacePresenter();

multiplePresenter.addPresenter(singleInterfacePresenter);
multiplePresenter.addPresenter(multipleInterfacePresenter);
return multiplePresenter;
}

@Override
public void showArticleSuccess(ArticleListBean bean) {
textView.setText(bean.data.datas.get(0).title);
}

@Override
public void showArticleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}

@Override
public void showMultipleSuccess(BannerBean bean) {
tv.setText(bean.data.get(0).title);
}

@Override
public void showMultipleFail(String errorMsg) {
Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}
}
写到这里,MVP框架基本算是完成.如果想再次优化,其实还是有可优化的地方,比如当View销毁时,现在只是让P层中的View对象置为null,并没有继续对M层通知.如果View销毁时,M层还在请求网络中呢,可以为此再加入一个取消网络请求的通用功能.这里只是举一个例子,每个人对MVP的理解不一样,而MVP架构也并不是一成不变,适合自己项目的才是最好的.

6. 完整项目地址

完整项目已提交到github(https://github.com/serge66/MVPDemo).点击下方阅读原文即可访问.

五. 参考资料

[一步步带你精通MVP](https://mp.weixin.qq.com/s/DuNbl3V4gZY-ZCETbhZGug)

[从0到1搭建MVP框架](https://mp.weixin.qq.com/s/QFpHhC-5JkAb4IlMP0nKug)

[Presenter层如何高度的复用](https://juejin.im/post/599ce8016fb9a0247e4255f4)
 
六. 后续

MVVM架构从入门到精通-真枪实弹 敬请期待~~~
 

qrcode_for_gh_08bfa7313fb2_258.jpg

微信公众号:IT大前端
关注可了解更多的大前端领域技术
 
1
回复

安卓账号登陆在华为Mate20上崩溃 环信_Android

lizg 回复了问题 • 3 人关注 • 603 次浏览 • 2019-03-21 21:21 • 来自相关话题

1
回复

环信集成出现Error: Program type already present: com.hyphenate.EMCallBack怎么解决 Android 环信

lizg 回复了问题 • 2 人关注 • 552 次浏览 • 2019-03-21 19:19 • 来自相关话题

1
回复

More than one file was found with OS independent path 'lib/arm64-v8a/libhyphenate.so' 环信_Android EaseUI

lizg 回复了问题 • 2 人关注 • 592 次浏览 • 2019-03-20 18:13 • 来自相关话题

0
评论

在线直播源码实现直播技术曾遇到的那些小问题 源码 互联网+ 直播

q2466131704 发表了文章 • 351 次浏览 • 2019-03-19 17:12 • 来自相关话题

文章主要内容:在直播过程经常会遇到哪些问题?在线直播源码是怎样实现相应的直播技术的?这些问题的产生是由怎样的原因导致的?
以下这些问题,我相信都是直播中十分常见,并且具有一定参考性的问题。大家可以通过以下内容寻找对应的问题和原因,希望能给大家产生一定的帮助。
1.播放失败:服务器连接失败、域名解析失败、只有音频没有视频、只有视频没有音频。
2.直播出现卡顿:(1)主播端网络不好,导致推流上行不稳定。(2)服务端线路质量差,造成分发不稳定。(3)用户端网络质量差,从而拉流下行不稳定。
3.延时高:网络传输延时、协议延时、业务代码中的缓冲区。
4.音画不同步:(应从视频直播的生产端进行排查)采集设备内部出现问题、时间戳没有在采集时被获取、采集源距离太远、时间戳出现回退或紊乱现象、播放端的性能问题。
5.马赛克:图像尺寸原因、视频编码参数配置原因、关键帧丢失。
6.播放黑屏:主播端编码失效、视频编码失效、码流前半段只有音频没有视频。
7.播放花屏:播放器没有从关键帧开始解码、码流中的视频尺寸发生变化、丢失参考帧、硬编解兼容性问题、推流端的图像尺寸格式。
8.播放闪屏:推流端原因、播放器缓冲机制原因。
9.播放杂音(回声):网络波动、回声消除、参数配置、混音越界。
10.拖动不准:直播过程中丢帧、关键帧间隔太大。
11.CPU/GPU占用率高:数据量大、格式转换、软编解格式。
12.在直播过程中,决定视频预加载效果的好坏主要由:视频的码率、缓冲文件大小和网速决定。
原因:网速快且码率低的情况下,不需要使用预加载。(码率中等且网速一般的情况适用)需要注意的是:缓冲文件不能设置过大,会影响正常播放。
12.为什么播放视频时,会停留在第一帧画面。
原因:(1)解码器出现错误,只接出了第一帧图像。(2)没有接收到视频帧。(3)时间戳的计算有误。
   以上内容简单总结了直播中经常出现的问题及原因,那么在文章的结尾,想给大家举个简单的例子,比如盖楼需要混凝土和砖;种树需要土壤和水;养鱼需要水和饲料,开发一个直播平台就需要在线直播源码。源码就是开发的基础,没有源码就无法完成。所以,选择优质的源码也是开发过程中十分重要的一步。
本文声明原创,转载请注明出处。 查看全部
文章主要内容:在直播过程经常会遇到哪些问题?在线直播源码是怎样实现相应的直播技术的?这些问题的产生是由怎样的原因导致的?
以下这些问题,我相信都是直播中十分常见,并且具有一定参考性的问题。大家可以通过以下内容寻找对应的问题和原因,希望能给大家产生一定的帮助。
1.播放失败:服务器连接失败、域名解析失败、只有音频没有视频、只有视频没有音频。
2.直播出现卡顿:(1)主播端网络不好,导致推流上行不稳定。(2)服务端线路质量差,造成分发不稳定。(3)用户端网络质量差,从而拉流下行不稳定。
3.延时高:网络传输延时、协议延时、业务代码中的缓冲区。
4.音画不同步:(应从视频直播的生产端进行排查)采集设备内部出现问题、时间戳没有在采集时被获取、采集源距离太远、时间戳出现回退或紊乱现象、播放端的性能问题。
5.马赛克:图像尺寸原因、视频编码参数配置原因、关键帧丢失。
6.播放黑屏:主播端编码失效、视频编码失效、码流前半段只有音频没有视频。
7.播放花屏:播放器没有从关键帧开始解码、码流中的视频尺寸发生变化、丢失参考帧、硬编解兼容性问题、推流端的图像尺寸格式。
8.播放闪屏:推流端原因、播放器缓冲机制原因。
9.播放杂音(回声):网络波动、回声消除、参数配置、混音越界。
10.拖动不准:直播过程中丢帧、关键帧间隔太大。
11.CPU/GPU占用率高:数据量大、格式转换、软编解格式。
12.在直播过程中,决定视频预加载效果的好坏主要由:视频的码率、缓冲文件大小和网速决定。
原因:网速快且码率低的情况下,不需要使用预加载。(码率中等且网速一般的情况适用)需要注意的是:缓冲文件不能设置过大,会影响正常播放。
12.为什么播放视频时,会停留在第一帧画面。
原因:(1)解码器出现错误,只接出了第一帧图像。(2)没有接收到视频帧。(3)时间戳的计算有误。
   以上内容简单总结了直播中经常出现的问题及原因,那么在文章的结尾,想给大家举个简单的例子,比如盖楼需要混凝土和砖;种树需要土壤和水;养鱼需要水和饲料,开发一个直播平台就需要在线直播源码。源码就是开发的基础,没有源码就无法完成。所以,选择优质的源码也是开发过程中十分重要的一步。
本文声明原创,转载请注明出处。
0
回复

如何进行二维码的扫码渠道来源监测? 二维码扫码统计

回复

whb2010 发起了问题 • 1 人关注 • 752 次浏览 • 2019-03-19 16:19 • 来自相关话题

0
回复

微信公众号关注来源统计查询谁会搞? 公众号关注来源

回复

whb2010 发起了问题 • 1 人关注 • 728 次浏览 • 2019-03-18 17:26 • 来自相关话题

0
回复

iOSapp上架有哪些方法 审核 马甲包 ios上架

回复

THX 发起了问题 • 1 人关注 • 598 次浏览 • 2019-03-15 12:36 • 来自相关话题

4
回复

获取登录用户的环信Id 环信技术支持

。。。 回复了问题 • 4 人关注 • 6496 次浏览 • 2019-03-15 11:40 • 来自相关话题

0
回复

识别二维码群发海报兑换功能怎么实现? 生成二维码海报

回复

whb2010 发起了问题 • 1 人关注 • 568 次浏览 • 2019-03-14 14:29 • 来自相关话题

1
回复
0
回复

公众号的二维码怎么生成? 公众号的二维码怎么生成

回复

whb2010 发起了问题 • 1 人关注 • 715 次浏览 • 2019-03-13 11:23 • 来自相关话题

2
回复

webIM 发送视频消息 失败 环信_WebIM 有专职工程师值守

lizg 回复了问题 • 3 人关注 • 1411 次浏览 • 2019-03-12 16:58 • 来自相关话题

3
回复

Android多人会议问题 环信_Android

lizg 回复了问题 • 2 人关注 • 737 次浏览 • 2019-03-12 16:53 • 来自相关话题

2
最佳

Swift项目红包集成是个什么流程?不使用EaseUI能集成红包吗? 环信_iOS

skoxe 回复了问题 • 2 人关注 • 1399 次浏览 • 2019-03-11 18:53 • 来自相关话题

1
回复

iOS环信聊天界面在view上怎么实现? iOS,环信聊天界面

berylwang 回复了问题 • 2 人关注 • 618 次浏览 • 2019-03-11 18:45 • 来自相关话题

1
回复

环信web端登录报错 failed: Data frame received after close 环信_WebIM 环信

lizg 回复了问题 • 2 人关注 • 670 次浏览 • 2019-03-11 18:00 • 来自相关话题

1
回复

android视频打开问题 环信_Android 视频

lizg 回复了问题 • 2 人关注 • 631 次浏览 • 2019-03-11 17:59 • 来自相关话题