温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

关于重构的一些思想

发布时间:2020-06-20 03:01:28 来源:网络 阅读:842 作者:屠夫章哥 栏目:移动开发

DRY原则:Don't Repeat Yourself (摘自wikipedia


OOA和OOD的作用及其区别 http://blog.sina.com.cn/s/blog_72ed42d401015y5c.html


站在为程序员提供第3方服务SDK的高度来写代码


1.抽象方法和非抽象方法

如果在类中定义了抽象方法,就是强制非抽象子类去实现。这样写的好处就是可以提醒子类需要复

写哪些方法,但是抽象方法不易过多,不需要强制实现的方法不要抽象化。


如果在类中定义了非抽象方法,可以给一个默认的实现,子类可以选择不复写,采用父类默认的实

现,也可以复写自定义实现。

应用场景:

  抽取了一个带ListView的BaseListFragment,后来又有需求,需要一个带头的ListView的

Fragment,那么,就没有必要再写一个类继承BaseListFragement了,况且继承了ListView的初始化已经

完成了,没法再加一个头。

  所以直接在BaseListFragment里定义一个方法返回头视图,默认将方法返回值至为null,在listView

初始化的时候,将方法返回值添加到listview的头部,不为空就添加到头部。


2.关于抽象的方法在哪儿被调的问题

  既然抽了方法,肯定在父类里的某个地方要让其执行。  

  如果父类有自己的生命周期方法,会自动执行一些方法,在这些方法里可以抽出方法。

  如果父类没有自己的生命周期方法,那么调用抽取的方法的父类方法要记得手动去调用。


3.关于怎么抽取的问题

  情景1:抽象适配器,适配器的的抽象方法调用适配器所在父类的方法,父类的这个方法再抽象让

      子类去实现。如我的开源中国带indicator的框架就是这么抽取的。

      这种方式就是实现了方法的抽象和转移,将内部的抽象转移成父类的抽象


4.关于电脑模型

  其实编程和现实是息息相关的,如电脑和各个零部件就是抽取的具体体现,各个零部件相互配合,但

  是又没有焊死在一起,这就是降低了耦合度。


  程序的框架包括界面框架、网络加载框架,框架在抽取的时候应该降低它们之间的依赖性。


  


如果要写出资深的重构代码,必需要“精通”以下的知识:

   0.继承与多态

    静态与多态无关

 public class ClassA {
    private static final String TAG = "ClassA";
    public static void test(){
        Log.e(TAG, "test: ClassA");
    }
}
public class ClassB extends ClassA {
    private static final String TAG = "ClassB";
    public static void test(){
        Log.e(TAG, "test: ClassB");
    }
}

    调用

ClassB classA = new ClassB();
classA.test();

    输出:test: ClassB

    调用

ClassA classA = new ClassB();
classA.test();

   输出:test: ClassA

   **上面ClassA与ClassB同时存在test()方法,可见静态方法不受继承的影响,声明的类是什么哪个类,就调用哪个类的静态方法。

   ** 因此,如果想要一个类的方法可以被外部访问,但是又不想被子类复写,那么只有public static了。


 

 0.封装

      封装就是一个类A调用另外的一个类C时,不会直接调用,间接调用B,再用B去调用C。

      比如在程序里通过要开启很多个对象,如service,广播等。如果直接开启会造成很大的耦合。


  • 封装要注意的地方

    1)避免画蛇添足的封装

    2)只暴露上层需要的接口

    3)对下层的异常回调要全面,不要有任何一点遗漏。

    4)不要过早的重构、封装

        

  •       实例:模块加载器

      1)定义加载器接口:

            

public interface IDxTestInstanceLoader extends IAreaModuleLifeMethods{
    void load();
    void unload();
    void attach(Context context);
}

     2)实现加载器接口,创建实例。

       比如实现load接口,可以开启服务,也可以开启广播,也可以开启线程。

     



      3)定义加载器管理者

 

DxTestInstanceManager  IAreaModuleLifeMethods{
    Context List<IDxTestInstanceLoader> = ArrayList<>(Arrays.(IDxTestInstanceLoader[]{
            TrafficStatTestInstanceLoader()ShareScreenLoader()
    }))DxTestInstanceManager = DxTestInstanceManager()(){}


    DxTestInstanceManager (){
        }

    (IDxTestInstanceLoader dxTestInstanceLoader){
        .add(dxTestInstanceLoader)}

    (){
        (IDxTestInstanceLoader item : ){
            item.attach()item.load()}
    }

    (){
        (IDxTestInstanceLoader item : ){
            item.unload()}
    }

    TrafficStatTestInstanceLoader (){
        (.size() > ){
            (TrafficStatTestInstanceLoader) .get()}
        }

    (Context context) {
        .= context}

    () {
        IDxTestInstanceLoader iDxTestInstanceLoader = .get()(iDxTestInstanceLoader != ){
            iDxTestInstanceLoader.onResetSettingFiles()}
    }

    () {
        IDxTestInstanceLoader iDxTestInstanceLoader = .get()(iDxTestInstanceLoader != ){
            iDxTestInstanceLoader.onDeviceServiceDestroyed()}
    }

    () {
        IDxTestInstanceLoader iDxTestInstanceLoader = .get()(iDxTestInstanceLoader != ){
            iDxTestInstanceLoader.onDeviceUpdateStepPassed()}
    }
}
  •   巧用runnable

      有时候执行某些方法,必须有一些前提条件,没有必要再每个要执行的方法里单独写执行条件的判断。可以封装一个方法:

      

  • private void runTask(Runnable runnable) {
        if (isBleConnected) {
            runnable.run();
        } else {
            shortToastOnUiThread("请先连接蓝牙再操作!");
        }
    }

       

      


  1.接口

    情景1:对几个列表按时间字段进行排序,但是几个列表的实体类之间没有任何的继承关系,得到

       时间的方法也不一样,所以对于每一个列表都要定义一个比较器。

       关于重构的一些思想

       我在想,同样比较的是时间,为什么要定义3个比较器呢?

        于是,我定义了一个接口:

       

    public interface     DateInterface {
         String getDate();
    }

        让每个列表的实体类都去实现这个接口:

      

public class BidRecordSQL extends Entity implements DateInterface{
    //...
    @Override
    public String getDate() {
       return investTime;
    }
}
public class BaseDepositsHistoryDomain extends Entity implements DateInterface{
    //...
    @Override
    public String getDate() {
       return investTime;
    }
}

       然后定义一个时间比较器:

    

/**
 * 时间比较器-降序排序
 * Created by Zhang on 2016/2/15.
 */
public class DescendingDateComparator implements Comparator<DateInterface> {
    @Override
    public int compare(DateInterface lhs, DateInterface rhs) {
        return -1 * lhs.getDate().compareTo(rhs.getDate());
    }
}

      然后,使用Collections工具类对List进行排序:

    关于重构的一些思想   

  使用接口,本质的作用是让原本看似不相干的实体类之间产生了关系。也就是定义相同的行为,getDate, 然后比较器针对接口编程,而不是某个具体的类。

  

  情景2:ViewPager装了多个Fragment,只想在viewpager滑动到哪一页,就更新哪一页的数据。

     一般人的做法就是在每个Fragment定义一个加载数据的方法,然后在onPageChange方法里根据

     position得到对应的Fragment,然后调用fragment的对应方法。

      如果让每一个Fragment实现一个懒加载数据的接口,那么在onPageChange就不需要根据posit       ion去if-else了,直接将fragment强转成接口,调用接口的方法即可。


  定义接口,有哪些好处:

  1)方便明确业务

  2)如果更换方法名,所有实现了接口的类都会自动更换,避免了手动更换的麻烦。

  3)方便其他模块调用,其他模块只关心接口的方法就行,不需要关注接口实现类的其他方法。

  

 


  2.反射

    反射需要注意的地方:反射的类在某个版本可能更新了,之前的版本可能没有某个方法,反射就会报NoSuchMethod异常。

  • 给Java类动态的增加属性

    最近在做一个项目的时候,用到JSON解析,会创建很多的实体,但这些实体一般只有一个属性,我在想,有没有一种技术只创建一个空的类,然后动态的改变它的属性呢

      后来百度了一下,确实存在这样的技术,Cglib

      

        https://blog.csdn.net/WUWENJINWUWENJIN/article/details/83276553

        https://blog.csdn.net/didi7696/article/details/82351167

        https://blog.csdn.net/zghwaicsdn/article/details/50957474/

        https://blog.csdn.net/li951418089/article/details/50392727

        http://www.gaohaiyan.com/1772.html

        https://www.cnblogs.com/zxf330301/p/5798241.html

        https://www.it610.com/article/182201.htm

      但是无赖的是这个技术在纯Java代码里可以正常执行(随便定义一个类,一个main方法,类无任何的继承。) ,但是在Activity里使用的话,就会报一个错误。要搞懂这个错误就得真正理解ClassLoader等类加载机制的原理。

        

     

  情景1:字段过滤器

  

public interface IPropertyFilter {
      boolean apply(Object object, String name, Object value);
}


public interface IGetFieldMap {
    Map getFieldMap();           //所有字段名,拼出map。
    Map getFieldMap(String ...fieldNames);              //根据字段名,拼出map。
    Map getFieldMapExcept(String ...exceptFieldNames);   //除了指定的几个字段名,拼出map。
    Map getFieldMap(List<String> fieldNames);           //根据字段名,拼出map。
    Map getFieldMap(IPropertyFilter propertyFilter);    //根据过滤器,拼出map。
}
public class BaseRequestBean implements IGetFieldMap {
    @Override
    public Map getFieldMap() {
        return getFieldMap(new IPropertyFilter() {
            @Override
            public boolean apply(Object object, String name, Object value) {
                return true;
            }
        });
    }

    @Override
    public Map getFieldMap(String... fieldNames) {
        return getFieldMap(Arrays.asList(fieldNames));
    }

    @Override
    public Map getFieldMapExcept(final String... exceptFieldNames) {
        return getFieldMap(new IPropertyFilter() {
            @Override
            public boolean apply(Object object, String name, Object value) {
                for (String item : exceptFieldNames){
                    if(name.equals(item)){
                        return false;
                    }
                }
                return true;
            }
        });
    }

    @Override
    public Map getFieldMap(List<String> fieldNames) {
        Map<String, Object> result = new HashMap();
        Class mClass = getClass();
        Field[] declaredFields = mClass.getDeclaredFields();
        for (Field field : declaredFields) {
            String fieldName = field.getName();
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            try {
                Object fieldValue = field.get(this);
                if (fieldValue == null) continue;
                if (!fieldNames.conta×××(fieldName)) continue;

                result.put(fieldName, fieldValue);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    @Override
    public Map getFieldMap(IPropertyFilter propertyFilter) {
        Map<String, Object> result = new HashMap();
        Class mClass = getClass();
        Field[] declaredFields = mClass.getDeclaredFields();
        for (Field field : declaredFields) {
            String fieldName = field.getName();
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            try {
                Object fieldValue = field.get(this);
                if (!propertyFilter.apply(this, fieldName, fieldValue))  continue;

                result.put(fieldName, fieldValue);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

}

 

  情景2:打印Build类里所有的字段

  https://blog.csdn.net/xiaoxian8023/article/details/24109185

  

  情景3:引用hide类并调用其方法

  https://blog.csdn.net/pshiping2014/article/details/79549680

  

  linux工程师说将一个key存储到build里了,但是查看Build类发现可以通过SystemProperties这个类来获取,但是这个类是hide类,所以只能用反射去拿。

 


  3.注解

 4.泛型

   Java泛型详解

   http://blog.csdn.net/jinuxwu/article/details/6771121

   

   获取泛型的Class 

   http://www.cnblogs.com/onlysun/p/4539472.html

 

 4-5.枚举(优化代码可读性)

   http://blog.csdn.net/lmj623565791/article/details/79278864

  •    枚举如何定义构造参数?

       注意最后一个枚举要加;然后按照正常的类写语法就行了。

public enum BleAreaType {
    HENAN("河南"),                      //河南地区
    GUIZHOU("贵州"),                    //贵州地区
    GUANGXI("广西");                      //广西地区
    private String areaChineseName;
    BleAreaType(String areaChineseName){
        this.areaChineseName = areaChineseName;
    }

    public String getAreaChineseName() {
        return areaChineseName;
    }
}

         

  5.自定义

   自定义View或者类有多种形式:

   1)完全自定义(复写onDraw 、onLayout)

   2)组合自定义

   3)包裹自定义(在自定义属性绑定需要操作的子View的id,在onFinishInflate方法里find处理相关的逻辑,Google的很多框架级的原生组件用的就是这个)

    如DrawerLayout,抽屉控件等。

   4)工具类中封装view,利用已有的view实现统一的业务接口。

   5)复写Android自定义的类(要求研究源码,然后才能随心所欲的复写哈。)

    实际场景1:使用ArrayAdapter这个类填充Spinner,如果ArrayAdapter的泛型是一个对象的话,最终Spinner显示的是对象的哈希值。而我真正想展示在

          Spinner上的只是ArrayAdapter泛型的某个字段而已。

    最笨的解决方法就是遍历List<T>,得到想要展示的字符串集合List<String>,再将List<String>设置给适配器。但是这样一来的话,在spinner里点击事件

    里往往又会用到List<T>,这样就容易造成混乱。

    


    于是研究了一下ArrayAdapter这个类的源码,看看它的view是如何生成的。

      public View getView(int position, View convertView, ViewGroup parent) {
    return createViewFromResource(mInflater, position, convertView, parent, mResource);
}

private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
        ViewGroup parent, int resource) {
    View view;
    TextView text;

    if (convertView == null) {
        view = inflater.inflate(resource, parent, false);
    } else {
        view = convertView;
    }

    try {
        if (mFieldId == 0) {
            //  If no custom field is assigned, assume the whole resource is a TextView
            text = (TextView) view;
        } else {
            //  Otherwise, find the TextView field within the layout
            text = (TextView) view.findViewById(mFieldId);
        }
    } catch (ClassCastException e) {
        Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
        throw new IllegalStateException(
                "ArrayAdapter requires the resource ID to be a TextView", e);
    }

    T item = getItem(position);
    if (item ×××tanceof CharSequence) {
        text.setText((CharSequence)item);
    } else {
        text.setText(item.toString());
    }

    return view;
}

   通过源码发现,如果ArrayAdapter的泛型是字符串,那么spinner展示的是字符串;如果ArrayAdapter的泛型是一个对象的话,返回的是这个对象的toString方法的返回值。

   解决方案1:复写ArrayAdapter的getView的相关方法。

      此解决方案的核心是将ArrayAdapter展示Spinner内容部分的具体代码抽象化成方法,从而使ArrayAdapter亦抽象化。

     但是此方式有一个弊端:每有一个泛型类,就得新建一个对应的Adapter类,太浪费资源。 

/**
 * Created by 陈章 on 2017/12/19.
 * 适配器
 */
public abstract  class CZArrayAdapter<T> extends ArrayAdapter{
    public CZArrayAdapter(Context context,  List<T> objects) {
        super(context, android.R.layout.simple_spinner_item, objects);
    }
    
    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(LayoutInflater.from(getContext()), position, convertView, parent);
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(LayoutInflater.from(getContext()), position, convertView, parent);
    }

    protected abstract String getText(T t);

    private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
                                        ViewGroup parent) {
        T item = (T) getItem(position);
        View view;
        TextView text;

        if (convertView == null) {
            view = inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
        } else {
            view = convertView;
        }
        text = (TextView) view;
        text.setText(getText(item));
        return view;
    }

}

  

 解决方案2:复写ArrayAdapter的getView的泛型类的toString方法

      复写泛型类的toString方法,返回想在spinner上展示的字段。如果泛型类的toString方法没有在其它地方有特殊的引用,这种解决方法是最快最简单的。 

6.配置  

   对于一些比较固定的配置,不要使用修改代码的方式,而是通过编辑配置文件、读配置文件的方式。这样更加的安全。


6.拦截思想  

   我们可以把一个正常的流程想象成一根线,有时想改变这根线的某一个点的执行流程,程序可能向后或者不向后执行。我们需要做的就是定义拦截器。

   示例1设备按下一个物理按键,通用程序会执行一个功能1,但是某一个地区可能需要执行功能2.

   一般人的想法就是,直接在通用程序里通过if-else来判断地区。将地区的逻辑,写在通用的代码里,这就是一种耦合。假如后续要增加地区,又要实现不同的功能,那这块的代码就会呈现爆炸式的增长。

   正确的做法就是:定义拦截器,地区设置了拦截器,就将数据丢给拦截器处理,各地区的功能代码写在各地区的模块里。这样就优美的将地区和通用程序的代码解耦了

          

...
case 1:// 升迈消息处理
    MessageBean messageBean = (MessageBean) msg.obj;
    QualityChecker.getInstance(SocketService.this,virtualSMSerialPort).onSMCallBack(messageBean);
    AndroidConsoleLogPrinter.e("升迈消息处理 " ,"cmd = " + Integer.parseInt(messageBean.getCMD(), 16));

    //回调升迈消息给地区子模块
    boolean intercept = false;
    if(smMessageReceiver != null){
       intercept = smMessageReceiver.dispatchSmIntent(messageBean);
    }
    AndroidConsoleLogPrinter.e("cmd = " + messageBean.getCMD() , "intercept: "  + intercept);
    if(intercept) return;       //子地区拦截复写了对应的指令,基本模块不再执行。
    switch (Integer.parseInt(messageBean.getCMD(), 16)) {
        case 0x1A:// OBU通道命令
...

     示例2通用程序需要给单片机定时发送一个心跳数据,单片机指示灯好显示各个状态的情况。但是某一个地区没有电子狗,心跳的部分数据还不一样,指示电子狗状态的netDogState字段,需要替换成另外一个应用的状态。

      正确做法:定义拦截器,拦截心跳数据,替换部分心跳数据,返回新的心跳数据。 

...
    //由于不同的地区,回复的心跳可能不太一样。需要让子区模块进行拦截
String heartData = gps+"," + Latitude+ "," + Longitude+"," + sim+"," + wifiState+"," + netDogState+"," + isNetwork+"," + statusInfo.gpsSpeed;

if(smMessageReceiver != null){
    String heartDataNew = smMessageReceiver.dispatchSmHeartIntent(heartData);
    cmd = SerialInterface.consistStatusPush(CodeTool.splitStringArray(heartDataNew, ","));
}else{
    cmd = SerialInterface.consistStatusPush(CodeTool.splitStringArray(heartData, ","));
}

virtualSMSerialPort.input(cmd);
...

        示例2客户端,接收到服务端的一个命令字,就会创建一个对象解析对应此命令字。 并且命令字解析完了, 直接就做UI显示了。

                    但是现在客户端有一个测试需要,需要执行多个命令字,都执行成功才算成功。

        

        关于重构的一些思想

         问题就来了,现在每个命令字都是单独解析处理的。需要集中处理,于是定义一个拦截器:

    /**
 * Created by XinYi on 2019/7/25.
 * 由于集成测试,需要拦截。
 */
public class CommandIntecepter {
    private boolean intercepted = false;            //是否拦截指令,只让自己处理。
    private InterceptCallBack interceptCallBack;

    private static final CommandIntecepter ourInstance = new CommandIntecepter();

    public static CommandIntecepter getInstance() {
        return ourInstance;
    }

    private CommandIntecepter() {
    }

    public void intercept(InterceptCallBack interceptCallBack){
        intercepted = true;
        this.interceptCallBack = interceptCallBack;
    }

    public void cancelIntercept(){
        intercepted = false;
        this.interceptCallBack = null;
    }

    public boolean isIntercepted() {
        return intercepted;
    }

    public InterceptCallBack getInterceptCallBack() {
        return interceptCallBack;
    }

    public interface  InterceptCallBack{
        void onA1(boolean success);
        void onA2(boolean success);
        void onA3(boolean success);
        void onA4(boolean success);
        void onA5(boolean success);
        void onA6(boolean success);
        void onA7(boolean success);
        void onFailure();
    }
}

        每一个命令字,都加上拦截处理:

/**    
     * Created by XinYi on 2018/9/29.
     * 设备握手响应
     */
    public class B1ResponseActioner extends BaseActioner {
        private static final String TAG = "B1ResponseActioner";
        private static B1ResponseActioner instance = new B1ResponseActioner();
        public static B1ResponseActioner getInstance(){
          return instance;
        }
    
        @Override
        public void action(String realData) {
            AndroidConsoleLogPrinter.d(TAG, "action: 收到设备握手响应<<-- A1 ");
            CommonResponseBean commonResponseBean = new CommonResponseBean(realData);
            if(commonResponseBean.isOK()){
                if(CommandIntecepter.getInstance().isIntercepted()){
                    CommandIntecepter.getInstance().getInterceptCallBack().onA1(true);
                    return;
                }
                BleResultUIDisplayer.getInstance().onA1(commonResponseBean.getData());
            }else{
                if(CommandIntecepter.getInstance().isIntercepted()){
                    CommandIntecepter.getInstance().getInterceptCallBack().onA1(false);
                    return;
                }
                BleResultUIDisplayer.getInstance().onFailure("设备握手响应,回复失败.");
            }
        }
    }

        测试代码里,加上拦截器的控制代码,这样各个指令的回调就集中了,便于判断处理:

        千万注意:拦截器一定要准确的取消拦截,不能影响拦截器切点处的代码执行

        

    private void onceTest(final OnceTestCallBack onceTestCallBack){

    CommandIntecepter.getInstance().intercept(new CommandIntecepter.InterceptCallBack() {
        @Override
        public void onA1(boolean success) {

        }

        @Override
        public void onA2(boolean success) {
          if(success){
              JuLiBleServerSDK.getInstance().getShangHaiPure33ProtocolSender().send(ClientCmds.REQUEST_CMD_A3, RequestDataGenerateUtil.createA3Data(iccCos));
          }else{
              onceTestCallBackFailure(onceTestCallBack,"ICC复位失败");
          }
        }

        @Override
        public void onA3(boolean success) {
            if(success){
                JuLiBleServerSDK.getInstance().getShangHaiPure33ProtocolSender().send(ClientCmds.REQUEST_CMD_A4, RequestDataGenerateUtil.createA4Data(true));
            }else{
                onceTestCallBackFailure(onceTestCallBack,"ICC通道失败");
            }
        }

        @Override
        public void onA4(boolean success) {
            if(success){
                JuLiBleServerSDK.getInstance().getShangHaiPure33ProtocolSender().send(ClientCmds.REQUEST_CMD_A5, RequestDataGenerateUtil.createA5Data(esamCos));
            }else{
                onceTestCallBackFailure(onceTestCallBack,"ESAM复位失败");
            }
        }

        @Override
        public void onA5(boolean success) {
            if(success){
                onceTestCallBack.onSuccess();
            }else{
                onceTestCallBackFailure(onceTestCallBack,"ESAM通道失败");
            }
        }

        @Override
        public void onA6(boolean success) {

        }

        @Override
        public void onA7(boolean success) {

        }

        @Override
        public void onFailure() {
            onceTestCallBackFailure(onceTestCallBack,"未知异常");
        }
    });

    //Picc复位
    JuLiBleServerSDK.getInstance().getShangHaiPure33ProtocolSender().send(ClientCmds.REQUEST_CMD_A2, RequestDataGenerateUtil.createA2Data(false, true));
}

          



8.注入 

    1)方法注入 (适用于静态方法无法扩展)

         最近在写音频播放,采用了MediaPlayer,发现这个类特别容易出现状态异常。就写了个类继承MediaPlayer,对各个方法稍微重写了下。

          MediaPlayer有很多静态的create方法,由于是静态的,子类无法复写,也没法扩展。create方法的内部会new MediaPlayer然后返回,这样create出来的是父类的对象,根本没有子类的属性。

          我尝试着再写个create方法,将new MediaPlayer弄一个抽象方法返回,但是抽象和静态是死敌,根本无法做到。我灵机一动,在自定义的create方法加个MediaPlayer的入参,这样子类在调用的时候就可以传

          递子类的对象了。


          

/**
     * 对父类的{@link MediaPlayer#create(Context, int)}方法作了拓展修改,增加了MediaPlayer对象参数,对象由外部创建,避免静态方法内部创建无法扩展。
     *
     * @param context
     * @param resid
     * @return
     */
    public static MediaPlayer create(Context context, MediaPlayer mp, int resid) {
        //注释的这段代码不加也不会有问题,加了会抛异常。
        int s = 0;
        /*try {
            s = (int) ReflectManger.invokeMethod(Class.forName("android.media.AudioSystem"), null, "newAudioSessionId");        //AudioSystem为hide,无法直接使用。
        } catch (NoSuchMethodException e) {
            //TODO 会抛异常 NoSuchMethodException: newAudioSessionId []
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }*/
        return create(context, mp, resid, null, s > 0 ? s : 0);
    }
    /**
     * 对父类的{@link MediaPlayer#create(Context, int, AudioAttributes, int)}方法作了拓展修改,增加了MediaPlayer对象参数,对象由外部创建,避免静态方法内部创建无法扩展。
     *
     * @param context
     * @param mp
     * @param resid
     * @param audioAttributes
     * @param audioSessionId
     * @return
     */
    public static MediaPlayer create(Context context, MediaPlayer mp, int resid,
                                     AudioAttributes audioAttributes, int audioSessionId) {
        try {
            AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
            if (afd == null) return null;
            final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                    new AudioAttributes.Builder().build();
            mp.setAudioAttributes(aa);
            mp.setAudioSessionId(audioSessionId);
            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
            afd.close();
            mp.prepare();
            return mp;
        } catch (IOException ex) {
            Log.d(TAG, "create failed:", ex);
            // fall through
        } catch (IllegalArgumentException ex) {
            Log.d(TAG, "create failed:", ex);
            // fall through
        } catch (SecurityException ex) {
            Log.d(TAG, "create failed:", ex);
            // fall through
        }
        return null;
    }


            这样一来,我对抽象又有了新的认识:抽象方法是对整个方法体的抽象,一般的带参方法是对方法体的部分抽象。

                

9.其它,肯定有,尚未总结。  

怎么解耦的问题:

      不解耦的弊端,比如我之前将有关android源码的探索全部全部放到一篇文章里,后来博客系统出现了点问题。差点导致那篇博客损毁。所以不解耦很明显有2个缺点:

        1)一损俱损:一个地方出现问题,会导致另外一个地方也出现问题。

        2)查阅不方便:将所有的内容写到一篇博客,就像将所有的代码写在一个类中。这样这个类看起来就比较麻烦。



  1.解耦

    1)View的解耦:一般如详情页面、带有Banner图的页面,里面的View可能会有很多。可以将

     大的View细分为小的View。

     

public abstract class BasePart<T> {
   /**
    * 获取当前模块的View对象
    * @return
    */
   public abstract View getView();
   
   /**
    * 处理逻辑和数据
    * @param t
    */
   public abstract void setData(T t);

   /**
    * startActivity with bundle
    *
    * @param clazz
    * @param bundle
    */
   protected void readyGo(Class<?> clazz, Bundle bundle) {
      Intent intent = new Intent(CommonHelper.context(), clazz);
      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);    //Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag.
      if (null != bundle) {
         intent.putExtras(bundle);
      }
      CommonHelper.context().startActivity(intent);
   }
}

  只要在activity里initview()的时候,new PartView(),将子View添加到Activity的ContainerView中

 ,请求到数据之后再partView.setData();


  但是PartView是一个很简单的类,如果需要Activity的参数(如调用Activity的方法等),可以在构造函数里传入Activity.



对于别人重构的框架,应该如何去用的问题:

  1.抽象类,肯定是要实现它示实现的方法。写逻辑就是对抽象方法的具体实现。


将一些公共的libs和基类放在依赖库中

  1)出现的问题:将工程的layout下的布局文件写到依赖库里,那么相关的资源color、style看着是

    明明定义了,但就是can't resolve.

  

AndroidStudio里所有项目的公共Library库,不要单独复制,要共有,防止以后要修改。

https://blog.csdn.net/codezjx/article/details/49531887


注意:如果ModuleA依赖了ModuleB,ModuleB有依赖了ModuleC,那么ModuleA的gradle里也要依赖moduleC,否则会报的ModuleB里找不到ModuleC的错误。

        上层的Module如果依赖了下层的Module,上层的module要依赖下层所依赖的所有module。!!!

        

        如果工程有多个module,一定要从子module找起,单独打开某个module集合目录 ,排除依赖没有写的情况 。!!!!!!!!!!!


module里不能有application结点,所以module里的service、receiver这些结点也只能挪到app里定义。


aar的引用

      https://blog.csdn.net/u013440413/article/details/78685192   (app里使用aar)

 

      https://blog.csdn.net/lin_dianwei/article/details/79532078      (module里使用aar)


      注意build.gradle里使用implecation compile api依赖的区别

AndroidStudio 多层级 Module 对 aar 引用问题

        https://www.cnblogs.com/bellkosmos/p/6146349.html



关于多重依赖清单文件重复的问题:

        https://www.cnblogs.com/bluestorm/p/6692789.html


关于依赖多个module,可能有一个致命的问题。各个module及app的compileSdk的版本不一致,可能会导致以下问题:

        Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.

        > java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

        上面这个错误并非是jar包重复,可能是各个module及app的compileSdk的版本不一致导致的。



  • 单个module打jar包

    1)在android结点增加 

  •   lintOptions {
            abortOnError false
        }


    2)在module的gradle里书写打包脚本

  • //修改jar名字+将指定jar生成的地方
    task makeJar(type: Copy) {
        //删除存在的
        delete 'build/libs/cz_zbar_sdk.jar'
        //设置拷贝的文件
        from('build/intermediates/intermediate-jars/release/')
        //打进jar包后的文件目录
        into('libs/')
        //将classes.jar放入build/libs/目录下
        //include ,exclude参数来设置过滤
        //(我们只关心classes.jar这个文件)
        include('classes.jar')
        //重命名
        rename ('classes.jar', 'cz_zbar_sdk.jar')
    }
    
    makeJar.dependsOn(build)

       2)在module的gradle里书写打包脚本,增加具体的时间

//修改jar名字+将指定jar生成的地方        
        task makeJar(type: Copy) {
            //删除存在的
            delete 'build/libs'
            //设置拷贝的文件
            from('build/intermediates/intermediate-jars/release/')
            //打进jar包后的文件目录
            into('libs/')
            //将classes.jar放入build/libs/目录下
            //include ,exclude参数来设置过滤
            //(我们只关心classes.jar这个文件)
            include('classes.jar')
            //重命名
            rename ('classes.jar', "CZBaseToolLibSdk_${releaseTime()}.jar")    //注意Jar包名字是双引号
        }
        
        makeJar.dependsOn(build)
        
        def releaseTime() {
            return new Date().format("yyyyMMddHHmm", TimeZone.getTimeZone("GMT+08:00"))
        }

多层module依赖,打jar包的问题。

         https://www.cnblogs.com/mq0036/p/8566427.html#a22


       1)moduleA依赖moduleB,moduleC依赖moduleA,如果将moduleA,moduleB打成jar包,给moduleC引用,回报重复类的错误:multidex class。。。

api (project(':moduleA')) {
    //解决重复依赖问题
    exclude module: 'moduleB'
}

            这样即可解决问题。

        接着,如果将moduleC再生成一个jar包,moduleC.jar。引用moduleC.jar,类调用正常,但是一运行就会报错:

Caused by: java.lang.ClassNotFoundException: Didn't find class "com.cz.basetool.ui_work.thread.ThreadManager" on path: DexPathList[[zip file "/data/app/com.example.jl.jiangxihfscj-2/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
                                                                                 at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
                                                                                 at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
                                                                                 at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
                                                                                 at com.juli.basescjmodule.libs.hardware_libary.a.initSystemConfig(SystemUtil.java:45)?
                                                                                 at com.juli.basescjmodule.libs.hardware_libary.a.a(SystemUtil.java:39)?
                                                                                 at com.juli.basescjmodule.libs.base_tool.hardware_service.obu.action.system.SystemActionController.init(SystemActionController.java:38)?
                                                                                 at com.juli.basescjmodule.libs.base_tool.hardware_service.obu.action.system.SystemActionController.init(SystemActionController.java:32)?
                                                                                 at com.juli.basescjmodule.libs.basesdk.JLBaseActionContoller.initSDK(JLBaseActionContoller.java:83)?
                                                                                 at com.example.jiangxisdklibrary.JLScjContoller.initSDK(JLScjContoller.java:30)?
                                                                                 at com.example.jl.jiangxihfscj.ProApplication.onCreate(ProApplication.java:24)?
                                                                                 at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1012)?
                                                                                 at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4553)?
                                                                                 at android.app.ActivityThread.access$1500(ActivityThread.java:151)?
                                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1364)?
                                                                                 at android.os.Handler.dispatchMessage(Handler.java:102)?
                                                                                 at android.os.Looper.loop(Looper.java:135)?
                                                                                 at android.app.ActivityThread.main(ActivityThread.java:5254)?
                                                                                 at java.lang.reflect.Method.invoke(Native Method)?
                                                                                 at java.lang.reflect.Method.invoke(Method.java:372)?
                                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)?
                                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)?
                                                                             	Suppressed: java.lang.ClassNotFoundException: com.cz.basetool.ui_work.thread.ThreadManager
                                                                                 at java.lang.Class.classForName(Native Method)
                                                                                 at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
                                                                                 at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
                                                                                 at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
                                                                                 		... 19 more
                                                                              Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available

         而且在moduleC.jar也找不到这个类,准确的说是找不到moduleA和moduleB的类。

        》》如果moduleC中有UI,将moduleC打成jar包,还会报UI相关找不到的错误。

        


                        

        解决办法:https://www.jianshu.com/p/8f9cf6271c20

                        https://www.cnblogs.com/Jason-Jan/p/9192273.html

        我的gradle的环境:compileSdkVersion 26     buildToolsVersion "25.0.3"   

                    


                1.fat-aar.gradle:要打哪个module的jar包,就放到哪个module下面。

                2.github上的这个库,有个前提就是gradle插件必须是2.3.3,所以我把

                    gradle要加上buildToolsVersion

                    gradle要将implecation换成compile   

                    

                    工程的gradle插件变为2.3.3

classpath 'com.android.tools.build:gradle:2.3.3'

                    properties的版本可以不用改,如果改成2.3.3还会报错:

                    https://www.jianshu.com/p/6a0bc5792860

                默认aar的生成位置:

                       关于重构的一些思想

                默认也会生成jar文件,但是引用后发现里面只有R文件:

                关于重构的一些思想

                那么,既然aar都能打出来,是不是改改fat-aar就能搞个fat-jar打个jar呢???????????????????????

                还有一种方法:https://blog.csdn.net/anymyna/article/details/82054020  (将aar转成jar),如果aar中有需要用到的资源文件或者so库,则不能只用jar包。否则就会报not found错误。

                


                3.怎么混淆:https://www.jianshu.com/p/aa3e78a7a095      (关于混淆的配置,只需要在最上层的merge模块中配置即可

                4.release的aar不出来咋办:

                     执行cleanBuildCache然后执行assembleRelease   

                    关于重构的一些思想

            5.假如打jar包的module要做修改,把jar包源码的module重新依赖上,可能会出现错误:

                 com.android.dex.DexException: Multiple dex files define Lcom/某个类

               这就有可能是你打jar的多个一级子module依赖了同一个二级子module导致的重复的引用的问题,因为降低gradle插件版本之前依赖module使用的implementation关键字,后来使用的

                compile关键字。

              关于这两个依赖module的关键字的区别:https://blog.csdn.net/hwra2008/article/details/78989640

              解决办法:所以可以在root module里创建一个公共的变量,各个module通过变量使用2个版本的gradle语法。https://www.cnblogs.com/DreamRecorder/p/9198155.html

              但是root module本身好像无法使用自己定义的这个变量。

            

                最后,一定要重新清理所有的module,否则可能有缓存。

            6.aar打成功了,但是引用的时候出现了个问题:

                                            

                            Error:Execution failed for task ':app:processDebugManifest'.

                            > com.android.manifmerger.ManifestMerger2$MergeFailureException: org.xml.sax.SAXParseException; lineNumber: 18; columnNumber: 47; 与元素类型 "uses-

                            permission" 相关联的属性 "tools:ignore" 的前缀 "tools" 未绑定。

               发现原来一个module里有 

<!--    <uses-permission
        android:name="android.permission.BLUETOOTH_PRIVILEGED"
        tools:ignore="ProtectedPermissions" />-->

               把这个属性重新注释掉,清除各个module的build文件夹,重新打包即可。

            7.module里引用了一个aar包aarA,再将这个module打成aar包aarB,去app里引用aarB,会找不到aarA包中的类。  

               解决办法:将aarA解压出来,将其classes.jar拷贝到aarB的libs目录下。(离架构又近了一步)

            8.打aar的时候,非fat-aar所在的module的class并没有打包进去,使用aar的时候,会报"not found class xxx"错误。

               后来才发现在全局替换的时候 ,有个release的方法,碰巧fat-aar里也有这个字符串。所以,还是得看懂fat-aar的意思才行。

            9.module嵌套依赖时,最好不要出现,mb依赖ma,mc依赖ma,然后md同时依赖mb和mc的情况,这样md打包aar可能就会报multiple dex files错误。

              

               ****这个时候fat-aar得关键字embedded,也不支持exlude的语法。只能从内部module出发,修改compile加上exclude,防止重复引用。

                     但是问题还是没有解决,发现竟然是md重复引用了一个module的原因,而且用的是compile语法。

               所以遇到这种问题,第一,定位到底是哪个module重复引用了 

 

编写SDK需要注意的地方

     1)考虑对宿主的兼容性

          不可 引用过高版本的依赖,导致宿主依赖的低版本有转换的相关的异常。

  •           比如在sdk中引用androidx库,但是宿主用的是传统的com.android.*的库。SDK中用到了androidx的ContextCompat,但是宿主传入的是低版本的Context。

         关闭AndroidX:https://blog.csdn.net/u013040819/article/details/94399117

          

           


将module与主程序解耦

    在module写了一些功能,但是功能需要主程序去辅助完成。这样就矛盾了,module又不能去依赖主程序,怎么调用主程序的东西呢?

    解决办法 1

        在module里定义一个抽象类,module能实现的功能全部实现,实现不了定义成抽象方法,将主程序完成某功能需要的参数回调出去。再在主程序里注入实现:

        主程序要干的事情:

       

IDeviceAction {
    (openOutputDataSender outputDataSender)(openOutputDataSender outputDataSender)(openOutputDataSender outputDataSender)(highVolumeOutputDataSender outputDataSender)(volumeValueOutputDataSender outputDataSender)(OutputDataSender outputDataSender)}

        

         moudle里半实现的抽象类:

BlueToothActionReceiver BroadcastReceiver IDeviceAction{
    String = String = String = (Context contextIntent intent) {
        (intent != && intent.getAction().equals()){
            option = intent.getIntExtra(-)String state = intent.getStringExtra()(option){
                DeviceActionOption.:
                    setGPS(state.equals() ? : OutputDataSender.())DeviceActionOption.:
                    setRecord(state.equals() ? : OutputDataSender.())DeviceActionOption.:
                    setSta(state.equals() ? : OutputDataSender.())DeviceActionOption.:
                    (state.substring()){
                        :      setVolume(OutputDataSender.()):      setVolume(OutputDataSender.()):      String valume = state.substring()value = CodeTool.(valume)setVolume(valueOutputDataSender.())}

                    DeviceActionOption.:
                    getDeviceStatus(OutputDataSender.())}
        }
    }
}


            在module里发广播,这样主程序就能完成未完成的功能。





重构的实例场景

  1)一个网络请求接口(传一个paperid得到试卷的题目)要在程序的多处调用,之前离职的程序员写

   的代码也没有封装:

   

/**
 * 从网络获取ListView的数据
 */
private void getListData(String p_id) {
    if (!NetWorkStatusUtil.isNetworkAvailable(context)) {
        ToastUtil.showShort(context, "请先链接网络");
        return;
    }
    LoadingDialog.showProgress(this, "题目正在加载中,请稍等...", true);
    String url = HttpConfig.QUESTION;
    RequestParams params = new RequestParams();
    // params.addBodyParameter("token", MD5Encoder.getMD5());
    KLog.e("试卷页面p_id:" + p_id);
    params.addQueryStringParameter("token", MD5Encoder.getMD5());
    params.addQueryStringParameter("p_id", p_id);
    //p_id=2是单选;
    //http://app.haopeixun.org/index.php?g=apps&m=paper&a=getExams&token=75c824f486b5fa5b60330697bdb03842&p_id=8
    //
    HttpUtils httpUtils = new HttpUtils();
    httpUtils.send(HttpMethod.GET, url, params, new RequestCallBack<String>() {

        @Override
        public void onSuccess(ResponseInfo<String> responseInfo) {
            jsonString = responseInfo.result;
            Log.i(TAG, "考题答案: ");
            KLog.json(jsonString);
            if (!TextUtils.isEmpty(jsonString)) {
                JSONObject jsonObject;
                try {
                    jsonObject = new JSONObject(jsonString);
                    int resultCode = Integer.parseInt(jsonObject.getString("result"));
                    KLog.e("结果码:" + resultCode);
                    switch (resultCode) {
                        case 0:
                            if (jsonString.isEmpty()) {
                                showEmptyView();
                                return;
                            }
                            parserData(jsonString);
                            break;
                        default:
                            showEmptyView();
                    }
                } catch (JSONException e) {
                    showEmptyView();
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally {
                    LoadingDialog.dismissProgressDialog();
                }
            }
        }

        @Override
        public void onFailure(HttpException arg0, String arg1) {
            KLog.e(arg1);
            LoadingDialog.dismissProgressDialog();
            ToastUtil.showLong(context, "请求考题失败");
        }
    });
}


private void parserData(String jsonString) {
    // ***********给user赋值******************
    qustionItems.clear();

    try {
        JSONObject jsonObject = new JSONObject(jsonString);
        JSONArray dataArray = jsonObject.getJSONArray("data");
        totalScore = dataArray.length();
        Log.i(TAG, "总共题目数量:" + totalScore);
        for (int i = 0; i < dataArray.length(); i++) {
            QuestionBean questionBean = new QuestionBean();
            JSONObject jsonObject2 = dataArray.getJSONObject(i);
            questionBean.addtime = jsonObject2.getString("addtime");
            questionBean.e_analyze = jsonObject2.getString("e_analyze");
            questionBean.e_answer_count = jsonObject2.getString("e_answer_count");
            // *********
            JSONObject jsonObject3 = jsonObject2.getJSONObject("e_answer");// 题选项对象
            questionBean.eanswer = questionBean.new Eanswer();
            questionBean.eanswer.setA(jsonObject3.getString("A"));
            questionBean.eanswer.setB(jsonObject3.getString("B"));
            String answerC = jsonObject3.optString("C");
            if (answerC != null && !TextUtils.isEmpty(answerC)) {
                questionBean.eanswer.setC(answerC);
            }

            String answerD = jsonObject3.optString("D");
            if (answerD != null && !TextUtils.isEmpty(answerD)) {
                questionBean.eanswer.setD(answerD);
            }

            switch (Integer.parseInt(questionBean.e_answer_count)) {
                case 5:
                    questionBean.eanswer.setE(jsonObject3.getString("E"));
                    break;
                case 6:
                    questionBean.eanswer.setE(jsonObject3.getString("E"));
                    questionBean.eanswer.setF(jsonObject3.getString("F"));
                    break;
                case 7:
                    questionBean.eanswer.setE(jsonObject3.getString("E"));
                    questionBean.eanswer.setF(jsonObject3.getString("F"));
                    questionBean.eanswer.setG(jsonObject3.getString("G"));
                    break;
            }
            // **********

            questionBean.e_classid = jsonObject2.getString("e_classid");
            questionBean.e_exam_type = jsonObject2.getString("e_exam_type");
            questionBean.e_fenzhi = jsonObject2.getString("e_fenzhi");
            questionBean.e_good = jsonObject2.getString("e_good");
            questionBean.e_id = jsonObject2.getString("e_id");
            questionBean.e_probability = jsonObject2.getString("e_probability");
            questionBean.e_result = jsonObject2.getString("e_result");
            questionBean.e_status = jsonObject2.getString("e_status");
            questionBean.e_title = jsonObject2.getString("e_title");
            questionBean.e_typeid = jsonObject2.getString("e_typeid");
            questionBean.examid = jsonObject2.getString("examid");
            questionBean.id = jsonObject2.getString("id");
            questionBean.paperid = jsonObject2.getString("paperid");
            qustionItems.add(questionBean);
            StringBuilder sb = new StringBuilder();
            //sb.append("");
            uAnswerList.add(sb);
        }
    } catch (JSONException e) {
        e.printStackTrace();
    } finally {
        LoadingDialog.dismissProgressDialog();
    }
    showDataView();
    KLog.e("集合大小" + qustionItems.size());
    if (qustionItems.size() > 0) {
        tv_total_questions.setText("1 / 共" + qustionItems.size() + "道题");
    }
    questionPagerAdapter.set(qustionItems, uAnswerList);
    uAnswerStateAdapter.set(getAnswerCard());
}

   同样的接口,我觉得没有必要再往新的activity复制一遍,于是我将这个接口单独封装成一个类

  PaperListRequester中

    关于重构的一些思想    在构造PaperListRequester这个类的时候,我将Context和接口需要的参数pid传递进去。定义了

一个监听器ParseListener,监听内部数据解析的结果。通过一个方法getData,并传递一个监听器返回结果。

   这是调用PaperListRequester这个类请求接口的代码:

 关于重构的一些思想   将请求、解析接口的逻辑封装在PaperListRequester这个类中,一下代码就看得顺眼了。这也是Google及一些开源库常用的工具类封装手法。

   2)工程中有个ActivityA,作用是取一个数据,但是这个取数据的过程,与activity里的handler相关联。代码量也比较多。这时候有个ActivityB,需要和

     ActivityA重复一样的工作,但是ActivityB本身类里逻辑比较复杂,如果直接把ActivityA里的代码粘贴过来,会严重影响这个类的结构。所以这里    

    就需要新建一个类C.java,用来封装ActivityA的逻辑,通过接口返回数据。在ActivityB里调用C,只管请求和取数据,中间的过程交由C.java去实现。

    实现解耦。

   

6.避免过多使用if-else

    如果一个项目中,一个变量因素会影响多个地方的行为或者动作,可以将这此动作定义成接口。然后根据变量定义不同的实例实现接口。

    实战1:比如我的项目里,不同的设备串口号、其它的一些信息不一样

   1-1:抽取接口

**
 * Created by XinYi on 2018/8/7.
 * 获取设备的一些信息,因设备硬件版本不同而不同,需要兼容。
 * 1.移远(28)
 * 2.旗文(06B)
 */

public interface IDeviceContent {
    /**
     * 升麦串口号
     * @return
     */
    String getSmSerialFileName();

    /**
     * OBD串口号
     * @return
     */
    String getObdSerialFileName();

    /**
     * SD卡挂载,扫描的根目录是否正确。
     * @see  SMCardManager.CardChecker#initFilter()
     * @param fileName
     * @return
     */
    boolean isSmScardPath(String fileName);


}

 1-2:根据不同的平台,实现接口,创建不同的实例:

    平台1:

/**
 * Created by XinYi on 2018/8/7.
 * 旗文(06B) 平台
 */

public class QWDeviceContent implements IDeviceContent {
    @Override
    public String getSmSerialFileName() {
        return "/dev/ttyMT2";
    }

    @Override
    public String getObdSerialFileName() {
        return "/dev/ttyM×××";
    }

    @Override
    public boolean isSmScardPath(String fileName) {
        return fileName.conta×××("usbotg");
    }
}

  平台2:

/**
 * Created by XinYi on 2018/8/7.
 * 移远(28)平台
 */

public class YYDeviceContent implements IDeviceContent {
    @Override
    public String getSmSerialFileName() {
        return "/dev/ttyHSL0";
    }

    @Override
    public String getObdSerialFileName() {
        return "/dev/ttyHSL1";
    }

    @Override
    public boolean isSmScardPath(String fileName) {
        return (fileName.conta×××(Constants.SM_SDCARD_QW_PATH) && fileName.length() == 9);
    }
}

  1-3:指定一个实际使用的实例

private void fillDataFromBuilder(Builder builder) {
    this.deviceType = builder.deviceType;
    this.areaId = builder.areaId;
    if(deviceType == DeviceType.TYPE_28){
        deviceContent = new YYDeviceContent();
    }else if(deviceType == DeviceType.TYPE_06B){
        deviceContent = new QWDeviceContent();
    }
}

 这样就避免了每一个方法返回的值都需要根据平台用if-else判断一下,一旦增加一个平台,只需要创建一个平台的实例即可。



避免多个异步接口多层嵌套回调

     我们调用的代码是异步的,或者开一个线程,都会导致我们不由自主的使用异步回调。以我多年的开发经验,异步可以转化成同步,定义方法最好用同步,这样遇到复杂的嵌套调用逻辑,就会避免多层嵌套的问题,提高代码的可阅读性。


        实际场景:下面这此设置项,每一项都有一个单独的接口,都是异步回调。这样如果有10多层异步嵌套,将会造成代码阅读性非常差。

        

        关于重构的一些思想

        于是我想到了“封装”

        #1.定义单项设置器的基类

/**
 * Created by XinYi on 2018/12/20.
 * 单个参数设置基类
 */

public abstract class BaseSingleRecordParamHandler {
    protected String TAG = this.getClass().getSimpleName();

    protected RecordParamsHandleController recordParamsSetController;
    protected   String param;

    /**
     * 设置参数
     */
    abstract void handleSetParam();


    /**
     * 获取参数
     */
    abstract void handleGetParam();

    protected YiYunController.YiYunActionCallBack getYiYunActionCallBack(){
        return new YiYunController.YiYunActionCallBack() {
            @Override
            public void onSuccess(Object obj) {
                BaseSingleRecordParamHandler.this.onSetParamSuccess();
            }

            @Override
            public void onFailure(Exception e) {
                BaseSingleRecordParamHandler.this.onSetParamFailure(e);
            }
        };
    }


    protected BaseSingleRecordParamHandler getNextSingleRecordParamHandler(){
        return recordParamsSetController.getNextSingleRecordParamHandler();
    }

    public void attachRecordParamsSetController(RecordParamsHandleController recordParamsSetController) {
        this.recordParamsSetController = recordParamsSetController;
    }

    public BaseSingleRecordParamHandler setParam(String param) {
        this.param = param;
        return this;
    }

    protected void onSetParamSuccess(){
        BaseSingleRecordParamHandler nextSingleRecordParamSetter = recordParamsSetController.getNextSingleRecordParamHandler();
        if(nextSingleRecordParamSetter != null){
            nextSingleRecordParamSetter.handleSetParam();
        }else{
            recordParamsSetController.getParamsSetResultCallBack().onSuccess();
        }
    }

    protected void onSetParamFailure(Exception e){
        recordParamsSetController.getParamsSetResultCallBack().onFailure(e);
    }


    protected void onGetParamSuccess(){
        BaseSingleRecordParamHandler nextSingleRecordParamSetter = recordParamsSetController.getNextSingleRecordParamHandler();
        if(nextSingleRecordParamSetter != null){
            nextSingleRecordParamSetter.handleSetParam();
        }else{
            recordParamsSetController.getParamsGetResultCallBack().onSuccess(recordParamsSetController.getCollectedParams());
        }
    }

    protected void onGetParamFailure(Exception e){
        recordParamsSetController.getParamsGetResultCallBack().onFailure(e);
    }


}

     

    #2.定义设置控制器

          控制器的好处:

          1)通过调整设置器的add顺序,可以轻松调整单项设置的先后顺序。(想想如果用的是多层嵌套,那将是一场灾难。)

          2)避免了多层嵌套回调

          3)隔离了用户与设置器

        

/**
 * Created by XinYi on 2018/12/20.
 * 记录仪参数设置控制器
 */
public class RecordParamsHandleController {
    private static final String TAG = "RecordParamsHandleContr";
    private int nextSetterIndex = 0;                       //标记当前处理者的索引
    private List<BaseSingleRecordParamHandler> singleRecordParamSetterList = new ArrayList<>();
    private static final RecordParamsHandleController ourInstance = new RecordParamsHandleController();
    private ParamsSetResultCallBack paramsSetResultCallBack;
    private ParamsGetResultCallBack paramsGetResultCallBack;
    private String collectedParams = "";


    public static RecordParamsHandleController getInstance() {
        return ourInstance;
    }

    private RecordParamsHandleController() {

    }


    /**
     *
     * @param param   0:参数获取或者设置标志      1:正式参数
     */
    public void init(String param[]){
        nextSetterIndex = 0;
        collectedParams = "";
        //注意下面的add是有顺序的
        singleRecordParamSetterList.clear();
        singleRecordParamSetterList.add(RecordResolutionParamHandler.getInstance(this));
        singleRecordParamSetterList.add(RecordMicParamHandler.getInstance(this));
        singleRecordParamSetterList.add(RecordLoopParamHandler.getInstance(this));
        singleRecordParamSetterList.add(RecordGsensorParamHandler.getInstance(this));
        singleRecordParamSetterList.add(AMapParamHandler.getInstance(this));
        singleRecordParamSetterList.add(ScreenIntensityParamHandler.getInstance(this));
        singleRecordParamSetterList.add(RecordDurationParamHandler.getInstance(this));
        singleRecordParamSetterList.add(ScreenSleepParamHandler.getInstance(this));
        singleRecordParamSetterList.add(ResetTFCardParamHandler.getInstance(this));
        singleRecordParamSetterList.add(ResetSystemParamHandler.getInstance(this));
        singleRecordParamSetterList.add(RecordOsdParamHandler.getInstance(this));

        LogController.d(TAG, "init: len = " + param.length);
        //设置参数
        if(param.length > 1){
            for (int i = 2; i < param.length; i++) {
                LogController.d(TAG, "init: i = " + i);
                singleRecordParamSetterList.get(i-2).setParam(param[i]);
            }
        }
    }

    /**
     * 开始设置参数
     * @param paramsSetResultCallBack
     */
    public void startSetParams(ParamsSetResultCallBack paramsSetResultCallBack){
        this.paramsSetResultCallBack = paramsSetResultCallBack;
        singleRecordParamSetterList.get(0).handleSetParam();
    }


    /**
     * 开始获取参数
     * @param paramsGetResultCallBack
     */
    public void startGetParams(ParamsGetResultCallBack paramsGetResultCallBack){
        this.paramsGetResultCallBack = paramsGetResultCallBack;
        singleRecordParamSetterList.get(0).handleGetParam();
    }

    protected BaseSingleRecordParamHandler getNextSingleRecordParamHandler(){
        nextSetterIndex++;
        if(nextSetterIndex + 1 >singleRecordParamSetterList.size()){
            return null;
        }
        return singleRecordParamSetterList.get(nextSetterIndex);
    }

    public interface  ParamsSetResultCallBack{
        void onSuccess();
        void onFailure(Exception e);
    }

    public interface  ParamsGetResultCallBack{
        void onSuccess(String param);
        void onFailure(Exception e);
    }


    public ParamsSetResultCallBack getParamsSetResultCallBack() {
        return paramsSetResultCallBack;
    }

    public ParamsGetResultCallBack getParamsGetResultCallBack() {
        return paramsGetResultCallBack;
    }

    public void collectParams(String param){
        collectedParams += param;
    }

    public String getCollectedParams() {
        return collectedParams;
    }
}



向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI