Android项目框架搭建(二)

接上一篇 Android项目框架搭建(一),本次把剩下的3块补充完整。

5.基类(BaseActivity/BaseFragment/BaseApplication)
6.Retrofit(最流行的网络请求框架)+RxJava(链式编程风格+异步)
7.程序崩溃界面处理

5.基类(BaseActivity/BaseFragment/BaseApplication)

BaseActivity.java(完整版)

public abstract class HRBaseActivity extends RxFragmentActivity {

    protected final String TAG = getClass().getSimpleName();
    protected HRBaseActivity mContext;
    protected WeakHandler mHandler = new WeakHandler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            handlerMsg(msg);
            return false;
        }
    });


    private AlertDialog mDialogLoading;
    private HRBaseActivityMonitor mHRBaseActivityMonitor;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LogUtils.d(TAG, String.format("%s onCreate", TAG));
        mContext = this;
        initLoading();

        setContentView(initLayout());
        initView();
        initData();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        LogUtils.d(TAG, String.format("%s onNewIntent", TAG));
    }

    @Override
    protected void onResume() {
        super.onResume();
        LogUtils.d(TAG, String.format("%s onResume", TAG));
    }

    @Override
    protected void onPause() {
        super.onPause();
        hideLoading();
        LogUtils.d(TAG, String.format("%s onPause", TAG));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        LogUtils.d(TAG, String.format("%s onDestroy", TAG));
        if(mHRBaseActivityMonitor != null) {
            mHRBaseActivityMonitor.destroy();
        }
        hideLoading();
        mHandler.removeCallbacksAndMessages(null);
    }

    protected void quitActivity() {
        sendBroadcast(new Intent(ConstantUtils.Action.ACTION_QUIT));
    }

    private void initLoading() {
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        mDialogLoading = builder.create();
        mDialogLoading.setCanceledOnTouchOutside(false);
    }

    public void showLoading() {
        if(mDialogLoading != null && !mDialogLoading.isShowing()) {
            mDialogLoading.show();
            View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_progress, null);
            mDialogLoading.setContentView(view);
            mDialogLoading.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
            mDialogLoading.getWindow().getDecorView().setBackgroundColor(0x00000000);
            mDialogLoading.getWindow().getDecorView().setPadding(0, 0, 0, 0);
        }
    }

    public void hideLoading() {
        if(mDialogLoading != null && mDialogLoading.isShowing()) {
            mDialogLoading.dismiss();
        }
    }

    public WeakHandler getHandler() {
        return mHandler;
    }

    public void setMonitorListener(HRBaseActivityMonitor.Listener listener) {
        if(mHRBaseActivityMonitor == null) {
            mHRBaseActivityMonitor = new HRBaseActivityMonitor(this);
        }
        mHRBaseActivityMonitor.setListener(listener);
    }

    /**
     * 设置根布局
     */
    public abstract int initLayout();

    /**
     * 初始化布局
     */
    public abstract void initView();

    /**
     * 设置数据
     */
    public abstract void initData();

    /**
     * 处理handler消息
     */
    public void handlerMsg(Message msg) {};

}

下面我会详细讲解下BaseActivity.java基类到底包含哪些东西。
首先思考下何为基类?
我理解的基类就是做一些比较通用的工作,并且对子类中的通用模块进行封装。
子类中通用的模块包括什么呢?包含以下几点:

1.布局/控件/数据的初始化,如下

 /**
     * 设置根布局
     */
    public abstract int initLayout();

    /**
     * 初始化布局
     */
    public abstract void initView();

    /**
     * 设置数据
     */
    public abstract void initData();

 
2.ProgressBar加载框。现在Android应用通常在界面跳转的时候,往往会发起网络请求。网络请求时候,一般都通过ProgressBar“转圈圈”来显示网络加载的过程。因此在BaseActivity.java基类中应该封装用于装显示和隐藏转圈圈的接口,子类中可以在发起网络请求的同时showLoading(),在网络请求有结果返回的时候hideLoading()。进行关于加载框,大家按需分配,如果项目需要就用,如果项目不需要,可以不用,代码如下:

  /**
     * 初始化转圈圈
     */
    private void initLoading() {
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        mDialogLoading = builder.create();
        mDialogLoading.setCanceledOnTouchOutside(false);
    }

    /**
     * 显示转圈圈
     */
    public void showLoading() {
        if(mDialogLoading != null && !mDialogLoading.isShowing()) {
            mDialogLoading.show();
            View view = LayoutInflater.from(mContext).inflate(R.layout.dialog_progress, null);
            mDialogLoading.setContentView(view);
            mDialogLoading.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
            mDialogLoading.getWindow().getDecorView().setBackgroundColor(0x00000000);
            mDialogLoading.getWindow().getDecorView().setPadding(0, 0, 0, 0);
        }
    }

    /**
     *隐藏转圈圈
     */
    public void hideLoading() {
        if(mDialogLoading != null && mDialogLoading.isShowing()) {
            mDialogLoading.dismiss();
        }
    }

目前转圈圈的布局中只含有一个ProgressBar,你完全可以进行扩充,比如添加加载动效等等。

dialog_progress.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</FrameLayout>

 
3.Handler消息处理。 项目中或多或少都会涉及线程间通信,或延时,定时操作。这时候自然就少不了Handler了消息处理机制了。Handler使用不当很容易造成内存泄漏。初学者往往不在意,下面就是一个典型的Handler造成的内存泄漏的例子。(相信下面的代码有小伙伴没少写吧,至少我写过)

.....
.....
.....
private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == MSG_WHAT) {
                String str = (String) msg.obj;
                Log.i(TAG, "handleMessage:str::" + str);
            }
        }
    };
.....
.....
.....

泄漏原因:非静态内部类默认持有外部类的引用。
具体描述可以移步 Handler导致的内存泄露分析以及内存泄露检测工具LeakCanary的集成,那里对Handler造成的内存泄漏的原因以及解决方案做了详细说明。

为了避免大家的不注意造成的Handler滥用的情况,特此从网上淘了一个大牛封装好的WeakHandler.java文件,相关文档见 WeakHandler:避免内存泄漏的Handler

集成方式:
(1).可以将此文件拷贝出来用
(2).在app 的build.gradle文件中添加添加依赖(见链接)

关于WeakHandler.java的文件我也就不贴了。

基类中BaseActivity.java中涉及到的Handler的代码如下

......
......
......
  protected WeakHandler mHandler = new WeakHandler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            handlerMsg(msg);
            return false;
        }
    });
......
.....
......
    /**
     * 处理handler消息
     */
    public void handlerMsg(Message msg) {};
    .....
    ....

在子类中重写 handlerMsg(Message msg)即可处理handler消息。简单粗暴。
 

4.广播监听。看到基类中的如下代码,你可能有点疑问------这玩意是干啥的?

   ......
   .....
   .....
    private HRBaseActivityMonitor mHRBaseActivityMonitor;
....
....
....
    public void setMonitorListener(HRBaseActivityMonitor.Listener listener) {
        if(mHRBaseActivityMonitor == null) {
            mHRBaseActivityMonitor = new HRBaseActivityMonitor(this);
        }
        mHRBaseActivityMonitor.setListener(listener);
    }
....
...
...

网上提供的基类往往很少涉及这块。但是它很重要。举个例子,我的App中正在进行 屏幕录制(举个例子) 操作,这个时候突然有人打电话进来了。那这个时候我们的App运行时就被接电话给打断了,毕竟接打电话具有最高的优先级。那这个时候我们的app处在一种什么状态呢?不好意思,我也不清楚。但是在接电话的时候,对当前正在运行的app做一些额外的操作还是很有必要的。比如来电话的时候,我将录制的屏幕视频保存到指定目录下,或者直接销毁当前活动等。

所以,mHRBaseActivityMonitor.java就是用来做这个工作的。
BaseActivity.java中涉及到的广播的封装类文件如下:
BaseActivityMonitor.java

public class HRBaseActivityMonitor extends HRBaseMonitor {

    private Listener mListener;

    public HRBaseActivityMonitor(Context context) {
        super(context);
    }

    @Override
    public IntentFilter register() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(ConstantUtils.Action.ACTION_QUIT);
        filter.addAction(ConstantUtils.Action.ACTION_SHUTDOWN);
        filter.addAction(ConstantUtils.Action.ACTION_PHONE_STATE);
        filter.addAction(ConstantUtils.Action.ACTION_NEW_OUTGOING_CALL);
        return filter;
    }

    @Override
    public void handleReceive(Context context, Intent intent) {
        String action = intent.getAction();
        LogUtils.d("onReceive, action = " + action);

        switch(action) {
            case ConstantUtils.Action.ACTION_QUIT:
            case ConstantUtils.Action.ACTION_PHONE_STATE:
            case ConstantUtils.Action.ACTION_NEW_OUTGOING_CALL:
            case ConstantUtils.Action.ACTION_SHUTDOWN:
                if(mListener != null) {
                    mListener.finish();
                }
                break;

            default:break;
        }
    }

    public void setListener(Listener listener) {
        mListener = listener;
    }

    public void destroy(){
        unRegister();
        mListener = null;
    }

/*********************************************Interface*************************************************************/

    public interface Listener {
        void finish();
    }


}

其中

            case ConstantUtils.Action.ACTION_QUIT:
            case ConstantUtils.Action.ACTION_PHONE_STATE:
            case ConstantUtils.Action.ACTION_NEW_OUTGOING_CALL:
            case ConstantUtils.Action.ACTION_SHUTDOWN:

是一些写在常量文件中的系统广播,比如来电和拨出等,

    public static final String ACTION_PHONE_STATE = "android.intent.action.PHONE_STATE"; // 来电
    public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"; // 拨出

自己按照需要来增添相应的广播即可。在需要进行打断监听的子类中直接设置监听即可,操作如下:

 setMonitorListener(new HRBaseActivityMonitor.Listener() {
            @Override
            public void finish() {
                //todo 在此处添加有来电话时候的处理逻辑
                quit();
            }
        });

 
好了,细心的你可能发现在BaseActivity中我们集成的是RxFragmentActivity 。

public abstract class HRBaseActivity extends RxFragmentActivity {
....
.....
}

RxFragmentActivity 是什么玩意,不都是继承AppcompatActivity么?
前缀是Rx,你能想到啥?Rxjava吧。 Retrofit(最流行的网络请求框架)+RxJava(链式编程风格+异步) 是目前Android 开发最流程的开发框架。Retrofit+Rxjava是基于订阅的。但是发布的订阅如果没有被及时取消的化,就会造成内存泄漏,因此我们通过继承Rx****Activity.java来控制组件生命周期结束时,自动取消对Observable订阅。后面讲到Rxjava+Retrofit的时候会说。
至于为什么不是继承RxAppCompatActivity而是继承RxFragmentActivity,那是因为我的子类会涉及到Fragment的切换,所以只能用RxFragmentActivity了。

关于RxFragmentActivity,确切的说是RxLifecycler的使用可参考 Rxlifecycle使用详解
 
好了说完BaseActivity基类,下面该说BaseFragment基类了。
BaseFragment.java(完整版)

public abstract class HRBaseFragment extends Fragment {

    protected HRBaseActivity activity;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(initLayout(), container, false);
        initView(view);
        initData();
        return view;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (HRBaseActivity) getActivity();
    }

    /**
     * 设置根布局
     */
    public abstract int initLayout();

    /**
     * 初始化布局
     */
    public abstract void initView(View root);

    /**
     * 设置数据
     */
    public abstract void initData();

    /**
     * 设置TitleBar
     * @param HRTitleBar
     */
    public abstract void setTitleBar(HRTitleBar HRTitleBar);
}

可以看到BaseFragment.java中和BaseActivity.java相比多了下面一点差异化的东西

 /**
     * 设置TitleBar
     * @param HRTitleBar
     */
    public abstract void setTitleBar(HRTitleBar HRTitleBar);

在实际开发中,涉及到Activity中切换Fragment的时候,往往都需要对TitleBar做些许的改动。Activity和Fragment中应该共用一套TitleBar 。示意图如下:
在这里插入图片描述
首先,这个TitleBar是在Activity层面的。这样为何便于各个不同的Fragment对TitleBar进行更改,于是我们需要在各个继承了BaseFragment的子Fragment中重写setTitleBar()即可。

....
....
....
 @Override
    public void setTitleBar(HRTitleBar titleBar) {
      //todo 在此处设置TitleBar上各个控件的显示以及控件的监听事件
      
        titleBar.reset();
        titleBar.setTitleText(getString(R.string.member_title));

        titleBar.showRightImageView(true);
        titleBar.setRightImageResource(R.mipmap.member_add);
        titleBar.setRightLayoutMargins(0, 0, getResources().getDimensionPixelSize(R.dimen.dp10), 0);
        titleBar.setRightClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showAddMemberPopWindow();
            }
        });
    }
....
....
.....

那么在Activity中如何处理TitleBar呢,如下

......
.....
.....
  private void initTitleBar() {
   //设置titlebar
        HRTitleBar titleBar = new HRTitleBar(findViewById(R.id.title_bar));
        titleBar.setTitleBarBackground(ContextCompat.getDrawable(this, R.drawable.shape_statistic_detail_title_bar_bg));
        titleBar.setLeftLayoutMargins(getResources().getDimensionPixelSize(R.dimen.dp11), 0, 0, 0);
        titleBar.setTitleTextColor(Color.parseColor("#FFFFFF"));
        titleBar.setTitleText(getString(R.string.statistics_data_title));

        titleBar.setLeftLayoutMargins(getResources().getDimensionPixelSize(R.dimen.dp11), 0, 0, 0);
        titleBar.setLeftImageResource(R.mipmap.ic_back);
        titleBar.showLeftImageView(true);
        titleBar.showLeftTextView(false);
        titleBar.setLeftClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                hideLoading();
                finish();
            }
        });

        titleBar.showRightImageView(false);
        titleBar.showRightTextView(false);
    }
......
......
......

其中的布局文件引用,大家可以自己定义,顺便提供一个吧

.....
.....
.....
   <include
        android:id="@+id/title_bar"
        layout="@layout/title_bar" />
....
.....

Titlebar的布局实现如下:
title_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp54"
    android:background="#FFFFFF">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Title"
        android:textColor="#000000"
        android:textSize="@dimen/dp13" />

    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/dp10"
        android:paddingBottom="@dimen/dp10"
        android:paddingEnd="@dimen/dp20"
        android:layout_gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/left_image"
            android:layout_width="@dimen/dp19"
            android:layout_height="@dimen/dp19"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:src="@mipmap/back" />

        <TextView
            android:id="@+id/left_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:maxLines="1"
            android:ellipsize="end"
            android:maxWidth="@dimen/dp120"
            android:gravity="center_vertical"
            android:text="Return"
            android:textColor="#6D6D6D"
            android:textSize="@dimen/dp13"/>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/right_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/dp10"
        android:paddingBottom="@dimen/dp10"
        android:paddingStart="@dimen/dp20"
        android:layout_gravity="center_vertical|right"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/right_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:text="Confirm"
            android:textColor="#6D6D6D"
            android:textSize="@dimen/dp13" />

        <ImageView
            android:id="@+id/right_image"
            android:layout_width="@dimen/dp19"
            android:layout_height="@dimen/dp19"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:src="@mipmap/setting" />
    </LinearLayout>

</FrameLayout>

预览效果如下:
在这里插入图片描述

那么Activity优势如何将TiltleBar对象传给Fragment的呢?

   ....
   .....
   //在切换到对应的Fragment中及逆行设置
        ((HRBaseFragment)curFragment).setTitleBar(mHRTitleBar);
       
  ......
  .....

 
下面附上最重要的titlebar实现类代码(依赖于上面提供的布局文件,自己可修改)
TitleBar.java

public class HRTitleBar {
    private View mRoot;
    private TextView mTitleView;
    private ImageView mLeftImageView;
    private TextView mLeftTextView;
    private LinearLayout mLeftLayout;
    private TextView mRightTextView;
    private ImageView mRightImageView;
    private LinearLayout mRightLayout;

    public HRTitleBar(View root) {
        mRoot = root;
        mTitleView = mRoot.findViewById(R.id.title);
        mLeftImageView = mRoot.findViewById(R.id.left_image);
        mLeftTextView = mRoot.findViewById(R.id.left_text);
        mLeftLayout = mRoot.findViewById(R.id.left_layout);
        mRightImageView = mRoot.findViewById(R.id.right_image);
        mRightTextView = mRoot.findViewById(R.id.right_text);
        mRightLayout = mRoot.findViewById(R.id.right_layout);

        mTitleView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
        reset();
    }

    /**
     *隐藏titlebar中的所有控件
     */
    public void reset() {
        mLeftImageView.setVisibility(View.GONE);
        mLeftTextView.setVisibility(View.GONE);
        mRightImageView.setVisibility(View.GONE);
        mRightTextView.setVisibility(View.GONE);

        mRightLayout.setOnClickListener(null);
        mLeftLayout.setOnClickListener(null);

        mTitleView.setTextColor(Color.parseColor("#000000"));
        mLeftTextView.setTextColor(Color.parseColor("#6D6D6D"));
        mRightTextView.setTextColor(Color.parseColor("#6D6D6D"));
        mRoot.setBackgroundColor(Color.parseColor("#FFFFFF"));
    }

    /**
     * 设置titlebar的背景色
     * @param color, color res id
     */
    public void setTitleBarBackground(int color) {
        mRoot.setBackgroundColor(color);
    }


    /**
     * 设置titlebar的背景色
     * @param drawable
     */
    public void setTitleBarBackground(Drawable drawable) {
        mRoot.setBackground(drawable);
    }

    /**
     * 设置titilebar 标题
     * @param text
     */
    public void setTitleText(String text) {
        mTitleView.setText(text);
    }

    /**
     * titilebar 右边图标是否显示
     * @param show
     */
    public void showRightImageView(boolean show) {
        mRightImageView.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    /**
     * titlebar右图标旁的文本是否显示
     * @param show
     */
    public void showRightTextView(boolean show) {
        mRightTextView.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    /**
     * 设置titlebar标题字体颜色
     * @param color
     */
    public void setTitleTextColor(int color) {
        mTitleView.setTextColor(color);
    }

    /**
     * 设置titlebar右边文本的颜色
     * @param color
     */
    public void setRightTextColor(int color) {
        mRightTextView.setTextColor(color);
    }

    /**
     * 设置titlebar右边图标
     * @param res, res id
     */
    public void setRightImageResource(int res) {
        mRightImageView.setImageResource(res);
    }

    /**
     * 设置titlebar右边图标文本
     * @param text
     */
    public void setRightText(String text) {
        mRightTextView.setText(text);
    }

    /**
     * 设置titilbar右边控件(图标+文本)的监听事件
     * @param l
     */
    public void setRightClickListener(View.OnClickListener l) {
        mRightLayout.setOnClickListener(l);
    }

    /**
     * 设置titlebar左图标
     * @param res, res id
     */
    public void setLeftImageResource(int res) {
        mLeftImageView.setImageResource(res);
    }


    /**
     * titilebar左边文本是否显示
     * @param show
     */
    public void showLeftTextView(boolean show) {
        mLeftTextView.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    /***********************************设置titlebar文本与图片相对位置************************************/

    public void setLeftCompoundDrawablesRelativeWithIntrinsicBounds(int left, int top, int right, int bottom) {
        mLeftTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(left, top, right, bottom);
    }

    public void setLeftCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
        mLeftTextView.setCompoundDrawables(left, top, right, bottom);
    }

    public void setLeftCompoundDrawablePadding(int padding) {
        mLeftTextView.setCompoundDrawablePadding(padding);
    }

    public void setRightCompoundDrawablesRelativeWithIntrinsicBounds(int left, int top, int right, int bottom) {
        mRightTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(left, top, right, bottom);
    }

    public void setRightCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
        mRightTextView.setCompoundDrawables(left, top, right, bottom);
    }

    public void setRightCompoundDrawablePadding(int padding) {
        mRightTextView.setCompoundDrawablePadding(padding);
    }

    public void setRightLayoutMargins(int left, int top, int right, int bottom) {
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mRightLayout.getLayoutParams());

        layoutParams.leftMargin   = left > 0   ? left   : layoutParams.leftMargin;
        layoutParams.topMargin    = top > 0    ? top    : layoutParams.topMargin;
        layoutParams.rightMargin  = right > 0  ? right  : layoutParams.rightMargin;
        layoutParams.bottomMargin = bottom > 0 ? bottom : layoutParams.bottomMargin;
        layoutParams.gravity = Gravity.CENTER_VERTICAL|Gravity.END;

        mRightLayout.setLayoutParams(layoutParams);
    }

    public void setLeftText(String text) {
        mLeftTextView.setText(text);
    }

    public void setLeftLayoutMargins(int left, int top, int right, int bottom) {
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mLeftLayout.getLayoutParams());

        layoutParams.leftMargin   = left > 0   ? left   : layoutParams.leftMargin;
        layoutParams.topMargin    = top > 0    ? top    : layoutParams.topMargin;
        layoutParams.rightMargin  = right > 0  ? right  : layoutParams.rightMargin;
        layoutParams.bottomMargin = bottom > 0 ? bottom : layoutParams.bottomMargin;
        layoutParams.gravity = Gravity.CENTER_VERTICAL;

        mLeftLayout.setLayoutParams(layoutParams);
    }

    public void showLeftImageView(boolean show) {
        mLeftImageView.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    public void setLeftClickListener(View.OnClickListener l) {
        mLeftLayout.setOnClickListener(l);
    }

    public void setTitleCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
        mTitleView.setCompoundDrawables(left, top, right, bottom);
    }

    public void setTitleCompoundDrawablePadding(int padding) {
        mTitleView.setCompoundDrawablePadding(padding);
    }

    public void setTitleClickListener(View.OnClickListener l) {
        mTitleView.setOnClickListener(l);
    }

}

好了,基类的介绍就到这了。如果想专门看看TitleBar的封装,也可移步 Android打造通用的TitleBar,内容一样,无非其中的接口注释是中文还是英文。
顺便提一嘴,关于BaseApplication基类所承载的工作一般是一些配置文件的初始化,比如某些库的初始化配置等等。有可能用不到。所以不在赘述。

 

6.Retrofit(最流行的网络请求框架)+RxJava(链式编程风格+异步)

关于Retrofit+Rxjava我不打算详解。可参考:
Android Retrofit 2.0 的详细 使用攻略(含实例讲解)

那么提及这一点的目的很简单,给Retrofit+RXJava组合方式使用有疑问的同学,提供下参考(代码中涉及到项目中的关键代码,我会做屏蔽处理)
主体HttpManager .java

/**
 * 处理网络请求管理类
 */
public class HttpManager {

    private final String TAG = "HttpManager";
    private final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");

    private volatile static HttpManager INSTANCE;
    private IHttpRequest mHttpRequest;

    public static HttpManager getInstance() {
        if (INSTANCE == null) {
            synchronized (HttpManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new HttpManager();
                }
            }
        }
        return INSTANCE;
    }

    private HttpManager() {
        Retrofit retrofit = new Retrofit.Builder()
                .client(new OkHttpManager().getOkHttpClient())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(HrWebConstantUtils.Url.URL_SAAS)
                .build();

        mHttpRequest = retrofit.create(IHttpRequest.class);
    }

    private void subscribe(RxFragmentActivity activity, Observable observable, HttpCallBack subscriber) {
        observable.compose(activity.bindUntilEvent(ActivityEvent.DESTROY))
                /** http请求线程 */
                .subscribeOn(Schedulers.io())
                /** 回调线程 */
                .observeOn(AndroidSchedulers.mainThread())
                /** 结果判断 */
                .subscribe(subscriber);
    }

    public void reqLogin(RxFragmentActivity activity, String userName, String pwd, HttpCallBack subscriber) {
        HrWebConstantUtils.AUTHOR_TOKEN = "";
        subscribe(activity, mHttpRequest.reqLogin(HrWebConstantUtils.Url.URL_ACCOUNT + "v1/auth/login", Credentials.basic(userName, pwd)), subscriber);
    }

    public void reqCommonConfig(RxFragmentActivity activity, HttpCallBack subscriber) {
        subscribe(activity, mHttpRequest.reqCommonConfig(), subscriber);
    }

    public void reqSections(RxFragmentActivity activity, HttpCallBack subscriber) {
        subscribe(activity, mHttpRequest.reqSections(), subscriber);
    }

    public void reqIPCList(RxFragmentActivity activity, String nodeId, int curPage, int pageSize, String sn, HttpCallBack subscriber) {
        JSONObject jsonBody = new JSONObject();
        try {
            jsonBody.put("current", curPage);
            jsonBody.put("page_size", pageSize);
            jsonBody.put("node_id", nodeId);
            if(!TextUtils.isEmpty(sn)) {
                jsonBody.put("ipc_sn", sn.trim());
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, jsonBody.toString());
        subscribe(activity, mHttpRequest.reqIPCList(body), subscriber);
    }

    public void reqAddIPC(RxFragmentActivity activity, String nodeId, String sn, String location, HttpCallBack subscriber) {
        JSONObject jsonBody = new JSONObject();
        try {
            jsonBody.put("ipc_sn", sn);
            jsonBody.put("node_id", nodeId);
            jsonBody.put("location", location);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, jsonBody.toString());
        subscribe(activity, mHttpRequest.reqAddIPC(body), subscriber);
    }

    public void reqNodeInfo(RxFragmentActivity activity, String nodeId, HttpCallBack subscriber) {
        subscribe(activity, mHttpRequest.reqNodeInfo(nodeId), subscriber);
    }

    public void reqMemberList(RxFragmentActivity activity, String nodeId, int curPage, int pageSize, String memType, HttpCallBack subscriber) {
        subscribe(activity, mHttpRequest.reqMemberList(nodeId, curPage, pageSize, memType), subscriber);
    }

    public void reqMemberDetails(RxFragmentActivity activity, String memId, String memType, String nodeId, int curPage, int pageSize, HttpCallBack subscriber) {
        subscribe(activity, mHttpRequest.reqMemberDetails(memId, memType, nodeId, curPage, pageSize), subscriber);
    }

    public void reqAccountInfo(RxFragmentActivity activity, HttpCallBack subscriber) {
        subscribe(activity, mHttpRequest.reqAccountnfo(), subscriber);
    }

    public void reqAddMember(RxFragmentActivity activity, ReqMemberInfo memberAdd, HttpCallBack subscriber) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, new Gson().toJson(memberAdd));
        subscribe(activity, mHttpRequest.reqAddMember(body), subscriber);
    }

    public void reqUpdateMember(RxFragmentActivity activity, ReqMemberInfo memberAdd, HttpCallBack subscriber) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, new Gson().toJson(memberAdd));
        subscribe(activity, mHttpRequest.reqUpdateMember(body), subscriber);
    }

    public void reqArrivalList(RxFragmentActivity activity, ReqArrivalQuery arrivalQuery, HttpCallBack subscriber) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, new Gson().toJson(arrivalQuery));
        subscribe(activity, mHttpRequest.reqArrivalList(body), subscriber);
    }

    public void reqStatisticsTraffics(RxFragmentActivity activity, ReqTraffics reqTraffics, HttpCallBack subscriber) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, new Gson().toJson(reqTraffics));
        subscribe(activity, mHttpRequest.reqStatisticsTraffics(body), subscriber);
    }

    public void reqStatisticAttributes(RxFragmentActivity activity, ReqTraffics reqTraffics, HttpCallBack subscriber) {
        RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, new Gson().toJson(reqTraffics));
        subscribe(activity, mHttpRequest.reqStatisticsAttributes(body), subscriber);
    }
}

 
下面是HttpManager中用到的一些类:
OkHttpManager.java

/**
 * okhttp的管理类
 */
public class OkHttpManager {

    private final String TAG = "OkHttpManager";
    private OkHttpClient mOkHttpClient;

    public OkHttpManager() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .retryOnConnectionFailure(true)//断网重连
                .connectTimeout(20, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .addInterceptor(getHttpLoggingInterceptor())
                .addInterceptor(new CommonHeaderInterceptor());

        if(HrWebConstantUtils.Url.URL_SAAS.contains("https")) {
            builder.hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }
                    })
                    .sslSocketFactory(SSLUtils.createSSLSocketFactory());

        }

        mOkHttpClient = builder.build();
    }

    public OkHttpClient getOkHttpClient() {
        return mOkHttpClient;
    }

    private class CommonHeaderInterceptor implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            Request.Builder requestBuilder = request.newBuilder();
            if(!TextUtils.isEmpty(HrWebConstantUtils.AUTHOR_TOKEN)) {
                requestBuilder.addHeader("Authorization", "Bearer " + HrWebConstantUtils.AUTHOR_TOKEN);
            }
            requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8");
            requestBuilder.addHeader("Accept", "application/json; charset=utf-8");
//            requestBuilder.addHeader("Connection", "close");

            return chain.proceed(requestBuilder.build());
        }

    }

    private HttpLoggingInterceptor getHttpLoggingInterceptor(){
        if(!HrLogUtils.DEBUG) {
            return null;
        }

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                HrLogUtils.d(TAG, message);
            }
        });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return loggingInterceptor;
    }

}

 
和Rxjava相关的用于处理http网络请求的回调接口
HttpCallBack.java

package com.hobot.webserver.library;

import android.text.TextUtils;

import com.hobot.webserver.library.model.BaseResponse;
import com.hobot.webserver.library.model.RespLogin;
import com.hobot.webserver.library.utils.HrWebConstantUtils;
import com.hobot.webserver.library.utils.HrLogUtils;

import java.lang.reflect.ParameterizedType;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

/**
 * 用于网络请求数据的返回
 * @param <T>
 */
public abstract class HttpCallBack<T> implements Observer<BaseResponse<T>> {

    private final String TAG = "HttpCallBack";

    public abstract void onSuccess(T t);
    public abstract void onFailure(int code, String msg);

    @Override
    public void onComplete() {

    }

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onError(Throwable e) {
        onFailure(HrWebConstantUtils.Response.FAILTURE, e.toString());
    }

    @Override
    public void onNext(BaseResponse<T> response) {
        if(response.getCode() != HrWebConstantUtils.Response.SUCCESS) {
            onFailure(response.getCode(), "from onNext");
            return;
        }

        if(TextUtils.isEmpty(HrWebConstantUtils.AUTHOR_TOKEN)) {
            ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
            Class cls = (Class<T>) pt.getActualTypeArguments()[0];
            if(cls.equals(RespLogin.class)) {
                HrWebConstantUtils.AUTHOR_TOKEN = ((RespLogin)response.getData()).getToken();
                HrLogUtils.d(TAG, "AUTHOR_TOKEN = " + HrWebConstantUtils.AUTHOR_TOKEN);
            }
        }

        onSuccess(response.getData());
    }
}


 
http网络请求结果数据的封装类
BaseResponse.java

package com.hobot.webserver.library.model;

public class BaseResponse<T> {

    private String msg;
    private int code;
    private T data;

    public void setMsg(String msg){
        this.msg = msg;
    }

    public String getMsg(){
        return msg;
    }

    public void setCode(int code){
        this.code = code;
    }

    public int getCode(){
        return code;
    }

    public void setData(T data){
        this.data = data;
    }

    public T getData(){
        return data;
    }

}

 
用于打印http请求和响应信息的日志类,在Interceptor拦截器中进行调用
HrLogUtils.java

public class HrLogUtils {

    public static final boolean DEBUG = true;

    public static void d(String tag, String msg) {
        if(DEBUG) {
            Log.d(tag, "[HrLogUtils-server]: " + msg);
        }
    }

    public static void e(String tag, String msg) {
        if(DEBUG) {
            Log.e(tag, "[HrLogUtils-server]: " + msg);
        }
    }

    public static void i(String tag, String msg) {
        if(DEBUG) {
            Log.i(tag, "[HrLogUtils-server]: " + msg);
        }
    }

}

&nbsp;
Retrofit中用到的BaseUrl等常量类:
HrWebConstantUtils.java

public class HrWebConstantUtils {

    public static String AUTHOR_TOKEN = "";

    public static class Url {
    .....
    .....
    .....
        /** 测试用 */
        public static final String URL_SAAS = "http://xxxx-pexxxx.axxx/api/";
.....
.....
    }

    public static class Response {
        public static final int SUCCESS = 0;
        public static final int FAILTURE = 1;
    }

}

 
创建网络接口的示例(重点)
IHttpRequest.java

/**
 * 创建 网络请求接口实例
 */
public interface IHttpRequest {

    @POST
    Observable<BaseResponse<RespLogin>> reqLogin(@Url String url, @Header("Authorization") String author);

    @Headers("Content-Type: application/x-www-form-urlencoded")
    @GET("v1/organization/xxxxx")
    Observable<BaseResponse<ArrayList<RespSection>>> reqSections();

    @GET("v1/common/xxxx")
    Observable<BaseResponse<RespCommonConfig>> reqCommonConfig();

    @POST("v1/ipc/query/xxxx")
    Observable<BaseResponse<RespIpcList>> reqIPCList(@Body RequestBody json);

    @POST("v1/ipc/create/xxxx")
    Observable<BaseResponse<String>> reqAddIPC(@Body RequestBody json);

    @GET("v1/organization/xxxx")
    Observable<BaseResponse<RespNodeInfo>> reqNodeInfo(@Query("node_id") String nodeId);

    @Headers("Content-Type: application/json")
    @GET("v1/member/xxxxx")
    Observable<BaseResponse<RespMemberList>> reqMemberList(
            @Query("node_id") String nodeId,
            @Query("current") int current,
            @Query("page_size") int pageSize,
            @Query("mem_type") String memType);


    @GET("v1/member/xxxxx")
    Observable<BaseResponse<RespMemberDetails>> reqMemberDetails(
            @Query("member_id") String memId,
            @Query("mem_type") String memType,
            @Query("node_id") String nodeId,
            @Query("current") int current,
            @Query("page_size") int pageSize);

    @Headers("Content-Type: application/json")
    @GET("v1/account/xxxxx")
    Observable<BaseResponse<RespAccountInfo>> reqAccountnfo();

    @POST("v1/member/xxxxx")
    Observable<BaseResponse<RespMsg>> reqAddMember(@Body RequestBody json);

    @POST("v1/member/xxxxx")
    Observable<BaseResponse<Object>> reqUpdateMember(@Body RequestBody json);

    @POST("v1/statistics/xxxxxx")
    Observable<BaseResponse<RespArrivalList>> reqArrivalList(@Body RequestBody json);

    @POST("v1/statistics/xxxxx")
    Observable<BaseResponse<RespStatisticsTraffics>> reqStatisticsTraffics(@Body RequestBody json);

    @POST("v1/statistics/attributes/xxxxx")
    Observable<BaseResponse<RespStatisticsTraffics>> reqStatisticsAttributes(@Body RequestBody json);
}

 
发起http请求示例如下:

....
....
....
//发起网络请求
   HttpManager.getInstance().reqMemberDetails(mActivity, memId, memType,nodeId, curPage, pageSize, new HttpCallBack<RespMemberDetails>() {
            @Override
            public void onSuccess(RespMemberDetails memDetails) {
            //todo 请求成功
                if (memDetails != null) {
                      mView.loadSuccess(memDetails);                 
                }
                ......
                ......
            }

            @Override
            public void onFailure(int code, String msg) {
            //todo 请求失败
                LogUtils.e(TAG, "msg::" + msg);
                String resMsg = mActivity.getString(R.string.member_query_fail);
                switch (code) {
                    case ERROR_CODE_NO_MEMBER:
                        resMsg = mActivity.getString(R.string.member_not_exist);
                        break;
                    default:
                        mView.loadFailture("");
                        break;
                }
                mView.loadFailture(resMsg);
            }
        });
......
......
.....

好了关于Retrofit+Rxjava就讲这么多。
 

7.程序崩溃界面处理

一般讲android项目框架搭建的可能会遗漏掉这块。如果项目要上线的话,为了在程序异常崩溃的时候能够让用户选择重启app和反馈,提升用户体验,集成程序崩溃框架—CustomActivityOnCrash还是很有必要的。

关于CustomActivityOnCrash框架的使用,不赘述了。大家可移步:
android程序崩溃框架—CustomActivityOnCrash

好了,一个完整的Android项目从搭建到正式上线,除了以上这些,还包含添加混淆,发布release版本等。

关于混淆,推荐链接:5分钟搞定android混淆

关于发布lelease版本,推荐链接 教你如何使用android studio发布release 版本(完整版)

好了,关于Android项目框架搭建,就简单介绍到这吧,如有问题还请大家留言。

 
 
 
附上链接:
Android项目框架搭建(一)

  • 6
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值