写在前面的话
之前一直在学习任玉刚老师的《Android开发艺术探索》,并且也有通过笔记的形式记录下自己认为符合自己需要的内容,一是加深记忆,二是方便以后查找复习。目前的工作内容主要是android原生应用的定制修改,基本上涉及不到使用网络的情况,但是现在的第三方app基本上无一例外的都需要使用网络,因此这方面的知识就需要弥补上来。这里要感谢我的同事王远志的帮助,大家也可以去看他的主页milovetingting,有问题也可以给他留言。
准备工作
项目使用的API由玩Android 开放API提供。创建项目,首先将需要用到的开源项目导入。在dependencies
里面加入如下内容。
//butterknife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
//banner
implementation 'com.youth.banner:banner:1.4.10'
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
//rx
implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
//glide
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
这里指定使用java 8来进行编译,方便使用lambda表达式,在android
里增加如下内容
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
搭建MVP框架
目前主流的app开发基本都是使用MVP模式来进行的,因此也去学习了一下MVP的相关知识,这里就直接使用。对于MVP模式的理解,可以这样来看,我们部门包括部长,组长,我,现在有一个开发任务,部长告诉组长,组长安排我来去完成,我完成开发任务之后告诉组长,组长再告诉部长,部长就可以说任务完成了。这里的我相当于M层(真正做事情的,获取数据等耗时操作),组长相当于P层(用来传递消息,在V和M之间的桥梁),部长相当于V层(用来展示数据),这样便于理解,现实中也是如此,尽量不要越级沟通。
1.创建base文件夹,里面为基类BasePresenter和BaseActivity
BasePresenter.java
package com.tom.wanandroid.base;
import java.lang.ref.WeakReference;
import io.reactivex.disposables.CompositeDisposable;
/**
* Title: BasePresenter
* Description:
*
* @author tom
* @date 2019/3/7 09:59
**/
public abstract class BasePresenter {
WeakReference mViewRef;
//防止内存泄漏
protected CompositeDisposable mCompositeDisposable = new CompositeDisposable();
void attachView(V view) {
mViewRef = new WeakReference(view);
}
protected V getView() {
return mViewRef == null ? null : mViewRef.get();
}
protected boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
void detachView() {
if (mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
mCompositeDisposable.clear();
}
}
BaseActivity.java
package com.tom.wanandroid.base;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* Title: BaseActivity
* Description:
*
* @author tom
* @date 2019/3/7 10:04
**/
public abstract class BaseActivity> extends AppCompatActivity {
protected P mPresenter;
protected Unbinder unbinder;
/**
* 获取布局id
*
* @return 布局id
*/
protected abstract int getContentViewId();
/**
* 初始化
*
* @param savedInstanceState bundle
*/
protected abstract void init(Bundle savedInstanceState);
/**
* 创建Presenter
*
* @return p
*/
protected abstract P createPresenter();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
unbinder = ButterKnife.bind(this);
mPresenter = createPresenter();
mPresenter.attachView((V) this);
init(savedInstanceState);
}
@Override
public void onDestroy() {
super.onDestroy();
unbinder.unbind();
mPresenter.detachView();
}
}
这两个基类基本在每次使用的时候就可以直接复制粘贴了,避免每次都要重写。
2.创建Contract.java类,在contract包下,这个类里面主要定义接口,接口里面需要包含哪些方法时根据具体的根据业务来定义的,基本框架如下。
Contract.java
package com.tom.wanandroid.contract;
/**
* Title: Contract
* Description:
*
* @author tom
* @date 2019/3/7 10:13
**/
public class Contract {
public interface IMainModel{
}
public interface IMainView{
}
public interface IMainPresenter{
}
}
3.因为我们要获取到banner图的数据,所以需要有一个bean,数据根据API返回的数据来定义,API返回的是一个json串,因此我们可以创建一个对应的bean,也可以使用插件,我这里使用了GsonFormat插件。网页介绍如下图所示
可以使用http://www.wanandroid.com/banner/json查看示例。
通过GsonFormat插件生成的类没有toString方法,这里把toString方法补上,方便调试。
BannerBean.java
package com.tom.wanandroid.bean;
import java.util.List;
/**
* Title: BannerBean
* Description:
*
* @author tom
* @date 2019/3/7 10:28
**/
public class BannerBean {
/**
* 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"}]
* errorCode : 0
* errorMsg :
*/
private int errorCode;
private String errorMsg;
private List data;
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public List getData() {
return data;
}
public void setData(List data) {
this.data = data;
}
public static class DataBean {
/**
* 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
*/
private String desc;
private int id;
private String imagePath;
private int isVisible;
private int order;
private String title;
private int type;
private String url;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getImagePath() {
return imagePath;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public int getIsVisible() {
return isVisible;
}
public void setIsVisible(int isVisible) {
this.isVisible = isVisible;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "DataBean{" +
"desc='" + desc + '\'' +
", id=" + id +
", imagePath='" + imagePath + '\'' +
", isVisible=" + isVisible +
", order=" + order +
", title='" + title + '\'' +
", type=" + type +
", url='" + url + '\'' +
'}';
}
}
@Override
public String toString() {
return "BannerBean{" +
"errorCode=" + errorCode +
", errorMsg='" + errorMsg + '\'' +
", data=" + data +
'}';
}
}
4.填充Contract.java类里面的接口
根据实际的业务,这里要获取banner的数据,因此增加如下内容。
Contract.java
package com.tom.wanandroid.contract;
import com.tom.wanandroid.bean.BannerBean;
import io.reactivex.Observable;
/**
* Title: Contract
* Description:
*
* @author tom
* @date 2019/3/7 10:13
**/
public class Contract {
public interface IMainModel {
/**
* 获取banner数据
* @return banner数据
*/
Observable loadBanner();
}
public interface IMainView {
/**
* View 获取到数据后进行显示
* @param bean banner的数据
*/
void loadBanner(BannerBean bean);
}
public interface IMainPresenter{
/**
* P层接口
*/
void loadBanner();
}
}
5.接口已经定义好了,接下来就是分别创建MVP对应的类,来实现这些接口。先创建Model层,这里创建MainModel类,在包model下,实现IMainModel接口,主要作用是来获取数据,真正干活的地方。
MainModel.java
package com.tom.wanandroid.model;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import io.reactivex.Observable;
/**
* Title: MainModel
* Description:
*
* @author tom
* @date 2019/3/7 10:54
**/
public class MainModel implements Contract.IMainModel {
@Override
public Observable loadBanner() {
return null;
}
}
接着创建MainPresenter.java,继承BasePresenter,实现IMainPresenter接口,用来传话的地方。
MainPresenter.java
package com.tom.wanandroid.presenter;
import com.tom.wanandroid.base.BasePresenter;
import com.tom.wanandroid.contract.Contract;
/**
* Title: MainPresenter
* Description:
*
* @author tom
* @date 2019/3/7 10:59
**/
public class MainPresenter extends BasePresenter implements Contract.IMainPresenter {
@Override
public void loadBanner() {
}
}
6.数据获取,获取banner数据是从服务器上获取的,这里使用retrofit开源框架来获取,retrofit的使用需要自己学习。因为网站开放API介绍的是使用get的无参方法,因此创建retrofit接口,用于使用该框架。
IRetrofitData.java
package com.tom.wanandroid.retrofit;
import com.tom.wanandroid.bean.BannerBean;
import io.reactivex.Observable;
import retrofit2.http.GET;
/**
* Title: IRetrofitData
* Description:
*
* @author tom
* @date 2019/3/7 11:34
**/
public interface IRetrofitData {
@GET("banner/json")
Observable loadBanner();
}
7.填充M层的方法,获取banner数据。
MainModel.java
package com.tom.wanandroid.model;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.retrofit.IRetrofitData;
import io.reactivex.Observable;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Title: MainModel
* Description:
*
* @author tom
* @date 2019/3/7 10:54
**/
public class MainModel implements Contract.IMainModel {
private static final String BASE_URL = "http://www.wanandroid.com";
@Override
public Observable loadBanner() {
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
IRetrofitData retrofitData = retrofit.create(IRetrofitData.class);
return retrofitData.loadBanner();
}
}
8.填充P层方法,将获取到的数据设置给V层,让View去显示数据。
MainPresenter.java
package com.tom.wanandroid.presenter;
import com.tom.wanandroid.base.BasePresenter;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.model.MainModel;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
* Title: MainPresenter
* Description:
*
* @author tom
* @date 2019/3/7 10:59
**/
public class MainPresenter extends BasePresenter implements Contract.IMainPresenter {
Contract.IMainModel mModel;
public MainPresenter() {
mModel = new MainModel();
}
@Override
public void loadBanner() {
mModel.loadBanner()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
mCompositeDisposable.add(d);
}
@Override
public void onNext(BannerBean bean) {
if (isViewAttached()) {
getView().loadBanner(bean);
}
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {
}
});
}
}
9.数据已经准备好并且传递给了View层,接下来就需要在View来展示数据,新建用来展示数据的Activity,继承BaseActivity,实现IMainView接口,轮播图的显示使用开源框架Banner,图片使用Glide框架。Banner使用
这里我把重写的图片加载器放到了utils包里。
GlideImageLoader.java
package com.tom.wanandroid.utils;
import android.content.Context;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.youth.banner.loader.ImageLoader;
/**
* Title: GlideImageLoader
* Description: 重写图片加载器
*
* @author tom
* @date 2019/3/7 14:21
**/
public class GlideImageLoader extends ImageLoader {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
Glide.with(context).load(path).into(imageView);
}
}
activity_main.xml 高度值banner_height
我设置了200dp。
Constant.java
/**
* Title: Constant
* Description: 使用到的常量
*
* @author tom
* @date 2019/3/7 14:23
**/
public class Constant {
/**
* banner获取成功
*/
public static final int BANNER_SUCCESS = 0;
/**
* 文章title的key
*/
public static final String ARTICLE_TITLE = "title";
/**
* 文章url的key
*/
public static final String ARTICLE_URL = "url";
}
MainActivity.java
package com.tom.wanandroid.view;
import android.os.Bundle;
import com.tom.wanandroid.Constant;
import com.tom.wanandroid.R;
import com.tom.wanandroid.base.BaseActivity;
import com.tom.wanandroid.bean.BannerBean;
import com.tom.wanandroid.contract.Contract;
import com.tom.wanandroid.presenter.MainPresenter;
import com.tom.wanandroid.utils.GlideImageLoader;
import com.youth.banner.Banner;
import com.youth.banner.BannerConfig;
import java.util.List;
import java.util.stream.Collectors;
import butterknife.BindView;
public class MainActivity extends BaseActivity implements Contract.IMainView {
@BindView(R.id.main_banner)
Banner mMainBanner;
@Override
protected int getContentViewId() {
return R.layout.activity_main;
}
@Override
protected void init(Bundle savedInstanceState) {
mPresenter.loadBanner();
}
@Override
protected MainPresenter createPresenter() {
return new MainPresenter();
}
@Override
public void loadBanner(BannerBean bean) {
mMainBanner.setImageLoader(new GlideImageLoader());
if (bean.getErrorCode() == Constant.BANNER_SUCCESS) {
//设置banner样式
mMainBanner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR_TITLE_INSIDE);
//获取图片路径
List images = bean.getData()
.stream()
.map(BannerBean.DataBean::getImagePath)
.collect(Collectors.toList());
mMainBanner.setImages(images);
//获取title
List titles = bean.getData()
.stream()
.map(BannerBean.DataBean::getTitle)
.collect(Collectors.toList());
mMainBanner.setBannerTitles(titles);
mMainBanner.start();
}
}
}
10.添加权限,添加使用开源库所需要的权限,因为目前只涉及到网络权限,所以只申请网络权限。在AndroidManifest.xml
文件中添加。
至此,基本内容都写完了,跑一下看看,发现提示 "default Activity not found"
,检查发现AndroidManifest.xml
中没有写默认启动的activity,因为目前只有一个Activity,添加如下内容,即可。
在此运行,符合续期效果。运行效果如图:
至此,本节内容告一段落,接下来要继续实现后续部分。
2.WanAndroid实战——首页文章
3.WanAndroid实战——内容显示
4.WanAndroid实战——刷新加载
5.WanAndroid实战——网络判断