您的位置:首页 > 运维架构 > 网站架构

第一次尝试——使用Retrofit+Dagger架构一个拿来就可以使用的Android空项目

2016-11-19 11:19 501 查看
    刚开始来公司的时候,接手了两个项目,两个项目的架构是用的同一个,虽然做了一些封装,但在我这个菜鸟的眼里,也觉得不好,单其中一个项目在不久后就上线了,因为百分之八十以上的页面在我来之前就写好了,而且之前也上传了几个版本了,在这种情况下,虽然后来的扩展和修改bug让我很头疼,也还是没有想着做一些改变。另外一个项目至今没有上线,还有一些东西没有完成,而另一个开发者又离职了,UI给我列出来一大堆要改的页面和逻辑,在缝缝补补做了半个月的时候,我终于终于没办法忍受,于是毅然决定重构整个项目,哦,不对,是重新做这个项目。

    那么,既然重新做,就不能用之前的架构了,缝缝补补太痛苦,所以开始了这篇博客的主题所指,当然,在我这个阅历下,能想到的封装思路和能够封装的程度都是有限的,只是给初级的开发者一些参考,大神,请绕步。。。

一、网络加载

    首先,选择网络加载框架,恩,之前就一直听说Retrofit,是最近非常流行的一个框架,但是一直没有机会自己来好好玩一次,所以在研究了一段时间之后毅然决定就它了。因为自己研究的程度目前还不能给大家分析,所以,详情请看下后面的代码吧!如果你都还没听说过,就去看看官方文档,这个不是今天的重点。

二、图片加载

    然后就是图片加载框架,现在流行的有三大图片框架:

        1)    Picasso

        2)    Glide

        3)    Fresco

    当然,ImageLoader是历史舞台的大佬,不过我好久没用过了。。。

    先简单的介绍下这三个框架:

    Picasso :和Square的网络库一起能发挥最大作用,因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。

    Glide:模仿了Picasso的API,而且在他的基础上加了很多的扩展(比如gif等支持),Glide默认的Bitmap格式是RGB_565,比    Picasso默认的ARGB_8888格式的内存开销要小一半;Picasso缓存的是全尺寸的(只缓存一种),而Glide缓存的是跟ImageView尺寸相同的(即56*56和128*128是两个缓存)


    FB的图片加载框架Fresco:最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。

    再看下三者的比较:

    Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。

    Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso,不过在使用起来也有些不便(小建议:他只能用内置的一个ImageView来实现这些功能,用起来比较麻烦,我们通常是根据Fresco自己改改,直接使用他的Bitmap层)

    因为使用了retrofit,再加上本应用对图片的要求不高,所以我们这里最完美的选择就是Picasso了,那就决定了,就用它。

三、其他

    做完了这两个选择后,接下来要考虑的事情是对基类的封装。当然最基础的基类有BaseApplication,BaseActivity和BaseFragment三个,对于BaseApplication的话,我没有做太多的处理,现在想到一个对网络状态进行监听的方法,然后在当网络由不可用转为确实可用的时候,发送一个消息给所有加载失败的页面,重新加载数据,这个在我写完这篇博客后会加上去的。

    BaseActivity和的ParentActivity封装

    至于ParentActivity是干嘛的?因为我需要在BaseActivity中封装好对错误页面,未加载完成的页面,加载成功后的页面和对标题栏等的一些页面的处理,但是很多页面其实是不需要加载网络的,所以不需要加载错误,未加载完成的页面,他只需要直接显示出来一些界面就好,所以ParentActivity就诞生了,在这个类里面,就是单单的做了除了与View有关的其他所有操作。

    ParentActivity.java

    

/**

* Created by cretin on 16/10/27.

*/



public abstract class ParentActivity extends AppCompatActivity {

   //记录下所有的Activity

   public final static List<ParentActivity> mActivities = new LinkedList<ParentActivity>();

   public static boolean isKitkat;

   public static ParentActivity mActivity;



   @Override

   protected void onCreate(@Nullable Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       getSupportActionBar().hide();

       synchronized (mActivities) {

           mActivities.add(this);

      }

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

           getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

           isKitkat = true;

      }

       initView(null);

   }



   protected abstract void initView(View view);



   @Override

   protected void onResume() {

       super.onResume();

       mActivity = this;

   }



   @Override

   protected void onPause() {

       super.onPause();

       mActivity = null;

   }



   @Override

   protected void onDestroy() {

       super.onDestroy();

       synchronized (mActivities) {

           mActivities.remove(this);

      }

       ButterKnife.unbind(this);

   }

}


    在上面的代码中,首先定义了一个List<ParentActivity> mActivities的集合,用于存储所有的已打开的Activtiy,便于以后统一的去处理和查询;还定义了一个public
static boolean isKitkat;这个是用来记录当前Android版本支不支持沉浸式状态栏,如果支持,直接使用沉浸式状态栏;还有一个抽象方法initView(null);是用来暴露给用户,让他去处理View相关的事情,但是由于ParentActivity没有处理View相关的操作,所以传入了null;在onDestroy方法里面,移除掉了当前的Activity,并且ButterKnife.unbind(this);由于整个架构默认使用ButterKnife来对View进行绑定,所以在退出的时候主动unbind掉,免得每个子类都要各自去处理。

    在BaseActivity里面,就需要处理上面ParentActivity没有能处理的事情,所以BaseActivity是继承自ParentActivity的。

具体如下:

BaseActivity.java

package com.cretin.www.createnewprojectdemo.base;



import android.graphics.Color;

import android.graphics.drawable.AnimationDrawable;

import android.os.Bundle;

import android.text.TextUtils;

import android.view.View;

import android.widget.ImageView;

import android.widget.RelativeLayout;

import android.widget.TextView;



import com.cretin.www.createnewprojectdemo.R;

import com.cretin.www.createnewprojectdemo.utils.ViewUtils;

import com.cretin.www.createnewprojectdemo.view.CustomProgressDialog;



import butterknife.ButterKnife;

import retrofit2.Call;

import retrofit2.Callback;

import retrofit2.Response;

import rx.Subscription;

import rx.subscriptions.CompositeSubscription;



/**

* Created by cretin on 16/10/27.

* 如果是需要处理自己的逻辑 则继承这个

* 如果只是给加载Fragment提供一个容器 则继承ParentActivity

*/



public abstract class BaseActivity extends ParentActivity {

   private CustomProgressDialog dialog;

   private OnTitleAreaCliclkListener onTitleAreaCliclkListener;



   private TextView tvMainTitle;

   private ImageView ivMainBack;

   private ImageView ivMainRight;

   private TextView tvMainRight;



   private RelativeLayout relaLoadContainer;

   private TextView tvLoadingMsg;



   private ImageView ivBack;



   private CompositeSubscription mCompositeSubscription;



   protected void addSubscription(Subscription s) {

       if ( this.mCompositeSubscription == null ) {

           this.mCompositeSubscription = new CompositeSubscription();

      }

       this.mCompositeSubscription.add(s);

   }



   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       View view = getLayoutInflater().inflate(R.layout.layout_base_activity, null);

       setContentView(view);

       initHeadView(view);

       initContentView(view);

       if ( isKitkat ) {

           view.findViewById(R.id.ll_main_title).setPadding(0, ViewUtils.getStatusBarHeights(), 0, 0);

      }

       initData();

       initEvent();

   }



   private AnimationDrawable animationDrawable;



   private void initContentView(View view) {

       RelativeLayout container = ( RelativeLayout ) view.findViewById(R.id.main_container);

       relaLoadContainer = ( RelativeLayout ) view.findViewById(R.id.load_container);

       tvLoadingMsg = ( TextView ) view.findViewById(R.id.loading_msg);

       ImageView imageView = ( ImageView ) view

               .findViewById(R.id.loading_image);

       animationDrawable = ( AnimationDrawable ) imageView

               .getBackground();

       animationDrawable.start();

       View v = getLayoutInflater().inflate(getContentViewId(), null);

       ButterKnife.bind(this, v);

       container.addView(v);

       initView(v);

   }



   //onResponse子类去实现

   public abstract class ResultCall<T> implements Callback<T> {

       @Override

       public void onResponse(Call<T> call, Response<T> response) {

           hidProgressView();

           onResponse(response);

      }



       protected abstract void onResponse(Response<T> response);



       @Override

       public void onFailure(Call<T> call, Throwable t) {

           showErrorView();

           onError(call, t);

      }



       protected abstract void onError(Call<T> call, Throwable t);

   }



   //隐藏正在加载视图

   public void hidProgressView() {

       if ( relaLoadContainer != null )

           relaLoadContainer.setVisibility(View.GONE);

       if ( animationDrawable != null )

           animationDrawable.stop();

   }



   //显示正在加载视图

   public void showProgressView() {

       if ( relaLoadContainer != null && relaLoadContainer.getVisibility() == View.GONE )

           relaLoadContainer.setVisibility(View.VISIBLE);

       if ( animationDrawable != null )

           animationDrawable.start();

   }



   //隐藏返回按钮

   public void hidBackIv() {

       if ( ivBack != null && ivBack.getVisibility() == View.VISIBLE )

           ivBack.setVisibility(View.GONE);

   }





   //显示加载错误

   public void showErrorView() {

       if ( relaLoadContainer != null && relaLoadContainer.getVisibility() == View.GONE )

           relaLoadContainer.setVisibility(View.VISIBLE);

       if ( animationDrawable != null )

           animationDrawable.stop();

       tvLoadingMsg.setText("加载错误");

   }



   //初始化头部视图

   private void initHeadView(View view) {

       tvMainTitle = ( TextView ) view.findViewById(R.id.tv_title_info);

       ivMainBack = ( ImageView ) view.findViewById(R.id.iv_back);

       ivMainRight = ( ImageView ) view.findViewById(R.id.iv_right);

       tvMainRight = ( TextView ) view.findViewById(R.id.tv_right);

       ivBack = ( ImageView ) view.findViewById(R.id.iv_back);

       ivMainBack.setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               finish();

               if ( onTitleAreaCliclkListener != null )

                   onTitleAreaCliclkListener.onTitleAreaClickListener(v);

          }

      });

       ivMainRight.setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               if ( onTitleAreaCliclkListener != null )

                   onTitleAreaCliclkListener.onTitleAreaClickListener(v);

          }

      });

       tvMainRight.setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               if ( onTitleAreaCliclkListener != null )

                   onTitleAreaCliclkListener.onTitleAreaClickListener(v);

          }

      });

   }



   /**

    * 显示加载对话框

    *

    * @param msg

    */

   public void showDialog(String msg) {

       if ( dialog == null ) {

           dialog = CustomProgressDialog.createDialog(this);

           if ( msg != null && !msg.equals("") ) {

               dialog.setMessage(msg);

          }

      }

       dialog.show();

   }



   /**

    * 关闭对话框

    */

   public void stopDialog() {

       if ( dialog != null && dialog.isShowing() ) {

           dialog.dismiss();

      }

   }



   public void setOnTitleAreaCliclkListener(OnTitleAreaCliclkListener onTitleAreaCliclkListener) {

       this.onTitleAreaCliclkListener = onTitleAreaCliclkListener;

   }



   //设置Title

   protected void setMainTitle(String title) {

       if ( !TextUtils.isEmpty(title) )

           tvMainTitle.setText(title);

   }



   //设置TitleColor

   protected void setMainTitleColor(String titleColor) {

       if ( !TextUtils.isEmpty(titleColor) )

           setMainTitleColor(Color.parseColor(titleColor));

   }



   //设置TitleColor

   protected void setMainTitleColor(int titleColor) {

       tvMainTitle.setTextColor(titleColor);

   }



   //设置右边TextView颜色

   protected void setMainTitleRightColor(int tvRightColor) {

       tvMainRight.setTextColor(tvRightColor);

   }



   //设置右边TextView颜色

   protected void setMainTitleRightColor(String tvRightColor) {

       if ( !TextUtils.isEmpty(tvRightColor) )

           setMainTitleRightColor(Color.parseColor(tvRightColor));

   }



   //设置右边TextView大小

   protected void setMainTitleRightSize(int size) {

       tvMainRight.setTextSize(size);

   }



   //设置右边TextView内容

   protected void setMainTitleRightContent(String content) {

       if ( !TextUtils.isEmpty(content) ) {

           if ( tvMainRight.getVisibility() == View.GONE )

               tvMainRight.setVisibility(View.VISIBLE);

           tvMainRight.setText(content);

      }

   }



   //设置左边ImageView资源

   protected void setMainLeftIvRes(int res) {

       if ( ivMainBack.getVisibility() == View.GONE )

           ivMainBack.setVisibility(View.VISIBLE);

       ivMainBack.setImageResource(res);

   }



   //设置又边ImageView资源

   protected void setMainRightIvRes(int res) {

       if ( ivMainRight.getVisibility() == View.GONE )

           ivMainRight.setVisibility(View.VISIBLE);

       ivMainRight.setImageResource(res);

   }



   interface OnTitleAreaCliclkListener {

       void onTitleAreaClickListener(View view);

   }



   protected abstract void initData();



   protected abstract int getContentViewId();



   protected void initEvent() {



   }



   @Override

   protected void onDestroy() {

       super.onDestroy();

       ButterKnife.unbind(this);

       if ( this.mCompositeSubscription != null ) {

           this.mCompositeSubscription.unsubscribe();

      }



   }

}


layouy_base_activity.xml:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout 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:orientation="vertical">



   <LinearLayout

       android:id="@+id/ll_main_title"

       android:layout_width="match_parent"

       android:layout_height="wrap_content"

       android:background="@color/white"

       android:orientation="vertical">



      <RelativeLayout

           android:layout_width="match_parent"

           android:layout_height="wrap_content">



           <ImageView

               android:id="@+id/iv_back"

               android:layout_width="wrap_content"

               android:layout_height="wrap_content"

               android:layout_centerVertical="true"

               android:layout_marginLeft="5dp"

               android:clickable="true"

               android:src="@mipmap/arrow_left"/>



           <TextView

               android:id="@+id/tv_title_info"

               android:layout_width="wrap_content"

               android:layout_height="wrap_content"

              android:layout_centerInParent="true"

               android:padding="13dp"

               android:textColor="@color/font_black3"

               android:textSize="@dimen/text_size_17"

               tools:text="标题" />



           <TextView

               android:id="@+id/tv_right"

               android:layout_width="wrap_content"

               android:layout_height="wrap_content"

               android:layout_alignParentRight="true"

              android:layout_centerInParent="true"

               android:layout_marginRight="5dp"

               android:clickable="true"

               android:drawablePadding="5dp"

               android:textColor="@color/font_black3"

               android:textSize="@dimen/text_size_17"

               android:visibility="gone"

               tools:text="修改" />



           <ImageView

               android:id="@+id/iv_right"

               android:layout_width="wrap_content"

               android:layout_height="wrap_content"

               android:layout_alignParentRight="true"

               android:layout_centerVertical="true"

               android:layout_marginRight="5dp"

               android:visibility="gone"

               tools:src="@mipmap/ic_launcher" />

      </RelativeLayout>



       <include layout="@layout/line" />

   </LinearLayout>





   <RelativeLayout

       android:id="@+id/main_container"

       android:layout_width="match_parent"

       android:layout_height="match_parent"

       android:layout_below="@+id/ll_main_title" />



   <RelativeLayout

       android:id="@+id/load_container"

       android:layout_width="match_parent"

       android:layout_height="match_parent"

       android:layout_below="@+id/ll_main_title"

       android:background="@color/white">



       <LinearLayout

           android:layout_width="120dip"

           android:layout_height="120dip"

           android:layout_centerInParent="true"

           android:gravity="center"

           android:orientation="vertical">



           <ImageView

               android:id="@+id/loading_image"

               android:layout_width="wrap_content"

               android:layout_height="wrap_content"

               android:layout_gravity="center_vertical|center_horizontal"

               android:background="@drawable/load_progress_bar" />



           <TextView

               android:id="@+id/loading_msg"

               android:layout_width="wrap_content"

               android:layout_height="wrap_content"

               android:layout_gravity="center_vertical|center_horizontal"

               android:padding="10dip"

               android:text="正在加载..."

               android:textSize="12sp" />

       </LinearLayout>

   </RelativeLayout>



</RelativeLayout>


     先看上面的布局文件,分为两个大的部分,一个是标题栏,一个是内容区域,标题栏左边有一个默认的返回ImageView,中间是一个TextView,用于显示标题,右边有一个TextView和一个ImageView,TextView用来显示比如修改的操作,ImageView用来显示设置什么的图标,默认左边的返回按钮个标题栏可见状态,右边两个都是隐藏的,因为用到他们的机会还是挺少的。在内用区域,有一个显示内容的布局,到时候用来显示需要加载的布局,还有一个正在加载的布局,用来显示正在加载中。。这个可自己去根据需求定制,这个页面也可以作为加载失败的页面,根据需求来就可以,当这个界面的网络加载完成之后,就可以主动的隐藏掉这个正在加载的页面就可以了,用户体验相对也会好一点。

    然后来看下BaseActivity中的操作,首先,我们避免写findViewById的操作,所以我们在BaseActivity中封装好了对ButterKnife的bind和unbind操作,这样在子类中就只需要简单的操作就行,不需要再bind和unbind了,然后提供了一系列的对标题栏自定义的操作,比如设置标题内容,文字大小颜色,设置标题栏右边的图标的资源。。等等,这些根据需求,不够的还可以自己再自行添加就好;还有在前面我们判断了Android版本是否支持沉浸式状态栏,如果支持的话,我在BaseActivity中也对标题栏进行了setPadding的操作,因为不设置的话,标题会跟状态栏的文字重叠,就会比较难看,这种操作不需要子类来理会,所以封装出来会比较好;另外我还提供了一个比较方便的的方法,就是showDialog和stopDialog的操作,当页面需要二次请求网路的时候,显示showDialog,加载完就stopDialog即可,这样用户体验就会好一点;还有一个很大的点就是对网络请求的封装,这个因为我现在对Retrofit还不是非常熟练,所以只进行了简单的封装,到时候经过再研究之后,再来进行完善!在目前的代码中,我写了一个抽象方法,在抽象方法中对请求的响应做出一个初级的判断,如果请求是成功的,并且数据的返回也是正常的,那么回调给调用者,否则直接显示网络加载错误的页面,统一的对网络错误进行处理,不过,这个要能真正完美的使用,需要后台不要太差。。不然老是返回乱七八糟的错误给你,就不太好统一处理了。。到此,BaseActivity的处理也要到此为止了。

    BaseFragment的封装

    对于BaseFragment来说,其封装跟BaseActtivity是一模一样的,本质的差别也就是一个是Activity一个是Fragment,所以就不再赘述了。

    BaseFragmentActivity和BackFragmentActivity的封装

    为什么会有这两个类的存在?我现在比较喜欢的一种加载界面方式是使用Fragment碎片化的方式,就是开一个Activity,这个Activity就是承载Fragment的一个容器,所以这个Activity啥都不做,就只作用于添加Fragment,显然,这个Activity是不需要加载网络和一些其他操作的,所以我把他们俩都是集成自ParentActivity,这个具体看下项目中的操作。看这两个类的名字也很清楚,继承BaseFragmentActivity的时候,代表要加载的Fragment是不需要假如回退栈的,继承BackFragmentActivity的时候,代表要加载的Fragment是需要假如回退栈的,当然,在计入Fragment的时候,也可以使用动画,具体的使用,看Demo就好,在此不再赘述了,代码什么的还是要自己看,理解才快。

工具类

    最后再来说下项目中默认添加使用的一些其他好用的东西:

    本地数据的存储:Hawk 

    这个工具是当年一个叫Calvin的大哥告诉我的,一用起来就爱不释手。

    Hawk 是一个非常便捷的数据库  . 操作数据库只需一行代码 , 能存任何数据类型 .

    github 地址: https://github.com/orhanobut/hawk

    Hawk 是一个简单的  key-value  数据库

    它使用: 

    AES 加密

    能选择使用SharedPreferences  或者  SQLite

       Gson解析

    提供:

    安全数据持久化

    能存储任何类型

    具体的使用,大家去Github上去看吧,相信只要你用过你就会喜欢上他的,信我。

    Android事件总线:EventBus

    这个不多说,已经非常成熟了。简单介绍下

    实际项目开发过程中,经常遇到如下场景:不同的应用程序组件的控件间具有一定的相互关联性,其中用户对后者进行的某种操作会引起前者的相应改变。举一个具体的场景:以糗事百科为例,在糗事列表页和详情页页,对于每个糗事而言,布局基本一致,在详情页点击了个赞,赞的数量增加,同时赞的图标发生了变化,此时返回到列表页,此糗事上的赞图标以及数量与刚刚详情页的需要保持一致。在举一个例子,对于多个底部导航tab下的资讯类阅读app,在咨询详情页点击了收藏,然后收藏成功,此时回到底部tab中的个人中心,假如个人中心中有我的收藏,同时后面显示的是收藏数量,此时此收藏数量需要同于于刚刚用户所进行的收藏/取消收藏而即时更改数字。凡此种种,类似需求场景非常常见。

    有时候,当此类需求相对简单时,通过接口以实现回调等方式可以完成,但是当不同组件/控件之间的关系纷繁复杂时,基于接口的方案不仅使得代码非常繁琐,同时是的程序逻辑很混乱,基于此,EventBus,为此类需求的实现提供了非常方便的方案。

    butterknife注解框架的偷懒插件

    这个也不多说,已经非常成熟了。简单介绍下

    事实上这是个Android Studio的plugin,他可以让你在添加Butterkinfe注解时偷偷懒,直接点击几下鼠标既可以完成注解的增加,同时还是图形化的操作,可以说,大大的减轻了开发负担。尤其是当你的layout中有很多很多的view需要通过findviewbyid来获得引用时。实际上如果不用这个插件而通过手打加ButtefKnife注解的方式,要是view很多启示也挺麻烦的,不是吗?

    到此这篇博客也就到了尾声了,可能有很多地方还不是很准确,希望大家多提提意见,而且,这个封装也还很基础,希望大佬有时间看下,多提提意见和建议,再次谢过了。如果觉得不是太烂,记得Star一下哦。

    

    我是Cretin,一个可爱的小男孩。

    下面是Github的地址:

    https://github.com/MZCretin/CreateNewProjectDemo
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 架构 项目
相关文章推荐