MVC(Model(模型)——View(视图)——Controller(控制器))
Android中MVC的角色定义如下:
简单来说MVC就是通过Controller层操作Model层数据,并且返回给VIew展示。
[外链图片转存中…(img-oi8sABEO-1702569842215)]
MVC的缺点:
MVP(Model——View——presenter)它是MVC的演化版本,MPV角色定义如下:
主要的控制逻辑在Presenter里实现,而且Presenter与具体的View是没有直接关联的,而是通过定义好的接口交互。这样使得View变更时Presenter不会受到影响。绝不允许View直接访问Model,这就是它和MVC的不同之处。
导入需要的相关依赖:
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'org.projectlombok:lombok:1.18.30'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.bumptech.glide:glide:4.16.0'
接下来设置网络请求权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
我们使用这个免费的Api获取一首网易热门歌曲
https://api.uomg.com/api/rand.music?sort=热歌榜&format=json
JSON数据结构如下所示:
{
"code": 1,
"data": {
"name": "晚安",
"url": "http://music.163.com/song/media/outer/url?id=1359356908",
"picurl": "http://p1.music.126.net/8N1fsMRm2L5HyZccc6I3ew==/109951164007377169.jpg",
"artistsname": "颜人中"
}
}
先看一下完整的项目目录结构:
[外链图片转存中…(img-pjbexowu-1702569842216)]
创建一个musicData用于存储转化后的JSON数据:
@NoArgsConstructor
@Data
public class musicData {
@SerializedName("data")
private DataDTO data;
@NoArgsConstructor
@Data
public static class DataDTO {
@SerializedName("name")
private String name;
@SerializedName("url")
private String url;
@SerializedName("picurl")
private String picurl;
@SerializedName("artistsname")
private String artistsname;
public String getName() {
return name;
}
public String getUrl() {
return url;
}
public String getPicurl() {
return picurl;
}
public String getArtistsname() {
return artistsname;
}
}
public DataDTO getData() {
return data;
}
}`
定义获取网络数据的接口类NetTask:
public interface NetTask<T> {
void execute(T data, LoadTasksCallBack callBack);
}
这里有一个回调接口LoadTasksCallBack用于监听网络访问回调的各种状态:
public interface LoadTasksCallBack<T> {
/**
* 网络请求成功
*
* @param data:数据仓库,存放解析后的数据
*/
void onSuccess(T data);
/**
*网络请求失败
*/
void onFailed();
}
接下来我们编写NetTask的实现类以获取数据,如下所示:
public class MusicInfoTask implements NetTask<String> {
private static MusicInfoTask INSTANCE = null;
private static final String HOST = "https://api.uomg.com/api/rand.music?sort=%E7%83%AD%E6%AD%8C%E6%A6%9C&format=json";
private LoadTasksCallBack loadTasksCallBack;
public static MusicInfoTask getInstance(){
if (INSTANCE == null){
INSTANCE = new MusicInfoTask();
}
return INSTANCE;
}
@Override
public void execute(String data, final LoadTasksCallBack callBack) {
//这里展示一下传入的data数据,HOSP是写死的,没有拼接data,可以自行实现使用data重新拼接。
Log.e("TAG", "execute: "+data);
sendOkHttpRequest(HOST,new okhttp3.Callback(){
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful()){
String responseBody = response.body().string();
Gson gson = new Gson();
musicData musicData = gson.fromJson(responseBody, musicData.class);
callBack.onSuccess(musicData);
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callBack.onFailed();
}
});
}
/**
* 发送网络请求
*
* @param address
* @param callback
*/
public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
MusicInfoTask是一个单例模式,在execute方法中通过OKHttp来获取数据,同时在okhttp3.Callback中回调自定义的LoadTasksCallBack。
通过时序图理解execute方法是如何做的:
我们首先定义一个契约接口MusicInfoContract
用来存放具有相同业务的Presenter和View的接口,便于查找和维护。代码如下:
public interface MusicInfoContract {
interface Presenter {
void getMusiInfo(String ip);
}
/**
* 视图需要的功能
*
* @author lukecc0
* @date 2023/12/13
*/
interface View extends BaseView<Presenter> {
void setMusicData(musicData musicData);
void showError();
/**
* 用于判断Fragment是否成功加入到Activity
*
* @return {@link Boolean}
*/
Boolean isACtive();
}
}
在这里看见Presenter接口定义了获取数据的方法,而View接口定义了与界面交互的方法。其中isActive方法用于判断Fragment是否加入到了Activity中。
另外,View接口基础于BaseView接口,BaseView接口如下所示:
public interface BaseView<T>{
/**
* 为视图绑定对应的presenter
*
* @param presenter
*/
void setPresenter(T presenter);
}
BaseView接口的目的就是为View绑定对应的Presenter。它作为一个View的管理类,为每一个View实现绑定方法。
接着实现Presenter接口,如下所示:
public class MusicInfoPresenter implements MusicInfoContract.Presenter, LoadTasksCallBack<musicData> {
private NetTask netTask;
private MusicInfoContract.View addTaskView;
/**
* @param addTaskView 是我们传入的View用于实现Presenter绑定View,然后我们操作这个View实现Ui的变更
* @param netTask 传入的Model用于实现Presenter绑定Model,通过这个Model获取数据
*/
public MusicInfoPresenter(MusicInfoContract.View addTaskView, NetTask netTask) {
this.netTask = netTask;
this.addTaskView = addTaskView;
}
@Override
public void onSuccess(musicData musicData) {
if (addTaskView.isACtive()) {
addTaskView.setMusicData(musicData);
}
}
@Override
public void onFailed() {
if (addTaskView.isACtive()) {
addTaskView.showError();
}
}
@Override
public void getMusiInfo(String ip) {
//1、将自身回调,把自身传入到Model中,实现Model绑定Presenter
netTask.execute(ip, this);
}
}
在MusicInfoPresenter中含有NetTask和MusicInfoContract.View的实例,并且还实现了LoadTasksCallBack接口。
在注释1中MusicInfoPresenter将自身传递给NetTask的execute方法来获取数据。并回调给MusicInfoPresenter本身实现的onSuccess、onFailed两个方法,在这两个方法中通过addTaskView与View交互。我们看一下这个getMusiInfo方法的时序图理解它是怎么做的:
[外链图片转存中…(img-6HAbY7JS-1702569842216)]
这样我们就理解了,这里的Presenter作为一个中间代理,通过NetTask也就是Model来获取和保存数据,然后通过View更新页面。在这个过程中通过自定义接口使得Model和View没有任何交互。
在前面的契约接口MusicInfoContract中我们已经自定义了View接口,接下来我们使用MusicInfoFragment实现它:
public class MusicInfoFragment extends Fragment implements MusicInfoContract.View {
private TextView textView;
private ImageView imageView;
private Button button;
private MusicInfoContract.Presenter mPresenter;
public static MusicInfoFragment newInstance() {
return new MusicInfoFragment();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.music_fragment, container, false);
textView = root.findViewById((int) R.id.textView);
imageView = root.findViewById((int) R.id.image);
button = root.findViewById((int) R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.getMusiInfo("热歌榜");
}
});
return root;
}
@Override
public void setPresenter(MusicInfoContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public void setMusicData(musicData musicData) {
Log.e("TAG", "execute: " + musicData.getData().toString());
if (musicData != null) {
textView.setText(musicData.getData().getName());
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
String originalUrl = musicData.getData().getPicurl();
//将http协议换成https
String modifiedUrl = originalUrl.replace("http://", "https://");
Glide.with(requireActivity()).load(modifiedUrl).into(imageView);
}
});
}
}
@Override
public void showError() {
textView.setText("Error");
}
@Override
public Boolean isACtive() {
//判断是否加入到Activity
return isAdded();
}
}
在上面注释1的部分通过实现setPresenter方法来实现注入MusicInfoPresenter,用于实现View绑定到Presenter。
在注释2中则调用MusicInfoPresenter的getMusiInfo方法来获取Ip地址的信息。另外Fragment实现了MusicInfoContract.View接口,用来接受MusicInfoPresenter的回调。
那么MusicInfoFragment是在哪里调用setPresenter实现注入MusicInfoPresenter?
其实在MainActivity中实现这个注入,我们看一下MainActivity做了什么事情:
public class MainActivity extends AppCompatActivity {
private MusicInfoPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1、创建Fragment
MusicInfoFragment fragment = (MusicInfoFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
if (fragment == null){
fragment = MusicInfoFragment.newInstance();
//2、将Fragment加入到Activity中
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),fragment,R.id.fragment);
}
MusicInfoTask task = MusicInfoTask.getInstance();
//3、将View和Model注入Presenter中
presenter = new MusicInfoPresenter(fragment,task);
//4、将Presenter注入View中实现双向绑定
fragment.setPresenter(presenter);
}
}
这个例子中Activity不作为View层,而是作为View、Model、Presenter三层的纽带。
📌注意我们看注释3、4可以发现View和Presenter是双向绑定的
ActivityUtils的addFragmentToActivity负责提交事务,实现了绑定Fragment到Activity,我们看一下代码:
public class ActivityUtils {
public static void addFragmentToActivity(FragmentManager fragmentManager,Fragment fragment,int frameId){
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(frameId,fragment);
transaction.commit();
}
}
从图中可以看出,View和Model之间并没有什么联系;View和Presenter通过接口交互,并在Activity中互相注入。Model的NetTask在Activity中注入Presenter,并等待Presenter调用。
这个例子是在上个例子的基础上修改的,加入了RxJava3和Retrofit2实现网络请求。
首先导入需要的依赖:
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
定义网络访问接口,如下所示:
public interface MusicService {
@GET("rand.music?format=json")
Observable<musicData> getMusicData(@Query("sort") String sort);
}
getMusicData返回Observable类型是为了支持RxJava,然后我们修改NetTask接口:
public interface NetTask<T> {
// void execute(T data, LoadTasksCallBack callBack);
Disposable execute(T data, LoadTasksCallBack callBack);
}
接下来修改NetTask接口的实现类MusicInfoTask,代码如下:
public class MusicInfoTask implements NetTask<String> {
private static MusicInfoTask INSTANCE = null;
private Retrofit retrofit;
private static final String HOST = "https://api.uomg.com/api/";
public MusicInfoTask() {
createRetrofit();
}
public static MusicInfoTask getInstance(){
if (INSTANCE == null){
INSTANCE = new MusicInfoTask();
}
return INSTANCE;
}
/**
* 初始化Retrofit
*/
private void createRetrofit(){
retrofit= new Retrofit.Builder()
.baseUrl(HOST)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
}
@Override
public Disposable execute(String data, LoadTasksCallBack callBack) {
MusicService service = retrofit.create(MusicService.class);
Disposable disposable = service.getMusicData(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<musicData>() {
@Override
public void onNext(@NonNull musicData musicData) {
// 收到数据时的操作
callBack.onSuccess(musicData);
}
@Override
public void onError(@NonNull Throwable e) {
// 发生错误时的操作
callBack.onFailed();
}
@Override
public void onComplete() {
// 完成时的操作
}
});
return disposable;
}
}
主要变化就是使用RxJava和Retrofit代替OKHttp访问网络,在execute方法中返回Disposable。接下来我们实现取消网络请求功能。首先定义一个BasePresenter接口:
public interface BasePresenter {
void subscribe();
void unsubscribe();
}
然后改写Presenter接口,继承这个BasePresenter:
public interface MusicInfoContract {
// interface Presenter {
// void getMusiInfo(String ip);
// }
interface Presenter extends BasePresenter{
void getMusiInfo(String ip);
}
...........
}
最后改写MusicInfoPresenter类:
public class MusicInfoPresenter implements MusicInfoContract.Presenter, LoadTasksCallBack<musicData> {
private NetTask netTask;
private MusicInfoContract.View addTaskView;
private CompositeDisposable compositeDisposable;
private Disposable disposable;
/**
* @param addTaskView 是我们传入的View用于实现Presenter绑定View,然后我们操作这个View实现Ui的变更
* @param netTask 传入的Model用于实现Presenter绑定Model,通过这个Model获取数据
*/
public MusicInfoPresenter(MusicInfoContract.View addTaskView, NetTask netTask) {
this.netTask = netTask;
this.addTaskView = addTaskView;
compositeDisposable = new CompositeDisposable();
}
@Override
public void onSuccess(musicData musicData) {
if (addTaskView.isACtive()) {
addTaskView.setMusicData(musicData);
}
}
@Override
public void onFailed() {
if (addTaskView.isACtive()) {
addTaskView.showError();
unsubscribe();
}
}
@Override
public void getMusiInfo(String ip) {
//1、将自身回调,把自身传入到Model中,实现Model绑定Presenter
disposable = netTask.execute(ip, this);
subscribe();
}
@Override
public void subscribe() {
if (disposable != null) {
compositeDisposable.add(disposable);
}
}
@Override
public void unsubscribe() {
if (compositeDisposable != null && compositeDisposable.isDisposed()){
compositeDisposable.dispose();
}
}
}
当注释1调用execute方法时返回一个disposable,然后调用subscribe方法传入compositeDisposable,用来管理这个网络请求,当我们需要取消这个网络请求时调用unsubscribe即可。