注解在项目中的使用
很早之前写过一篇关于MVP模式的文章,大家都知道MVP模式相对于MVC来说更加的解耦,当然了现在还有比较热的MVVM模式等,每一种新的架构模式的产生则表示之前的模式在某种程度上并不能很好的满足项目的需要,对于MVVM模式目前还没有接触,表示以后会抽时间研究。这里为什么会再次写关于MVP模式相关的文章呢?首先,之前文章是在CSDN上写的,这里表示懒得直接搬过来,所以做个复习;其次,之前的写法都是针对单个P来实现的,比如我们在一个activity中同时实现登录和注册,那么这个activity就需要对应两个presenter,那么按照之前的实现方式是不支持的,所以这里作为一个完善。
在此之前还是先来复习下之前写的MVP模式吧,还是用干货集中营的api,再次表示感谢 annotation_mvp.gif大家都知道P层是作为V层和M层之间的桥梁,也就是说,当我们进行网络请求的操作是在M层做的,然后将请求得到的结果回调到P层,最后在P层再将结果回调到V层。那么会不会出现这样的问题,当网络请求结果还没回调回来时我们的V层(activity)由于某些原因被销毁了,但是P层还持有V层的引用,导致activity不能被gc回收,也就出现了内存泄漏,当结果回来时P层将拿到的结果再给V层,这样也有可能会导致应用闪退等。所以我们应该让P层也拥有V层同样的生命周期,这里我们简单定义下presenter的基类BasePresenter
public abstract class BasePresenter<V> {
private V mvpView;
protected void attachView(V mvpView){
this.mvpView = mvpView;
}
protected void detachView(){
mvpView = null;
}
protected V getMvpView(){
return mvpView;
}
}
你也可以定义一个view的基类,比如请求加载框的显示与隐藏,数据加载成功或者失败的显示以及一些通用的toast提示等等,这里就不再赘述
接下来我们定义下activity的基类
public abstract class BaseMvpActivity<V, P extends BasePresenter<V>> extends AppCompatActivity{
private P presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//布局
setContentView(getLayoutId());
//获取presenter
presenter = createPresenter();
if (presenter != null){
presenter.attachView((V) this);
}
//初始化等操作
initView(savedInstanceState);
//findViewById
findViewById();
//设置监听事件
setListener();
//setViews
setViews();
//请求数据
setRequestDatas();
}
protected abstract int getLayoutId();
protected abstract P createPresenter();
protected void findViewById(){}
protected abstract void initView(Bundle savedInstanceState);
protected void setListener(){}
protected void setViews(){}
protected void setRequestDatas(){}
protected P getPresenter(){
return presenter;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (presenter != null){
presenter.detachView();
}
}
}
为了方便管理各层之间定义的接口,接下来我们需要定义一个契约类来统一管理
public class MZContract {
//view interface
public interface MZViewI{
void onSuccess(List<MZBean> lists);
void onFailed(String msg);
}
//presenter interface
public interface MZPresenterI{
void setMZDatas(Context context, String category, int count, int size);
}
//model
public interface MZModelI{
void requestDatas(Context context, String category, int count, int size, ResultStatuI resultStatu);
}
public interface ResultStatuI{
void onSuccess(List<MZBean> lists);
void onFailed(String msg);
}
}
看下我们进行网络请求的model层的处理
public class InfoModel implements MZContract.MZModelI{
@Override
public void requestDatas(Context context, String category, int count, int size, final MZContract.ResultStatuI resultStatu) {
RxRetrofitManger.getInstance().getService()
.getMZInfo(category, count, size)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RxObserver<BaseBean<List<MZBean>>>(context, false) {
@Override
public void onSuccess(BaseBean<List<MZBean>> response) {
resultStatu.onSuccess(response.getResults());
}
@Override
public void onFailed(BaseBean<List<MZBean>> response) {
resultStatu.onFailed("获取数据失败");
}
});
}
}
代码中简单封装了RxJava+OkHttp+Retrofit实现的网络请求框架,感兴趣的可以自己查看下代码。
接着我们再来看下和model交互的presenter的具体实现类
public class MZPresenter extends BasePresenter<MZContract.MZViewI> implements MZContract.MZPresenterI{
private InfoModel model;
public MZPresenter(){
model = new InfoModel();
}
@Override
public void setMZDatas(Context context, String category, int count, int size) {
if (getMvpView() != null){
model.requestDatas(context, category, count, size, new MZContract.ResultStatuI() {
@Override
public void onSuccess(List<MZBean> lists) {
if (getMvpView() != null){
getMvpView().onSuccess(lists);
}
}
@Override
public void onFailed(String msg) {
if (getMvpView() != null){
getMvpView().onFailed(msg);
}
}
});
}
}
}
可以看出,我们在进行网络请求以及结果回调时都进行了判断,如果view不存在了就不再进行后续操作。其实你还可以定义个方法去取消网络请求等等,这里示例只是用来简单解释说明,如需更加详细、完善的想法,可自行添加。
接着我们再来看下v层和p层是如何交互的
public class MainActivity extends BaseMvpActivity<MZContract.MZViewI, MZPresenter> implements MZContract.MZViewI{
private RecyclerView recyclerView;
private MZListsAdapter mzAdapter;
private List<MZBean> lists;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected MZPresenter createPresenter() {
return new MZPresenter();
}
@Override
protected void initView(Bundle savedInstanceState) {
}
@Override
protected void findViewById() {
recyclerView = findViewById(R.id.mz_recycler_view);
}
@Override
protected void setViews() {
lists = new ArrayList<>();
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(gridLayoutManager);
mzAdapter = new MZListsAdapter(this, lists);
recyclerView.setAdapter(mzAdapter);
}
@Override
protected void setRequestDatas() {
getPresenter().setMZDatas(this,"福利", 10, 1);
}
@Override
public void onSuccess(List<MZBean> lists) {
if (lists != null && lists.size() > 0){
mzAdapter.setDatas(lists);
}
}
@Override
public void onFailed(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
我们在V层通过createPresenter()进行和P层的绑定,对于解除绑定我们放在了base基类中去操作了,同时在解绑时我们也可以再增加个取消网络请求的操作等。
从上面的案例可以看到并不能满足一个V对应多个P的情况,所以,接下来我们就通过注解的方式实现这种需求,关于注解我们可以看下这位大牛的文章,讲解的很清楚深入理解Java注解类型(@Annotation),还要感谢L_Xian提供的通过注解的方式实现一个V对应多个P的案例,之前也学习过注解的一些相关知识,刚好通过这个案例加以实践。首先我们先看下修改之后的View层都发生了哪些变化
@CreatePresenter(presenters = {MZPresenter.class, ArticlePresenter.class})
public class MainActivity extends BaseMvpActivity implements MZContract.MZViewI, View.OnClickListener{
private LinearLayout llTitle;
private RecyclerView recyclerView;
private MZListsAdapter mzAdapter;
private List<MZBean> lists;
private ArticleListsAdapter articleAdapter;
private List<MZBean> articleLists;
private boolean isShowArticle;
@PresenterFiles
MZPresenter presenter;
@PresenterFiles
ArticlePresenter articlePresenter;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
/*@Override
protected MZPresenter createPresenter() {
return new MZPresenter();
}*/
@Override
protected void initView(Bundle savedInstanceState) {
}
@Override
protected void findViewById() {
llTitle = findViewById(R.id.ll_title);
recyclerView = findViewById(R.id.mz_recycler_view);
}
@Override
protected void setViews() {
lists = new ArrayList<>();
articleLists = new ArrayList<>();
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(gridLayoutManager);
mzAdapter = new MZListsAdapter(this, lists);
recyclerView.setAdapter(mzAdapter);
}
@Override
protected void setListener() {
llTitle.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.ll_title:
getArticleDatas();
break;
}
}
private void getArticleDatas() {
isShowArticle = true;
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
articleAdapter = new ArticleListsAdapter(this, articleLists);
recyclerView.setAdapter(articleAdapter);
//请求
articlePresenter.setMZDatas(this, "Android", 10, 1);
}
@Override
protected void setRequestDatas() {
presenter.setMZDatas(this,"福利", 10, 1);
//getPresenter().setMZDatas(this,"福利", 10, 1);
}
@Override
public void onSuccess(List<MZBean> lists) {
if (lists != null && lists.size() > 0){
if (isShowArticle){
articleAdapter.setArticleDatas(lists);
}else {
mzAdapter.setDatas(lists);
}
}
}
@Override
public void onFailed(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
由于获取妹纸字段和获取文章字段一样,所以为了偷懒在很多地方都共用了,这不重要,重要的是理解注解的实现方式。从上面代码中可以看出,之前我们是通过createPresenter()方法去new出我们要和view层进行绑定的presenter实例对象,现在我们是通过@CreatePresenter(presenters = {MZPresenter.class, ArticlePresenter.class})注解的方式将我们的presenter传过去,我们会在注解处理器中对传进来的这些presenter进行实例化,这种使用方式和使用阿里开源的ARouter使用类似,其次是我们发现多了两个加了@PresenterFiles注解的成员变量,这个@PresenterFiles注解和Android系统自带的@Test注解类似,是一个标记注解,我们会在注解处理器中通过反射先找到所有的成员变量字段,然后判断每个字段上是否存在@PresenterFiles注解,存在的话就通过key找到存储的相对应的value值,这个value值也就是前面传进去的presenter实例对象(前面我们说了@CreatePresenter注解,其作用就是去实例化传进去的这些presenter,通过map去进行以类名为key,实例化对象为value的存储),所以要注意下这个key必须要保持一致,因为我们都是用类名作为key。
接下来我们就来看下这两个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CreatePresenter {
Class<?>[] presenters();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PresenterFiles {
}
以及处理这俩注解的处理器
/**
* 运行时注解处理器
*/
public class PresenterCreator {
private Activity activity;
private Fragment fragment;
private Class<?> mClass;
private HashMap<String, BasePresenter> map = new HashMap<>();
public static PresenterCreator init(Activity activity){
return new PresenterCreator(activity, null);
}
public static PresenterCreator init(Fragment fragment){
return new PresenterCreator(null, fragment);
}
private PresenterCreator(Activity activity, Fragment fragment){
if (activity != null){
mClass = activity.getClass();
dealPresenterAnnotationOfType(activity);
dealPresenterOfField(activity);
}
if (fragment != null){
mClass = fragment.getClass();
dealPresenterAnnotationOfType(fragment);
dealPresenterOfField(fragment);
}
}
private <P extends BasePresenter> void dealPresenterAnnotationOfType(Object view){
CreatePresenter createPresenter = mClass.getAnnotation(CreatePresenter.class);
if (createPresenter != null){
Class<P>[] presenterClass = (Class<P>[]) createPresenter.presenters();
for (Class<P> clazz : presenterClass){
try {
P presenter = clazz.newInstance();
if (presenter != null){
map.put(clazz.getCanonicalName(), presenter);
//P和V绑定
presenter.attachView(view);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private <P extends BasePresenter> void dealPresenterOfField(Object view){
//首先通过反射先获取所有成员字段
Field[] fields = mClass.getDeclaredFields();
for (Field field : fields){
//再获取每一个字段上面的注解
Annotation[] anns = field.getDeclaredAnnotations();
if (anns.length < 1){//如果此字段上没有注解,则会返回一个长度为0的数组
continue;
}
//因为一个字段上可能会有多个注解
if (anns[0] instanceof PresenterFiles){
P presenter = (P) map.get(field.getType().getCanonicalName());
if (presenter != null){
try {
//给字段赋值
field.setAccessible(true);
field.set(view, presenter);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
public void detachView(){
for (Map.Entry<String, BasePresenter> entry : map.entrySet()){
BasePresenter presenter = entry.getValue();
if (presenter != null){
presenter.detachView();
}
}
}
}
然后我们在baseactivity基类中去启动这个注解处理器
public abstract class BaseMvpActivity extends AppCompatActivity{
//private P presenter;
private PresenterCreator presenterCreator;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//布局
setContentView(getLayoutId());
//获取presenter
presenterCreator = PresenterCreator.init(this);
/* presenter = createPresenter();
if (presenter != null){
presenter.attachView((V) this);
}*/
//初始化等操作
initView(savedInstanceState);
//findViewById
findViewById();
//设置监听事件
setListener();
//setViews
setViews();
//请求数据
setRequestDatas();
}
protected abstract int getLayoutId();
//protected abstract P createPresenter();
protected void findViewById(){}
protected abstract void initView(Bundle savedInstanceState);
protected void setListener(){}
protected void setViews(){}
protected void setRequestDatas(){}
/*protected P getPresenter(){
return presenter;
}*/
@Override
protected void onDestroy() {
super.onDestroy();
if (presenterCreator != null){
presenterCreator.detachView();
}
/*if (presenter != null){
presenter.detachView();
}*/
}
}
看到这里你可以能有些疑虑,这种方式会导致性能损耗吧?是的,因为我们定义的这些注解@Retention(RetentionPolicy.RUNTIME)可以看出是运行时注解,期间通过反射进行一系列的操作必然会导致性能上有所损耗,那应该怎么办呢?相信大家都用过黄油刀ButterKnife,他们有个@BindView注解,点击进去查看下这个注解定义的是@Retention(CLASS),也就是编译时注解,导致可能会在编译期时间会稍微长些,但是在运行时是不会有性能上的损耗的,那么他们是怎么做的呢?以后我会抽个时间研究下,等研究明白了再对此方式进行优化吧,最后引用zejian_博客中的关于@Retention中的三个可取值进行解释说明
@Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),其含有如下:
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。