冠亚体育手机网站浅析Android代码质量管理,统一编写共有逻辑

模板方法-基类封装

在上一篇文章NetworkStateView的结尾说到可以在BaseActivity中对NetworkStateView进行统一设置,从而进行界面多状态的加载,那么今天就说一说BaseActivity,在BaseActivity怎么进行NetworkStateView的设置以及BaseActivity的一些其他作用,还没有看过NetworkStaetView的可以先看一下这一篇文章

Activity和Fragment应该是Android最常用的组件,对他进行简单的封装对提高代码的简洁性也有很大的帮助。

在项目中,我们在写Activity时一般都不会直接继承AppCompatActivity,而是先写一个基类BaseActivity,让其他的Activity继承BaseActivity,这样就有一个好处,就是我们可以把Activity的共有逻辑写在BaseActivity中,例如NetworkStateView的逻辑,从而不需要在每个Activity都写一遍

BaseActivity :

下面就对BaseActivityNetworkStateView以及一些其他共有的逻辑内容进行介绍

public abstract class BaseActivity extends FragmentActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  init();
  findViews();
  initData();
  setListener();
  setting();
 }

 /**
  * 获得上下文
  * @return Context
  */
 public Context getContext(){
  return this;
 }

 /**
  * 始化参数
  */
 public abstract void init();
 /**
  * 查找所有的控件
  */
 public abstract void findViews();
 /**
  * 初始化页面数据
  */
 public abstract void initData();
 /**
  * 设置控件的监听事件
  */
 public abstract void setListener();

 /**
  * 后续参数设置
  */
 public abstract void setting();

}
  • 定义加载布局,进行共同界面(NetworkStateView)的加载

BaseFragment :

其实在BaseActivity中,我们可以先给BaseActivity先定义一个布局文件如activity_base,在activity_base布局文件中先定义好共同的布局控件如NetworkStateViewToolbar等,并且在最后定义一个FrameLayout,这个FrameLayout是关键人物,后面再说他的作用,哈哈

public abstract class BaseFragment extends Fragment {


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

 @Override
 public void onStart() {
  super.onStart();
  init();
  findViews();
  initData();
  setListener();
  setting();
 }

 public Context getContext() {
  return getActivity();
 }

 public abstract void init();

 public abstract void findViews();

 public abstract void initData();

 public abstract void setListener();

 public abstract void setting();

}
<LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <include layout="@layout/view_network_state" /> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> </FrameLayout></LinearLayout>

代码比较简单,用到了模板设计模式,一个方法只做一样事情,初始化的就只做初始化操作,设置监听的就只设置监听。不管多少个Activity\Fragment都能很好的统一化编码风格,看起来更清晰不乱。

定义好布局文件之后,我们需要在BaseActivity中重写setContentView方法,让其加载activity_base的布局文件并进行设置填充,接着再加载子类Activity的布局文件

Fragment简单管理

@SuppressLint("InflateParams")@Overridepublic void setContentView(@LayoutRes int layoutResID) { View view = getLayoutInflater().inflate(R.layout.activity_base, null); //设置填充activity_base布局 super.setContentView; if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { view.setFitsSystemWindows; } //加载子类Activity的布局 initDefaultView(layoutResID);}

下面先看看标准的创建和管理Fragment

看到这里可能有人会有疑惑,先将activity_base的布局设置填充了之后,还怎么进行设置子类Activity的布局文件?哈哈,这个时候上面的FrameLayout就要发挥作用了,其实这里设置子类Activity的布局并不是直接填充给BaseActivity,而是让其作为一个子View添加到activity_base中的FrameLayout中,这样不就让BaseActivity和子类Activity的布局相结合了,看看initDefaultView(layoutResID)

private void showFragment(){ 
 FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); 
 hideFragment(fragmentTransaction); 
 if (mFragment1== null) { 
 mFragment1 = new MyFragment1(context); 
 fragmentTransaction.add(R.id.content, mFragment1); 
 fragmentTransaction.commit(); 
 } else { 
 fragmentTransaction.show(mFragment1); 
 fragmentTransaction.commit(); 
 } 
} 
private void initDefaultView(int layoutResId) { networkStateView = (NetworkStateView) findViewById(R.id.nsv_state_view); FrameLayout container = (FrameLayout) findViewById(R.id.fl_activity_child_container); View childView = LayoutInflater.from.inflate(layoutResId, null); container.addView(childView, 0);}

每次创建一个Fragment都要复制一边这个方法,代码冗余、不利于维护和更新。

嗯,对NetworkStateView进行findViewById查找,接着定义NetworkStateView的相关方法之后,我们就能在子类Activity中进行相关调用了

下面封装一下

/** * 显示加载中的布局 */public void showLoadingView() { networkStateView.showLoading();}/** * 显示加载完成后的布局(即子类Activity的布局) */public void showContentView() { networkStateView.showSuccess();}/** * 显示没有网络的布局 */public void showNoNetworkView() { networkStateView.showNoNetwork(); networkStateView.setOnRefreshListener;}/** * 显示没有数据的布局 */public void showEmptyView() { networkStateView.showEmpty(); networkStateView.setOnRefreshListener;}/** * 显示数据错误,网络错误等布局 */public void showErrorView() { networkStateView.showError(); networkStateView.setOnRefreshListener;}
public class FragmentFactory { 

 private FragmentActivity mContext; 
 private static FragmentFactory factory = new FragmentFactory(); 
 //用于存储已创建的Fragment对象 
 private Map<String, Fragment> mFragmentMap=new HashMap<>(); 
 private int mLayoutId; 

 private FragmentFactory() { 
 } 

 public static FragmentFactory getInstance() { 
 return factory; 
 } 

 //layoutId 传入布局文件的id 
 public FragmentFactory init(FragmentActivity context,int layoutId) { 
 this.mContext = context; 
 this.mLayoutId=layoutId; 
 return factory; 
 } 

 public Activity getParentActivity() { 
 return mContext; 
 } 


 private <T extends Fragment> Fragment createFragment(Class<T> clazz) { 
 Fragment fragment = null; 
 try { 
 fragment = getFragment(clazz.getName()); 
 FragmentTransaction fragmentTransaction = mContext.getSupportFragmentManager().beginTransaction(); 
 hideFragment(fragmentTransaction); 
 if (fragment == null) { 

 fragment = (Fragment) clazz.newInstance(); 
 setFragment(fragment); 
 fragmentTransaction.add(mLayoutId, fragment); 
 fragmentTransaction.commit(); 
 } else { 
 fragmentTransaction.show(fragment); 
 fragmentTransaction.commit(); 
 } 
 } catch (InstantiationException e) { 
 e.printStackTrace(); 
 } catch (IllegalAccessException e) { 
 e.printStackTrace(); 
 } 
 return fragment; 
 } 

 private <T extends Fragment> Fragment getFragment(String className) { 
 Fragment fragment = mFragmentMap.get(className); 
 return fragment; 
 } 

 private <T extends Fragment> void setFragment(Fragment fragment) throws InstantiationException, IllegalAccessException { 
 String className = fragment.getClass().getName(); 
 mFragmentMap.put(className, fragment); 
 } 

 private void hideFragment(FragmentTransaction fragmentTransaction) { 
 Set<String> keySet = mFragmentMap.keySet(); 
 for (String key : keySet) { 
 Fragment fragment = mFragmentMap.get(key); 
 fragmentTransaction.hide(fragment); 
 } 

 } 

 public <T extends Fragment> T showFragment(Class<T> clazz) { 
 return (T) createFragment(clazz); 
 } 
} 

嗯,这样我们就可以统一设置NetworkStateView了,不过大家可能注意到,这样其实会给每个Activity增加多了一层FrameLayout,如果子类Activity本身就是LinearLayout还会增加多一层,有人可能会觉得影响性能,不过个人认为这样会极大增加便利性,而且对于一层FrameLayout的影响并不是很大,所以觉得这种方法还是可取的,不过读者可自行考虑

调用代码:

  • 定义Activity栈,进行Activity的保存
FragmentFactory mFragmentFactory = FragmentFactory.getInstance().init(this, R.id.fl_content); 
mFragmentFactory.showFragment(MyFragment1.class); 
mFragmentFactory.showFragment(MyFragment2.class); 

在开发的时候,我们可能会遇到这样的情况,在启动了多个Activity之后需要一次性销毁并退出应用,
又或者是销毁顶部一个或多个Activity,显示指定的Activity,在这样的情形下,我们可以定义一个
Activity栈,在Activity执行onCreate时在栈里添加此Activity,在Activity指定onDestroy
时在Activity栈中移除

上面的封装用到了泛型、工厂、单例等知识。只需要在Activity初始化一次对象就可以一行代码管理Fragment了,想显示哪个页面就传入对应的Fragment的class。

在这样的每次添加和移除操作的共有逻辑,我们就可以在BaseActivity进行书写,只要Activity继承BaseActivity便可以在Activity进行添加移除了

简单通用的适配器

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId; ... ActivityUtils.addActivity;}@Overrideprotected void onDestroy() { super.onDestroy(); ... ActivityUtils.removeActivity;}

ListView是Android最常用的一个组件,优化Litsview那就是必不可少的工作了。

ActivityUtils就是我们定义的Activity栈工具类

用Listview最痛苦的就是写BaseAdapter的getView()方法,一遍又一遍的写,大部分代码都是重复冗余,但又不得不写。下面来抽取冗余的代码封装起来。

public class ActivityUtils { private static Stack<Activity> mActivityStack; /** * 添加一个Activity到堆栈中 * @param activity */ public static void addActivity(Activity activity) { if (null == mActivityStack) { mActivityStack = new Stack<>(); } mActivityStack.add; } /** * 从堆栈中移除指定的Activity * @param activity */ public static void removeActivity(Activity activity) { if (activity != null) { mActivityStack.remove; } } /** * 获取顶部的Activity * @return 顶部的Activity */ public static Activity getTopActivity() { if (mActivityStack.isEmpty { return null; } else { return mActivityStack.get(mActivityStack.size; } } /** * 结束所有的Activity,退出应用 */ public static void removeAllActivity() { if (mActivityStack != null && mActivityStack.size { for (Activity activity : mActivityStack) { activity.finish(); } } }}
public abstract class CommonAdapter<T> extends BaseAdapter { 
 //需要显示的数据,List中的类型为泛型,因为不知道用户的封装Bean 
 private List<T> mDatas; 

 private Context mContext; 
 //布局文件Id 
 private int mLayoutId; 
 public CommonAdapter(Context context,List<T> data,int layoutId) { 
 mDatas = data; 
 mContext = context; 
 mLayoutId = layoutId; 
 } 
 @Override 
 public int getCount() { 
 return mDatas.size(); 
 } 

 @Override 
 public Object getItem(int position) { 
 return mDatas.get(position); 
 } 

 @Override 
 public long getItemId(int position) { 
 return position; 
 } 

 @Override 
 public View getView(int position, View convertView, ViewGroup parent) { 
 ViewHolder holder = ViewHolder.getHolder(mContext,convertView, parent, mLayoutId); 
 setDatas(holder,getItem(position)); 
 return holder.getConvertView(); 
 } 

 /** 
 * 为各个item中的控件设置数据 
 * @param holder ViewHolder 
 * @param object 从集合中所取的一个对象 
 */ 
 public abstract void setDatas(ViewHolder holder, Object object); 
} 


public class ViewHolder { 
 private View mConvertView; 
 //用来存布局中的各个组件,以键值对形式 
 private HashMap<Integer,View> mViews = new HashMap<>(); 
 //ViewHolder构造函数,只有当convertView为空的时候才创建 
 public ViewHolder(Context context,View convertView, ViewGroup parent, int layouId) { 
 convertView = LayoutInflater.from(context).inflate(layouId,parent,false); 
 convertView.setTag(this); //将其setTag() 
 mConvertView = convertView; 
 } 
 //返回一个ViewHolder对象 
 public static ViewHolder getHolder(Context context, View convertView, ViewGroup parent, int layoutId) { 
 if (convertView == null) { 
 return new ViewHolder(context,convertView,parent,layoutId); 
 }else { 
 return (ViewHolder) convertView.getTag(); 
 } 
 } 
 //返回一个View的子类对象,因为不确定用户布局有什么组件,相当于findViewById 
 //这里返回一个泛型,也可以返回一个View或Object 
 public <T extends View>T getView(int resId) { 
 View view = mViews.get(resId); //从集合中取出这个组件 
 if (view == null) { //如果为空,说明为第一屏 
 view = mConvertView.findViewById(resId); //从convertView中找 
 mViews.put(resId,view); 
 } 
 return (T) view; 
 } 

 public View getConvertView() { 
 return mConvertView; 
 } 
} 
  • 定义共有的UI操作

调用代码:

为了方便子类Activity可以执行共有的UI操作,我们可以将共有的UI操作写在BaseActivity中,例如显示DialogToastSnackbar

public class MyAdapter extends CommonAdapter<Bean> { 
 public MyAdapter(Context context, List<Bean> data, int layoutId) { 
 super(context, data, layoutId); 
 } 
 @Override 
 public void setDatas(ViewHolder holder, Object object) { 
 Bean bean = (Bean) object; 
 ((TextView)holder.getView(R.id.title_Tv)).setText(bean.getTitle()); 
 ((TextView)holder.getView(R.id.desc_Tv)).setText(bean.getDesc()); 
 ((TextView)holder.getView(R.id.time_Tv)).setText(bean.getTime()); 
 ((TextView)holder.getView(R.id.phone_Tv)).setText(bean.getPhone()); 
 } 
} 


 List<Bean> data=new ArrayList<>(); 
 Bean bean=new Bean("标题1", "内容1", "时间1", "18300000000"); 
 Bean bean2=new Bean("标题2", "内容2", "时间2", "18300000000"); 
 data.add(bean); 
 data.add(bean2); 
 listView.setAdapter(new MyAdapter(context, data, R.layout.listview_item)); 
private void initDialog() { mDialog = new Dialog(this, R.style.dialog_transparent_style); mDialogContentView = LayoutInflater.from.inflate(R.layout.dialog_loading, null); tv_loadText =  mDialogContentView.findViewById(R.id.tv_loading_text); iv_loadImage = (ImageView) mDialogContentView.findViewById(R.id.iv_load_image); pb_loadProgress = (ProgressBar) mDialogContentView.findViewById(R.id.pb_load_progress); mDialog.setCanceledOnTouchOutside; mDialog.setContentView(mDialogContentView); Window window = mDialog.getWindow(); if (null != window) { window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); }}public void showProgressDialog() { if (mDialog != null && !mDialog.isShowing { pb_loadProgress.setVisibility(View.VISIBLE); iv_loadImage.setVisibility(View.GONE); tv_loadText.setVisibility(View.GONE); mDialog.show(); }}public void showToast(String text) { if (!TextUtils.isEmpty { Toast.makeText(App.getApplication(), text, Toast.LENGTH_LONG).show(); }}

注释写的很清楚了,就不多说了。

BaseActivity写了显示Toast,Dialog等的UI操作后,在子类Activity就可以共同调用了,可以减少共有操作的重写,当然了,不写在BaseActivity也是可以,我们可以封装成一个工具类,在需要用的时候再调用工具类的相关方法也是可以的,这就看个人的喜好了,哈哈

自定义组合控,布局模块化

  • 友盟消息推送,统计

正常的项目开发中肯定有很多布局冗余例如下面图红框中的设置和导航。

在项目发布之后,我们可能需要向用户推送消息,需要知道用户在界面的点击次数,异常信息等,以方便我们对产品的改进

冠亚体育手机网站 1

由于在每个Activity或Fragment界面我们都要进行统计,所以我们需要让每个Activity或Fragment都要调用到友盟的API,但是如果一个一个Activity或Fragment的去进行调用,是非常繁琐的,特别是如果项目比较大,Activity和Fragment的数量较多,更是不可想象

很多人会把这些布局文件一遍又一遍的复制,只修改其中的ID、字符串等,其他部分几乎一模一样,造成布局文件代码特别多。

所以我们应该在BaseActivity的生命周期方法去调用友盟API

最要命的不是这个,而且把所有的逻辑写在Activity\Fragment里,造成Activity\Fragment特别的庞大,真正实现一坨X代码。

@Overrideprotected void onResume() { MobclickAgent.onResume;}@Overrideprotected void onPause() { MobclickAgent.onPause;}

我觉得应该把公用的布局单独抽取出来到一个xml里,再用一个GroupView去处理这些逻辑和业务,减少activity\Fragment的负担。

  • ButterKnife,EventBus等

代码就不贴了,自己去源码demo里查看ParamSwitchView,这个View是图1的一个Item,封装了布局和所需要的遥控按键左右切换数据的逻辑。

使用过ButterKnife和EventBus的同学都知道,ButterKnife需要进行View的绑定和解绑,而EventBus需要对Activity对象进行注册和反注册,这样我们也不会在每个Activity都去重复进行这一操作,而是在BaseActivity进行bindunbind的操作,用到EventBus时,也可以在BaseActivity进行注册和反注册

面向接口编程

private Unbinder unbinder;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { ... unbinder = ButterKnife.bind; EventBus.getDefault().register; ActivityUtils.addActivity; initDialog(); ...}public void onEventMainThread(BaseEvent event) { ...do...}@Overrideprotected void onDestroy() { super.onDestroy(); unbinder.unbind(); EventBus.getDefault().unregister; ActivityUtils.removeActivity;}

面向接口编程的意思是指在面向对象的系统中所有的类或者模块之间的交互是由接口完成的。

  • Android6.0运行时权限处理

父类的引用指向子类对象,指向不同的子类对象,产生不同的行为:

在Android6.0版本以上,对于一些危险权限,需要用户授权之后才能使用,这也需要调用对危险权限进行申请

父 a =new 子A;

在申请危险权限时,需要在Activity中进行代码编写申请,虽然对于权限处理的代码并不是很复杂,但是如果在多个Activity中都需要申请权限,就需要编写很多重复的代码,所以最好就能做一个封装,最简单的封装方法就是将其统一进行设置在BaseActivity中,这样子类Activity能统一调用

有很多童靴在项目开发中经常更变业务,例如:定制化系统应用,底层的接口在不同型号的TV\手机上都有可能不一样。
这时候把这些底层接口单独封装在一个类进行管理,在平台发生改变的时候只需要改变实现。

在进行封装时,先写一个权限回调接口

定义接口类统一化管理方法

public interface PermissionListener { void onGranted(); void onDenied(List<String> deniedPermissions);}
public interface IManager { 

 void setBackLight(int value); 
 void setPictureMode(int mode); 

} 

在接口中定义两个方法,分别是统一授权和取消授权,定义接口之后,在BaseActvity中定义一个申请权限的方法,该方法的代码如下

实现类 1

public static void requestPermissions(String[] permissions, PermissionListener listener) { Activity activity = ActivityUtils.getTopActivity(); if (null == activity) { return; } mPermissionListener = listener; List<String> permissionList = new ArrayList<>(); for (String permission : permissionList) { //权限没有授权 if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) { permissionList.add(permission); } } if (!permissionList.isEmpty { ActivityCompat.requestPermissions(activity, permissionList.toArray(new String[permissionList.size, CODE_REQUEST_PERMISSION); } else { mPermissionListener.onGranted(); }}
public class HuaWeiManager implements IManager { 

 @Override 
 public void setBackLight(int value) { 
 <strong>HuaWei</strong>.savaBackLight(value); 
 } 

 @Override 
 public void setPictureMode(int mode) { 
 <strong>HuaWei</strong>.setPictureMode(mode); 
 } 

} 

可以看到,方法需要有两个参数,分别是需要申请的权限,是一个String数组,另一个则是权限的回调接口,用于在授权或取消授权后能做出相应的处理,接着在方法里面对权限数组进行一个循环判断其是否已经授权,对于没有授权的会添加到List中,可以用于再次申请,在申请之后,需要重写Activity的onRequestPermissionsResult方法,判断授权结果

假如现在业务需求是华为的定制系统,只需要调用华为的子类

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case CODE_REQUEST_PERMISSION: if (grantResults.length > 0) { List<String> deniedPermissions = new ArrayList<>(); for (int i = 0; i < grantResults.length; i++) { int result = grantResults[i]; if (result != PackageManager.PERMISSION_GRANTED) { String permission = permissions[i]; deniedPermissions.add(permission); } } if (deniedPermissions.isEmpty { mPermissionListener.onGranted(); } else { mPermissionListener.onDenied(deniedPermissions); } } break; default: break; }}
IManager iManager=new HuaWeiManager(); 
iManager.setBackLight(100); 

这样统一封装在BaseActivity之后,在子类Activity只要调用requestPermissions方法并传入权限数组和回调接口的参数就能对结果进行对应的处理了

如果业务需求转变成小米,那么只需要创建一个类进行实现

在上面的分析知道,基类BaseActivity可以用于处理子类共有的逻辑,这样可以比main重复进行处理同一逻辑,在本篇文章中,讲到的内容还是有限的,对于其他的共有逻辑,可以自行补充

实现类 2

本篇文章的代码已上传到github,在github上对应着BaseProject项目,该项目将会对于在开发中经常需要使用到的例如BaseActivityBaseFragment冠亚体育手机网站,,网络请求等进行一些基本封装,方便以后使用,项目将会持续更新,欢迎大家进行star和关注

public class XiaoMiManager implements IManager { 

 @Override 
 public void setBackLight(int value) { 
 XiaoMi.savaBackLight(value); 
 } 

 @Override 
 public void setPictureMode(int mode) { 
 XiaoMi.setPictureMode(mode); 
 } 

} 

调用代码里只需要把HuaWeiManager改成XiaoMiManager就能适配其他机型了。

//IManager iManager=new HuaWeiManager(); 
IManager iManager=new XiaoMiManager(); 
iManager.setBackLight(100); 

在这里只是灌输一个编码思维,实际开发中突发情况比较多,并不一定全部适用。

在编码之前一定要花一点点时间简单构思和组织一下代码,不要想到什么写什么。

注重工具类的封装

我们正常的开发中经常用到很多不需要在逻辑层编写的方法,我们就可以单独的把他抽取出来放在单独的类里面去单独管理。

例如:Toast 、SharePreference、获取时间、系统版本、网络、MD5等等。。。。

这些东西都可以单独的封装和管理,减少逻辑层的代码,并且也可以让其他逻辑层调用。

坏习惯

有些人喜欢把定义个Tools这样的工具类,里面存放着所有的工具方法。

1.
网络、Toast、状态、时间等等全部都用一个类去管理,这样造成的后果就是后期不方便维护和不利于更新,代码看起来杂乱无章。

  1. 把一些公共的方法直接在逻辑层构建,其他地方需要就直接复制一份过去。

或者有其他相同的比较类似的方法没有进行封装,在其他地方直接复制过去只修改其他一行的代码。

好习惯

1.
把这些tools单独创建各种各样的tools去存放这些方法,Toast只存Toast相关的,网络只存网络相关的,避免交杂在一起。也符合设计原则之一的:单一原则。

  1. 类似的方法独立抽取出来,用传参flag标记去区分应用场景。

源码里收藏了一些常用的工具类分享给大家。

MVP分层架构

去年写了一篇关于它的文章,大家可以看看。能够让代码变得异常的清晰。

//www.jb51.net/article/98422.htm

改善代码的方式很多很多,一下子想不完,后面想到了什么再继续更新分享。

您可能感兴趣的文章:

  • Android源代码仓库及其管理工具Repo分析详解
  • Android
    采用AOP方式封装6.0权限管理的方法
  • 浅谈Android中线程池的管理
  • Android版学生管理系统
  • 详解Android使用Gradle统一配置依赖管理
  • 直接可用的Android
    studio学生信息管理系统
  • Android实现电池管理系统

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注