本篇先记录下当前项目中涉及的主要技术要点。也算是对所作项目的一次总结。如果这个过程能对你有些许的帮助,那可能就显得有意义点了。

一个完整的Android项目会涉及后台和前端。我们只关注于前端,也就是我们的app本身。

下面列出项目架构需要具备的技术点。(以当前所作项目为例)
1.项目结构(MVP设计模式)
2.屏幕适配
3.程序启动页
4.运行权限获取
5.基类(BaseActivity/BaseFragment/BaseApplication)
6.Retrofit(最流行的网络请求框架)+RxJava(链式编程风格+异步)
7.程序崩溃界面处理

1.项目结构

项目采用MVP设计架构。
关于MVC,MVP,MVVM设计模式的升级改造做如下说明:

MVC模式:
M 指模型层(网络IO、文件IO等操作)
V 指视图层(对应Android中的Layout和Activity/Fragment)
C 指控制层(对应Android中的Activity/Fragment)

在Android中,Activity/Fragment既充当控制层又充当视图层,这就导致了V和C这两层耦合在一起,当业务比较复杂时,Activity/Fragment文件就很庞大,导致难以维护和测试,于是MVP模式便应用而生。

MVP模式:
M 指模型层(同MVC)
V 指视图层(同MVC)
P 指业务层(业务逻辑)

Activity/Fragment只充当视图层,不做任何的业务逻辑,将业务逻辑全部放在业务层,由Presenter和Model进行交互,避免Model直接操作View。MVP的优点:将业务从Activity/Fragment分离,便于后期维护和测试。MVP使用特点是面向接口编程(View/Presenter/Model都定义一套接口)。可以说MVP模式的出现主要是将MVC中Control控制层进行解耦,View-视图层中需要展示数据则通过Present-业务层调用Model-模型层去获取,Present-业务层拿到数据后以接口形式回传给View-视图层View-视图层只需要注册监听Present-业务层用于更新View-视图层的接口就行了。

关于MVC和MVP的区别,大家可以参考 Android中MVC/MVP模式区别 ,里面介绍的很清楚,简单易懂。

有了MVP就够了,为什么又出来了一个MVVM呢?
关于MVP和MVVM模式的区别,网上资料多而杂,看过一圈之后,你可能会感觉更晕了。这个时候你可以继续往下看我的总结。

MVC和MVVM的唯一区别就是MVVM中多了一个DataBinding,其他基本无差别。
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中。

关于DataBinding框架你可以参考 Android DataBinding 从入门到进阶,但是我没有在项目中使用。尝试用了一下,发现不好用。

原因如下:
第一条,DataBindding优势在于支持数据绑定(单向和双向),这意味着MVP模式中Present-业务层通过接口回调的方式来通知View-视图层来进行界面更新操作流程不存在了,取而代之的是通过标签绑定的形式来进行更新。如此,layout.xml中可能会进行一些简单的业务逻辑处理。虽然是简单的业务逻辑,我还是感觉和我们一致提倡的视图&&业务分离的理念相悖。

第二条,.使用DataBinding不方便维护。我曾尝试将MVP模式的项目代码改为DataBinding+MVVM的实现方式。结果很是不爽。举个例子,在使用DataBinding设置监听事件的时候,对点击事件的引用是不会进行错误提示的,一旦你定义的名字和实际引用的存在大小写不匹配的情况,很难排查出错点。Android IDE对代码补全的支持还不是很友好。

3.使用DataBinding的使用率不是很高。别的我不知道,我身边的人也仅限于了解过活着写过简单的Demo去学习其用法。但是实际项目中没有用到。因此使用DataBinding的话还需谨慎,如果多人维护同一个项目的话,那么大家都会DataBinding的使用,不然日后维护绝对是个坑。

4.网上介绍的关于DataBinding的用法的介绍,我不想吐槽。好多都是直接复制粘贴过来的,都是些基本的不能再基本的用法,高级用法介绍的很少。有些连基本用法都没讲清楚。比如,我们经常在layout.xml中来使用include来进行布局的复用(虽然不能减少层级嵌套),但是在include中的Layout中使用clickListener的时候,需要由父布局层层传递下去,才能起作用。但是这种很重要的用法,有些文章连提都没提。

算了,不吐槽了。不然该有小伙伴拿“存在即合理”来反驳我了。

当项目界面比较复杂控件比较多的时候,可能会需要写大量的findById(R.id.xxx),如果你看着别扭的话,可以使用开源库Butterknife(黄油刀)来进行替换。不一定要使用DataBindding。算了,你们还是自己回头在实际项目中用一下吧,如果你们觉得好用,麻烦告诉我以下,我们再交流交流。

如果对于MVC/MVP设计架构不清楚的话,推荐大家阅读:
Android App的设计架构:MVC,MVP,MVVM与架构经验谈,文章中有段话说的特别好:

刚开始理解这些概念的时候认为这几种模式虽然都是要将view和model解耦,但是非此即彼,没有关系,一个应用只会用一种模式。后来慢慢发现世界绝对不是只有黑白两面,中间最大的一块其实是灰色地带,同样,这几种模式的边界并非那么明显,可能你在自己的应用中都会用到。实际上也根本没必要去纠结自己到底用的是MVC、MVP还是MVVP,不管黑猫白猫,捉住老鼠就是好猫。

通俗表述就是,不要刻意的是自己的代码故意去迎合某种设计模式,而是通过这种"视图&&业务逻辑解耦"的思想去引贯穿和引导自己去写代码。

2.屏幕适配

前面受限于截图,特此将values/dimens.xml文件展开如上图。android项目开发,屏幕适配是首先要考虑的问题关于屏幕适配,大家可参照另一篇文章:
Android 屏幕适配方案

3.程序启动页

AndroidMantifest.xml

...<activityandroid:name=".ui.activity.HRSplashActivity"android:theme="@style/SplashTheme"android:screenOrientation="portrait"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>
...
 <!-- Base application theme. --><style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><item name="colorPrimary">#00FFFF</item><item name="colorPrimaryDark">#FFFFFF</item><item name="colorAccent">#FFFF00</item><item name="android:windowBackground">@mipmap/startup</item><item name="android:windowActionBar">false</item><item name="android:windowNoTitle">true</item></style>

上述中的

<item name="android:windowBackground">@mipmap/startup</item>

就是启动页的图。
 
HRSplashActivity.java

public class HRSplashActivity extends FragmentActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//todo:可在此处添加关于启动页面的延时操作eg(延时2s): try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}startActivity(new Intent(HRSplashActivity.this, HRLoginActivity.class));finish();}}

4.运行权限获取

正常情况下程序运行的流程图下:
启动页----->登录页----->程序首页
没错,但是在从启动页跳转到登录页的时候,涉及到权限授予,主要是针对Android 6.0权限的授予。也许你会问,我在用到权限的时候再申请不行么? 答案是:可以,但是不建议。如果如果只有少量界面需要申请权限,即用即申请是可以的。但是如果需要权限的界面很多,这就比较麻烦了-------我们需要在每个界面都要判断所需权限是否已经授予了,如果没有的话就继续申请。权限申请逻辑比较分散,不便于后续维护。

因此最好的办法就是在程序开启时就要进行权限申请,如果所需权限没有全部被授予**(记住:所需权限必须全部被授予)**,就不允许用户登录。事实上QQ就是这么做的,当权限被用户手动拒绝之后,会弹出提示框,从而引导用户去设置中手动设置。

废话不说,直接贴出权限工具类,这个工具类其实还有些别的权限相关的方法,怕影响阅读,我把它阉割掉了,如果想看完整的,也可移步
Android常用的工具类汇总(方便日后使用)
PermissionUtils .java

public final class PermissionUtils {/*** Return whether <em>you</em> have granted the permissions.** @param permissions The permissions.* @return {@code true}: yes<br>{@code false}: no*/public static boolean isGranted(final String... permissions) {for (String permission : permissions) {if (!isGranted(permission)) {return false;}}return true;}private static boolean isGranted(final String permission) {return Build.VERSION.SDK_INT < Build.VERSION_CODES.M|| PackageManager.PERMISSION_GRANTED== ContextCompat.checkSelfPermission(Utils.getApp(), permission);}/*** Launch the application's details settings.*/public static void launchAppDetailsSettings() {Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");intent.setData(Uri.parse("package:" + Utils.getApp().getPackageName()));Utils.getApp().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));}/*** 批量申请权限(如果当前没有权限的话)。授权结果在onRequestPermissionsResult中处理*/public static void requestPermissionsIfNeed(Activity activity, String[] perms, int requestCode) {if (perms.length == 0) {return;}HashSet<String> needPerms = new HashSet<>();for (String perm : perms) {if (!isGranted(perm)) {needPerms.add(perm);}}if (needPerms.size() == 0) {return;} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {activity.requestPermissions(needPerms.toArray(new String[needPerms.size()]), requestCode);}}}}

上述保留了最基本的权限申请逻辑,完全能满足需求。

至于怎么在程序入口处调用,很简单,看程序入口。
HRLoginActivity.java

public class HRLoginActivity extends HRBaseActivity implements View.OnClickListener {private final int PERMISSION_REQUEST_CODE = 0x183;private final String perms[] = {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.MODIFY_AUDIO_SETTINGS,Manifest.permission.INTERNET,Manifest.permission.ACCESS_WIFI_STATE,Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO,Manifest.permission.CHANGE_NETWORK_STATE};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestPermissions();............}private void requestPermissions() {PermissionUtils.requestPermissionsIfNeed(this, perms, PERMISSION_REQUEST_CODE);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.login_confirm:if (PermissionUtils.isGranted(perms)) {login();} else {requestPermissions();}break;default:break;}}/*** 某一权限没有被授予,弹出提示框*/private void showPermissionDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(this).setTitle("温馨提示").setMessage("请在设置中开启所需权限,以正常使用xxx功能").setNeutralButton("取消", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();finish();}}).setNegativeButton("去设置", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();PermissionUtils.launchAppDetailsSettings();finish();}});final AlertDialog dialog = builder.show();dialog.setCanceledOnTouchOutside(false);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode != PERMISSION_REQUEST_CODE) {return;}for (int i = 0; i < grantResults.length; i++) {if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {showPermissionDialog();return;}}}}

逻辑很简单:如果权限没授予,就弹出提示框,引导用户去“设置”中手动授予权限,如果在弹框中点击“取消”,则退出程序。下次进来,依旧如此,直至所有权限全部被授予。

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() {@Overridepublic boolean handleMessage(Message msg) {handlerMsg(msg);return false;}});private AlertDialog mDialogLoading;private HRBaseActivityMonitor mHRBaseActivityMonitor;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);LogUtils.d(TAG, String.format("%s onCreate", TAG));mContext = this;initLoading();setContentView(initLayout());initView();initData();}@Overrideprotected void onNewIntent(Intent intent) {super.onNewIntent(intent);LogUtils.d(TAG, String.format("%s onNewIntent", TAG));}@Overrideprotected void onResume() {super.onResume();LogUtils.d(TAG, String.format("%s onResume", TAG));}@Overrideprotected void onPause() {super.onPause();hideLoading();LogUtils.d(TAG, String.format("%s onPause", TAG));}@Overrideprotected 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

<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/transparent"><ProgressBarandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"/>
</FrameLayout>

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

.....
.....
.....
private Handler mHandler = new Handler() {@Overridepublic 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() {@Overridepublic 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);}@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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;@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(initLayout(), container, false);initView(view);initData();return view;}@Overridepublic 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()即可。

....
....
....@Overridepublic 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() {@Overridepublic void onClick(View view) {showAddMemberPopWindow();}});}
....
....
.....

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

......
.....
.....private void initTitleBar() {//设置titlebarHRTitleBar 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() {@Overridepublic void onClick(View v) {hideLoading();finish();}});titleBar.showRightImageView(false);titleBar.showRightTextView(false);}
......
......
......

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="@dimen/dp54"android:background="#FFFFFF"><TextViewandroid: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" /><LinearLayoutandroid: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"><ImageViewandroid: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" /><TextViewandroid: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><LinearLayoutandroid: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"><TextViewandroid: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" /><ImageViewandroid: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() {@Overridepublic boolean verify(String hostname, SSLSession session) {return true;}}).sslSocketFactory(SSLUtils.createSSLSocketFactory());}mOkHttpClient = builder.build();}public OkHttpClient getOkHttpClient() {return mOkHttpClient;}private class CommonHeaderInterceptor implements Interceptor {@Overridepublic 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() {@Overridepublic 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);@Overridepublic void onComplete() {}@Overridepublic void onSubscribe(Disposable d) {}@Overridepublic void onError(Throwable e) {onFailure(HrWebConstantUtils.Response.FAILTURE, e.toString());}@Overridepublic 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);}}}

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 {@POSTObservable<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>() {@Overridepublic void onSuccess(RespMemberDetails memDetails) {//todo 请求成功if (memDetails != null) {mView.loadSuccess(memDetails);                 }............}@Overridepublic 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最好用的项目框架搭建相关推荐

  1. (三) Angular2项目框架搭建心得

    前言: 在哪看到过angular程序员被React程序员鄙视,略显尴尬,确实Angular挺值得被调侃的,在1.*版本存在的几个性能问题,性能优化的"潜规则"贼多,以及从1.*到2 ...

  2. 【高校宿舍管理系统】第一章 建立数据库以及项目框架搭建

    第一章 建立数据库以及项目框架搭建 提示:本博客个为人独立博客,不是权威,仅供参考!所有思路只做交流之用!如有不足之处,望各位在评论区友善指正. 文章目录 第一章 建立数据库以及项目框架搭建 前言 一 ...

  3. SpringBoot后端项目框架搭建

    SpringBoot后端项目框架搭建 本节内容服务于SpringBoot + Vue 搭建 JavaWeb 增删改查项目. 工具安装 电脑已安装\配置如下工具: IDEA.jdk.MySQL及其可视化 ...

  4. SpringSecurity(二)、权限项目框架搭建

    Springboot + SpringSecurity权限项目框架搭建 目录 一.项目介绍 二.项目搭建(父子工程) 1.添加 pom 依赖 2.修改 yml 配置 3.编写JwtTokenUtil工 ...

  5. day18_项目框架搭建1

    项目的工程目录: py_53 --根目录 py_api --子目录 day18_项目框架搭建 --项目工程目录 common -公用模块 excle.py -获取Excel表单的数据 handler_ ...

  6. spring cloud多模块项目框架搭建-Redis-Cluster集群搭建及系统集成

    第九章 Redis-Cluster集群搭建及系统集成 本系列博客旨在搭建一套能用于实际开发使用的spring cloud多模块微服务项目框架,并不是一个spring cloud的demo而已,提供系统 ...

  7. spring cloud多模块项目框架搭建-集成lombok

    第五章: spring cloud多模块项目框架搭建-集成lombok 本系列博客旨在搭建一套能用于实际开发使用的spring cloud多模块微服务项目框架,并不是一个spring cloud的de ...

  8. spring cloud多模块项目框架搭建-集成SLF4J和log4j2日志组件

    第七章  集成SLF4J和log4j2进行日志管理 本系列博客旨在搭建一套能用于实际开发使用的spring cloud多模块项目框架,并不是一个spring cloud的demo而已,提供分布式系统的 ...

  9. 一、友录项目框架搭建

    1,概况: 首先简单介绍本项目的概况,友录,即通讯录管理APP,其功能有四个: 一.通话记录:记录和联系人的通话: 二.联系人:联系人的查询,添加,删除,修改: 三.短信:与联系人的短信往来: 四.拨 ...

最新文章

  1. linux mq查看端口_通过rabbitmq的web监控mcollective的状态
  2. iOS进阶之协议Protocol(13)
  3. JS原型继承和类式继承
  4. 工作373-前端 import与export区别
  5. 15 FI配置-财务会计-将记账期间变式分配给公司代码
  6. 直接说,我要怎样才能做到年薪 50 万?
  7. mysql国内源码安装,mysql 源码包安装
  8. type python django models_Django 模型
  9. DotNetBar的初步使用
  10. Solidity基础入门知识---函数的访问权限和可见性
  11. java代码抖音舞,java实现抖音代码舞源码
  12. NOIP2017普及组复赛 解题分析
  13. 批量导入AD账户批量启用Exchange 账户步骤
  14. #后疫情时代的新思考#AI助力,“无接触”服务加速金融数字化转型丨数据猿公益策划...
  15. 京东云鼎服务器系统时间问题-时间戳参与与服务器时间差异大于设定值
  16. matlab绘图崩溃,重新采用硬件加速绘图
  17. 网络安全基础(十二)
  18. 在UBUNTU LINUX下搭建 ANDROID开发
  19. identifier of an instance of was altered from xxx to xxx问题解决
  20. 创建数据库是列名无效咋办_列创建后,sql server上的列名无效

热门文章

  1. JavaScript高阶
  2. MMPV-上月账期关闭,当月账期打开
  3. 如何用手机上的计算机弹音乐,如何使用手机qq音乐遥控电脑播歌
  4. Things I wish I’d known when I was younger 年轻时就该知道的那些事儿
  5. knif4j 在线APl 文档测试
  6. Desktop Central 应用规范报告—如何利用BYOD(一)
  7. linux下的iic驱动程序,实战经验吐血推荐:怎样在Linux环境下轻松实现基于I2C总线的EEPROM驱动程序...
  8. 查找SAPParameterID_SAP刘梦_新浪博客
  9. IDES翻译—采购流程中的Enjoy界面
  10. java类编来那个初始化顺序_java类的初始化顺序