关于重构的一些思想
DRY原则:Don't Repeat Yourself (摘自wikipedia)
成都创新互联公司-专业网站定制、快速模板网站建设、高性价比西峡网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式西峡网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖西峡地区。费用合理售后完善,十多年实体公司更值得信赖。
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= 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{ @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(ListfieldNames); //根据字段名,拼出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(ListfieldNames) { Map 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 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
里往往又会用到List
于是研究了一下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 CZArrayAdapterextends ArrayAdapter{ public CZArrayAdapter(Context context, List 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{ /** * 获取当前模块的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里有
把这个属性重新注释掉,清除各个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() { @Override public void onSuccess(ResponseInfo 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(jsonObjec
网页题目:关于重构的一些思想
文章网址:http://hbruida.cn/article/jhpgdi.html