开发艺术探索 设计模式 个人读书笔记
Activity
- 打开一个新activity时,旧的activity先回调onPause(),新的activity才开始启动。所以不要再onPause里做太多耗时操作。
- Activity调用onSaveInstanceState()时会委托Window去保存数据,Window又委托它上面的顶级容器一般是DecorView,然后DecorView又遍历子View去保存数据。一种典型的委托思想。
- 在intent中设置标记位intent.addFlags来指定启动模式,优先级大于在AndroidMenifest文件中指定。
- 要想让activity支持隐式启动,category.DEFAULT一定要加上哦。
- URL中的schema有默认值:content、file。7.1以后直接使用file的URL会CRASH,请使用FileProvider。
- 隐式启动activity请使用intent.resolveActivity(getPackageManager())判断是否有符合条件的activity。
- android:exported="true",true表示其他应用可以打开这个activity,同样适用于service、contentprovider。
- 如果不想让activity出现在最近打开列表,设置android:excludeFromRecents="true"。
- Scheme跳转协议:可以在浏览器中根据url跳转到activity。详细参考:http://blog.csdn.net/qq_23547831/article/details/51685310
- Activity的启动过程:有点复杂,死记硬背没得用,先占坑。
IPC
跟着书走了一遍,还是有点蒙,想要真正掌握这个点,我想还是需要实际项目的经验。初步认识了Binder。Binder指的是一种跨进程通信方式,比如说AIDL使用Binder进行通信,指的就是这种IPC机制。
对于开发人员来讲,Binder是Android中的一个可以帮助你进行跨进程通信的类,是客户端和服务端进行通信的媒介。Binder驱动会对Binder对象进行特殊处理,帮助你完成数据的转换。
(服务端实现的Binder对象返回一个代理对象到客户端,客户端对这个代理Binder对象进行操作,会通过Binder驱动转发到服务端的本地Binder对象上去。Binder驱动会对这个对象进行特殊处理,帮助你完成数据的转换)
在安卓framework层中,ServiceManager通过Binder连接了各种Manager,比如WindowManager、ActivityManager等等。。。android中常用的实现IPC的方式,Bundle,文件共享,AIDL,Messenger,CP、Socket。
View
View的属性top、left、right、bottom,x、y,translationX、translationY。
x,y是View的左上角坐标,换算关系如下:
x = left + translationX ;
y = top + translationY ;
view在平移的过程中,改变的是x、y、translationX、translationY这四个属性,t、l、r、b不会发生改变,这四个属性在onLayout()之后就确定了。
- TouchSlop,一个常量,在不同的设备上可能有不同的值。它代表最小的滑动距离,大于这个值,可以认为用户是在滑动,使用ViewConfiguration.get(getContext()).getScaledTouchSlop()得到。
-
VelocityTracker,addMovement(MotionEvent ev)添加事件,然后通过设定时间间隔计算出X、Y方向的速度。使用完成之后务必释放资源:clear(),recycle();
-
GestureDetector,手势检测,如果你的自定义View需要检测用户的单击、长按、双击等行为,使用它会比较方便。如果只需要检测滑动,就直接在onTouchEvent中处理吧。
-
scrollTo/By,对View的内容进行移动。
-
事件分发机制:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false; if(onInterceptTouchEvent(ev)){ //如果拦截事件 consume = onTouchEvent(ev); } else { //把事件交给子View consume = child.dispatchTouchEvent(ev); } return consume; }
-
ViewRoot连接WindowManager和DecorView的纽带。View的绘制从ViewRoot的performTraverSals开始,完成DecorView的measure、layout、draw。
-
DecorView:顶级View。
-
MeasureSpec,一个32位的int值,一个值其实包含了两个值:高2位是测量模式,低30位是测量尺寸。提供了打包解包方法。
-
根据子View的LayoutParams和父View的MeasureSpec还有子View的margin和padding值来确定子View的MeasureSpec,比如在ViewGroup的measureChilWithMargins()方法的实现。
-
Measure过程:ViewGroup.onMeasure()里测量子View:measureChild()里getChildMeasureSpec()然后调用child.measure();然后View的onmeasure()里setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默认返回measureSpec的测量数值。这个时候测量的宽高就可以确定了。可以通过getMeasureHeight/Width获取到值了。
-
Layout过程:最外层的ViewGroup的先layout(l,t,r,b),中使用setFrame()设置本View的四个顶点位置。在onLayout(抽象方法)中确定子View的位置,如LinearLayout会遍历子View,循环调用setChildFrame()-->子View.layout()。在onLayout()之后会确定View的left,top,right,bottom属性,这时可以通过getHeight/Width()获取真实宽高了。
-
最外层ViewGroup的draw():其中会先后调用background.draw()(绘制背景)、onDraw()(绘制自己)、dispatchDraw()(绘制子View)、onDrawScrollBars()(绘制装饰)。
-
让自定义View支持wrap_content,需要在onMeasure()中对wrap_content进行特殊处理。具体情况具体处理。
-
自定义View:定义自定义属性,获取自定义属性,onMeasure,onLayout(一般自定义ViewGroup根据需要去实现),onDraw,如有需要,别忘了处理点击事件。
-
获取measure()后的宽高:
1.Activity#onWindowFocusChange()中调用获取
2.view.post(Runnable)将获取的代码投递到消息队列的尾部。
3.ViewTreeObservable。 -
熟悉View的弹性滑动、布局参数、滑动冲突、绘制步骤与原理、使用动画、SVG矢量图的使用等,在平时开发中注意积累经验,学会融会贯通,自定义View就会得心应手了。
动画
- View动画:平移动画<translate>,缩放动画<scale>,旋转动画<rotate>,透明度动画<alpha>。
- 帧动画:顺序播放一组预先定义好的图片。
- 属性动画:可以对任意对象的属性做变化,在一个时间间隔内对对象的属性从一个值到另一个值。View动画不真正改变View的属性,属性动画是真正的改变属性,要求属性提供get/set方法。
- 如果想使用属性动画改变一个属性,然而却没有get/set方法,则可以用一个类来包装原始对象,间接的为其提供get/set方法。
- 插值器和估值器,用来实现非匀速动画。
Window和WindowManager
- Window,一个窗口的概念,起到了承载视图的作用,是View的直接管理者。每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。
- 只能使用WindowManager来管理Window,WindowManager有三个接口方法:addView,updateViewLayout以及removeView。
- Window的添加过程:WindowManager的实现类WindowManagerImpl.addView---->然后委托WindowManagerGlobal类进一步操作:1:检查window。2,创建ViewRootImpl并将View添加。3,通过ViewRootImpl更新界面,然后使用IPC和WindowManagerService进行通信,交给WMS完成Window的添加。
- Activity中Window的创建过程:在Activity的attach方法里,系统会由Policy类创建Window的实现类PhoneWindow。并设置回调CallBack接口(onAttachedToWindow()、dispatchTouchEvent()等等),然后创建DecorView,然后通过Activity#setContentView()调用PhoneWindow的setContentView把布局添加到DecorView里。最后使用WindowManager.addView通知远端WMS,然后才真正完成了添加和显示过程。
- Window的实体是存在于远端的WindowMangerService中,所以增删改Window在本端是修改是通过ViewRootImpl重绘View,通过WindowSession(每个应用一个)在远端修改Window。
- 有View的地方就会有Window,比如在Activity,Dialog,Toast,PopUpWindow,Menu等视图都对应着一个Window。所以一个应用有几个Window?
Android中的消息机制
随便记,不涉及原理。
- Handler:在主线程创建一个Handler,在其他线程调用sendMessage()发出消息,此时主线程的MessageQueue中会插入一条message,然后Looper会把消息取出,然后在主线程Handler中的handlerMessage方法中处理消息。
- MessageQueue:消息队列,使用一个单链表维护消息。enqueueMessage方法是往消息队列中插入一条消息,next方法是从消息队列读取一条消息并删除消息。next方法会无限循环。
- Looper:Looper创建的时候会创建一个MessageQueue,调用loop()方法的时候消息循环开始,loop()也是一个死循环,会不断调用messageQueue的next(),当有消息就处理,否则阻塞在messageQueue的next()中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也跟着退出。
- ThreadLocal:使用ThreadLocal,可以实现同一个变量在不同的线程存储不同的值。对应有get/set方法。原理:还没弄懂。
- 为什么只能在主线程操作UI:因为UI操作较为复杂,多线程同时操作可能会导致不可预期的情况,而如果使用加锁管理,则逻辑变得更复杂而且降低效率。所以使用了单线程模型来管理UI操作。APP启动时,主线程被创建。在其他线程操作UI时,在ViewRootImpl中会检查线程,如果不是主线程则会抛出异常。
Android中的多线程
除了在java的多线程知识,在android中,还需要了解在android在Thread的基础上封装的几个类,AsyncTask、HandlerThead、IntentService。
-
AsyncTask:一个轻量级的异步任务抽象类,使用时需要继承它然后实现它的4个核心方法。AsyncTask里使用到了线程池。需要特别注意的是:在不同的SDK版本中,AsycnTask的实现会有差异。android 3.0之后默认是串行执行的,也可以通过executeOnExecutor方法来并行执行任务。
-
HandlerThread:继承自Thread,一个可以使用Handler的Thread。在内部创建了一个Looper,也就是在内部创建了消息队列,外部可以发送消息到HandlerThread,然后它会执行特定任务。
-
IntentService:继承自Service,抽象类。因为是服务,所以优先级较高,而且在异步任务执行完成后,会自动停止。IntentService封装了HandlerThread和Handler。我们重写onHandleIntent(Intent intent)方法,在里面根据intent中所包含的信息进行判断,然后进行执行特定任务。so easy!
-
在多线程编程的时,如果有多个线程同时对同一个对象进行操作的情况,可以使用一些能够保证线程安全的类比如Vector、StringBuffer、ConcurrentHashMap等。
-
Android中的线程池:首先,使用线程池能给我们带来什么好处呢?1.使用线程池来管理线程,可以有效避免线程频繁创建和销毁所带来的性能损耗和资源浪费。线程池会对线程进行重用。2,能有效的控制最大并发数,提高系统资源利用率。3,提供了定时任务、定期执行、指定间隔循环执行等管理功能。
-
ThreadPoolExecutor,它的构造方法提供了一系列参数来配置线程。
int corePoolSize ;//线程池的核心线程数 int maximumPoolSize;//线程池所能容纳的最大线程数。 long keepAliveTime; //非核心线程闲置时间的超时时长。 TimeUnit unit;//指定keepAliveTime参数的时间单位。 BlockingQueue<Runnable> workQueue;//线程池中的人物队列,通过execute方法提交的Runnable对象会存储在这个参数中。 ThreadFactory threadFactory;//线程工厂,为线程池提供创建新线程的功能。它是一个接口,只有一个方法:Thread newThread(Runnable r)。
以上就是主要的参数,还有一些不常用参数未列出。
Android中常见的四类具有不同功能特性的线程池,他们都直接或间接地通过配置ThreadPoolExecutor来实现自己的功能特性。
- FixedThreadPool:通过Executors的newFixedThreadPool(int nThreads)来创建。它是一种线程数量固定的线程池。
- CachedThreadPool:一种县城数量不定的线程池,它只有非核心线程,并且最大线程数为Integer.MAX_VALUE。线程超时时间为60秒,超时则会被回收。这个线程池比较适合执行大量的耗时较少的任务。当线程池处于闲置状态时,所有线程都会被超时回收,和上面的FixedThreadPool不一样。
- ScheduledThreadPool:这个线程池的核心线程数固定,最大线程数没有限制。并且当非核心线程闲置时会被立即回收。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
- SingleThreadExecutor:这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中去执行。所以在线程同步问题上不需要去过多地去考虑。
对了,这两年出现了一个特别牛B的库:RxJava。异步、简洁的链式调用。这个Cool得不行的响应式编程框架一用根本停不下来,没用过的可以去试试!
Bitmap和Cache
- Bitmap的高效加载:使用BitmapFactory.Options里的inSampleSize参数,即采样率。当inSampleSzie为1时,采样后图片大小为原始大小,当inSampleSize为2时,那么采样后的宽高为原始宽高的1/2,即占用内存大小变为1/4。计算出正确的采样率,然后设置给BitmapFactory的四个加载Bitmap的方法。就可以加快图片加载速度以及减少内存的消耗,就可以有效的避免OOM了。
- Android中的缓存策略:一般的图片加载框架都有着3级缓存机制,即内存-磁盘-网络。先看内存缓存,再查找磁盘缓存,都没有缓存的话再从网络中加载。目前常用的缓存算法为LRU(Least Recently Used),即近期最少使用算法。会优先淘汰掉近期最少使用的缓存对象。
- LruCache:一个内存缓存工具类,内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象。提供了get/put方法完成缓存的获取和添加操作。使用的时候只需要提供缓存的总容量大小然后重写sizeOf方法来计算单个缓存对象的大小方法就可以了。
- DiskLruCache:上面是内存缓存,这个一看名字就懂了,DiskLruCache就是用于实现存储设备,一般是磁盘缓存的了。它通过讲缓存对象写入文件系统来实现缓存。DiskLruCache的使用方法相对比较复杂。请查阅相关的具体文档。
- 如何实现一个ImageLoader:大概的说一下
- 使用线程与线程池实现图片的异步加载
- 图片的同步加载
- 图片的压缩,避免OOM。使用BitmapFactory加载正确尺寸。
- 缓存功能:使用LruCache和DiskLruCache实现缓存功能。
- 网络拉取功能:使用网络从服务器上获取图片,把文件输出流转化为Bitmap。
我看过google出品的图片加载框架gilde的代码总体架构,只能说惊了。过一段时间我会动手写一个ImageLoader来练手。。。以熟悉设计模式、网络请求、内存优化以及上边的知识点等等。加油加油。
综合技术
- 使用CrashHandler来获取应用的crash信息:写一个类实现UncaughtExceptionHandler接口,重写uncaughtException方法,可以获取到未catch的crash信息。然后做相应处理,写入文件,下一次再上传到服务器什么的。不过现在app一般会使用腾讯Bugly等SDK来获取crash信息了,比较专业。
- 使用multidex来解决方法数65536:这个在Gradle里配置一下就好了。还可以使用插件化来解决这个65536。
- Android插件化技术:也叫动态加载技术。先占坑。。。研究ing。。
- 反编译技术:smail....占坑。
JNI和NDK编程
-
JNI:Java Native Interface 。通过JNI,可以用java调用native的C、C++方法。
-
NDK:是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码。使用NDK有如下好处:
- 提高代码的安全性,由于so库反编译比较困难,相对来讲的。所以NDK提高了程序安全性。
- 可以很方便的使用已有的C/C++开源库。
- 便于平台间的移植。通过C/C++实现的动态库可以很方便的在其他平台上使用。
- 提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能。
-
JNI的开发流程:
- 在java中声明native方法。
- 编译java源文件得到class文件,然后通过javah命令导出JNI的头文件。
- 实现JNI方法。
- 编译so库并在java中调用。
-
NDK的开发流程:
- 下载并配置NDK。
- 创建android项目,并声明所需的native方法。
- 实现Android项目中的native方法。
- 切换到jni目录的父目录,然后通过ndk-build命令编译产生so库。
-
JNI调用java方法的流程:先通过类名找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法了。如果调用的是非静态方法,那么还需要构造出类的对象。详细步骤略。
Android性能优化
- 布局优化:
- 减少布局文件中的层级嵌套,能用一个ViewGroup解决的,就别用2个。
- 使用<include>标签和<merge>标签搭配,以重用布局和减少布局层级。
- ViewStub,ViewStub继承了View,它非常轻量级,不参与任何的布局和绘制过程。使用ViewStub可以实现按需加载,即需要加载某个界面的时候再加载,这个界面或许不会出现,需要出现了再去加载。可以提高资源利用率。比如说加载网络错误界面。
- 绘制优化:绘制优化是指View的onDraw方法要避免执行大量的操作。
- 不要在onDraw中创建新的布局对象。
- 不要在onDraw中执行耗时的任务。
其实不仅是onDraw,在onMeasure和onLayout中也要尽量注意。优化我们的算法逻辑。以提高性能。
-
内存泄漏优化:内存泄漏,老生常谈了。一般是指无用的对象的引用被持有,导致无用对象不能被回收而停留在内存中,使内存得不到释放的一种情况。在平时的android开发中,需要注意以下几点:
- 静态变量导致的内存泄漏。在静态变量中持有其他变量的引用,会导致那个变量的内存的不到释放。
- 单例模式导致的内存泄漏:和上面差不多,可以使用弱引用或者使用application的context。
- 属性动画导致的内存泄漏:使用属性动画播放无限循环的动画时,记得停止。
- 注册没取消、资源没关闭的造成的内存泄漏:比如广播接收器,Cursor。
- 匿名内部类和非静态内部类持有外部类的引用造成的内存泄漏:比如说我们常用的Handler,使用不当就很容易造成内存泄漏。
- WebView造成的泄露,讲道理webview坑比较多。
-
响应速度优化:比如说不要在onPause中执行太多操作,会减慢新activity的显示速度。多使用线程做计算、耗时操作。即使给予用户反馈,比如说加载新页面马上显示loading。。。等。
-
ListView和Bitmap优化:ViewHolder的使用、快速滑动时不要开启大量线程任务。Bitmap优化上面也介绍过了。
-
线程优化:使用线程池,可以有效提高系统资源利用率。
-
其他:
- 避免创建过多的对象。
- 尽量避免使用枚举。
- 常量使用static final修饰。
- 使用Android特有的数据结构:比如SparseArray、Pair等,这些数据结构在移动平台上使用更少的内存,具有较好的性能。
- 适当使用软引用和弱引用。
- 使用缓存。
待完善。。。
设计模式 个人笔记
面向对象六大原则
- 单一职责原则:一个类只负责做好一件事情。
- 开闭原则:对于拓展是开放的,对于修改是封闭的。也就是说,我们应该通过拓展来实现变化,而不是修改原有代码。当然了,这只是一种理想状态,实际开发中不要太拘泥于此。
- 里氏替换原则:所有引用基类的地方必须能够透明地使用其子类的对象。也就是多态这个特性的正确使用方法咯。
- 依赖倒置原则:主要是实现解耦,使得高层次的模块不依赖于低层次模块的具体实现细节。依赖于抽象类型:抽象类或接口。比如:各个模块之间相互传递的参数声明为抽象类型,而不是声明为具体的实现类。
- 接口隔离原则:类之间的依赖关系应该建立在最小的接口上。其原则是将非常庞大的、臃肿的接口拆分成更小的更具体的接口。
- 迪米特原则:一个对象应该对其他的对象有最少的了解。比如说:假设类A实现了某个功能,类B需要调用类A的去执行这个功能,那么类A应该只暴露一个函数给类B,这个函数表示是实现这个功能的函数,而不是让类A把实现这个功能的所有细分的函数暴露给B。
设计模式
单例模式
单例模式就不用过多的介绍了。下面列两种推荐写法:
-
静态内部类单例模式:
public class Singleton{ private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.sInstance; } private static class SingletonHolder{ private static final Singleton sInstance = new Singleton(); } }
在第一次调用Singleton的getInstance方法时,JVM会加载SingletonHolder类,因为这一步发生在JVM,所以保证了线程安全与单例对象的唯一性,同时还有延迟加载的效果。
-
枚举单例模式:
public enum Singleton{ INSTANCE; public void dosomething(){......} }
是的没错,就是如此的简单。枚举单例模式不仅写法简单,而且在保证了线程安全与唯一性的同时,还保证了反序列化时不会重新生成对象。可以说这种写法是目前单例模式的最佳写法。
-
什么时候该使用单例模式?
需要确保类有且只有一个对象时,比如说有一个进行网络请求的仓库类,这个类消耗的系统资源较多,而且不需要存在多个实例的时候。在android framework中,Application类就是一个单例类,在整个APP运行过程中,仅存在一个Application对象(单进程)。
Builder模式(建造者模式)
Builder模式的定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
相对于直接在javabean里使用setter,builder模式更加易读,在调用build()方法之后才返回相应的对象,保证了对象的一致性。
public class MyBuilder{
private int id;
private String num;
public MyData build(){
MyData d=new MyData();
d.setId(id);
d.setNum(num);
return t;
//build之后返回组装好的对象。
//在这里可以对设置数据的调用顺序进行正确的控制。
}
public MyBuilder setId(int id){
this.id=id;
return this;
}
//返回自身this,用于链式调用。
public MyBuilder setNum(String num){
this.num=num;
return this;
}
}
public class Test{
public static void main(String[] args){
MyData d=new MyBuilder().setId(10).setNum("hc").build();
}
}
什么时候使用builder模式呢?
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 多个部件或者零件,都可以装配到一个对象中,但是产生时运行结果又不相同时。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用建造者模式非常合适。
- 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时。
Android中的builder模式
AlertDialog.Builer builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
.setTitle("title")
.setMessage("message")
.setPositiveButton("Button1",
new DialogInterface.OnclickListener(){
public void onClick(DialogInterface dialog,int whichButton){
setTitle("click");
}
})
.create()
.show();
熟悉吧!看着这一串的链式调用,是不是很舒服呢~~
工厂方法模式
定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。
//抽象产品
public abstract class Product{
public abstract void method();
}
//具体产品A
public class ConcreteProductA extends Prodect{
public void method(){
System.out.println("我是产品A!");
}
}
//具体产品B
public class ConcreteProductB extends Prodect{
public void method(){
System.out.println("我是产品B!");
}
}
//抽象工厂
public abstract class Factory{
public abstract Product createProduct();
}
//具体工厂
public class MyFactory extends Factory{
//这里可以设置一个参数,让参数决定实例化哪个类。
public Product createProduct(){
return new ConcreteProductA();
}
}
这里主要分为四大模块:抽象工厂、具体工厂、抽象产品与具体产品。
在客户端中,我们需要哪个产品,就在工厂中生产哪个产品。
在Android中,我们经常会通过Context.getSystemService(String name)方法获取系统级的服务,在这个方法里,通过我们传入的服务名称字符参数来决定返回哪一个对象,各种系统级的服务都会注册在ContextImpl的一个map容器中,这些服务都是单例。所以在这里,用到了单例和工厂方法模式。
public Object getSystemService(String name) {
if (getBaseContext() == null) {
throw new IllegalStateException("System services not available to Activities before onCreate()");
}
//........
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
//.......
return super.getSystemService(name);
}
抽象工厂模式
定义:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要制定他们的具体类 。
放个简单栗子:
public abstract class AbstractProductA{
public abstract void method();
}
public abstract class AbstractProdectB{
public abstract void method();
}
public class ConcreteProductA1 extends AbstractProductA{
public void method(){
System.out.println("具体产品A1的方法!");
}
}
public class ConcreteProductA2 extends AbstractProductA{
public void method(){
System.out.println("具体产品A2的方法!");
}
}
public class ConcreteProductB1 extends AbstractProductB{
public void method(){
System.out.println("具体产品B1的方法!");
}
}
public class ConcreteProductB2 extends AbstractProductB{
public void method(){
System.out.println("具体产品B2的方法!");
}
}
public abstract class AbstractFactory{
public abstract AbstractProductA createProductA();
public abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory{
public AbstractProductA createProductA(){
return new ConcreteProductA1();
}
public AbstractProductB createProductB(){
return new ConcreteProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory{
public AbstractProductA createProductA(){
return new ConcreteProductA2();
}
public AbstractProductB createProductB(){
return new ConcreteProductB2();
}
}
抽象工厂模式比工厂模式更加抽象。说实话,在实际开发当中,工厂模式已经可以解决大部分情况。
策略模式
定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
举个例子:比如你现在又很多排序算法:冒泡、希尔、归并、选择等等。我们要根据实际情况来选择使用哪种算法,有一种常见的方法是,通过if…else或者case…等条件判断语句来选择。但是这个类的维护成本会变高,维护时也容易发生错误。这个时候就可以使用策略模式:我们可以定义一个算法抽象类AbstractAlgorithm,这个类定义一个抽象方法sort()。每个具体的排序算法去继承AbstractAlgorithm类并重写sort()实现排序。在需要使用排序的类Client类中,添加一个setAlgorithm(AbstractAlgorithm al);方法将算法注入Client,每次Client需要排序而是就调用al.sort()。
在Android中,我们平时在属性动画中使用的时间插值器,插值器有着不同的策略:线性插值其、加速减速插值器、减速插值器、自定义插值器等。我们在客户端中可以根据不同的需求注入不同的实现策略。
状态模式
定义:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。状态模式的一图是让一个对象在其内部状态改变的时候,其行为也随之改变。
看例子:
public interface TvState{
public void nextChannerl();
public void prevChannerl();
public void turnUp();
public void turnDown();
}
//具体的状态:关机时,对于操作不做具体响应
public class PowerOffState implements TvState{
public void nextChannel(){}
public void prevChannel(){}
public void turnUp(){}
public void turnDown(){}
}
//开机时,响应具体的操作。
public class PowerOnState implements TvState{
public void nextChannel(){
System.out.println("下一频道");
}
public void prevChannel(){
System.out.println("上一频道");
}
public void turnUp(){
System.out.println("调高音量");
}
public void turnDown(){
System.out.println("调低音量");
}
}
public interface PowerController{
public void powerOn();
public void powerOff();
}
//相当于一个电视遥控器。
public class TvController implements PowerController{
TvState mTvState;
public void setTvState(TvStete tvState){
mTvState=tvState;
}
public void powerOn(){
setTvState(new PowerOnState());
System.out.println("开机啦");
}
public void powerOff(){
setTvState(new PowerOffState());
System.out.println("关机啦");
}
public void nextChannel(){
mTvState.nextChannel();
}
public void prevChannel(){
mTvState.prevChannel();
}
public void turnUp(){
mTvState.turnUp();
}
public void turnDown(){
mTvState.turnDown();
}
}
//使用时
public class Client{
public static void main(String[] args){
TvController tvController=new TvController();
tvController.powerOn();
tvController.nextChannel();
tvController.turnUp();
tvController.powerOff();
//调高音量,此时不会生效
tvController.turnUp();
}
}
可以想像到,如果不使用状态模式,直接在客户端使用if--else判断当前状态的话,代码会多么难以维护和混乱。我们把状态封装成了对象,在不同的具体状态有着不同的实现。整个结构就变得很清晰了。
在Android源码中,WIFI管理模块用到了状态模式。当WIFI开启时,自动扫描周围的接入点,然后以列表的形式展示;当wifi关闭时则清空。这里wifi管理模块就是根据不同的状态执行不同的行为。
责任链模式
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系,将这些对象连成一条链,并沿这条链传递该请求,直到有对象处理它为止。
Android源码中的责任链模式:在Android处理点击事件时,父View先接收到点击事件,如果父View不处理则交给子View,依次往下传递,直到有View 处理它便会停止传递。
解释器模式
定义:给定一个语言,定义它的语法,并定义一个解释器,这个解释器用于解析语言。
这个实际开发中用得比较少。在Android中,我们经常使用的AndroidManifest.xml文件里,<Activity>,<Service>等标签语句,就是一种语法,PackageManagerService就是解释器,通过PackageManagerService解析这些标签,然后保存解析出来的信息。
命令模式
定义:将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
比如,当我们点击“关机”命令,系统会执行一系列操作,比如暂停事件处理、保存系统配置、结束程序进程、调用内核命令关闭计算机等等,这些命令封装从不同的对象,然后放入到队列中一个个去执行,还可以提供撤销操作。
Android中的命令模式:在Android事件机制中,底层逻辑对事件的转发处理。每次的按键事件会被封装成NotifyKeyArgs对象。通过InputDispatcher封装具体的事件操作。
观察者模式
定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被更新。
观察者模式中主要有以下角色:
- Subject:抽象主题,也就是被观察(Observable)的角色,抽象主题角色把所有观察者对象的引用保存在一个集合中,每个主题可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题,是抽象主题的具体实现。
- Observer:抽象观察者,该角色是观察者的抽象类,它定义了一个更新接口,使得在得到主题的更改通知时更新自己。
- ConcreteObserver:具体的观察者,抽象观察者的具体实现。
比如我们很熟悉的“订阅--发布”系统,就是观察者模式的一个经典应用。有新消息发布时,就会将消息发送给每个订阅者。
Android中,ListView的Adapter中的notifyDataSetChanged(),这个函数通知ListView的每个Item,数据源发生了变化,请重绘。还有BroadcastReceiver广播接收器,也是一种订阅-发布的情况,也属于观察者模式。
备忘录模式
定义:在不破坏封闭的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样,以后就可将对象恢复到原先保存的状态中。
在Android开发中,Activity里的onSaveInstanceState和onRestoreInstanceState我们都很熟悉,用于在异常情况中保存和恢复数据。这就是备忘录模式啦。
迭代器模式
迭代器模式,又称为(Cursor)模式。
定义:提供一种方法顺序访问一个容器对象中的各个元素,而不需要暴露该对象的内部表示。
Java中的Iterator类就是迭代器模式。
在Android开发中,我们使用SQLiteDatabase或者ContentProvider的时候,使用query方法时,通过返回的Cursor对象对数据进行顺序遍历,这就是迭代器模式。
模板方法模式
定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。
模板方法实际上是封装了一个固定流程,就像是一套执行模板一样,这些都在父类中定义好,而子类可以有不同的具体实现。
在Android开发中,我们在使用Activity时,用的就是系统给我们提供的模板,onCreate、onStart、onResume......我们只需要在这些方法里去实现我们的逻辑,就可以很轻松的完成一个activity了。
访问者模式
定义:封装一些作用于某种数据结构中各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
Android中运用访问者模式,其实主要是在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT。
中介者模式
定义:中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显调用,从而使他们可以轻松耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用保证这些作用可以彼此独立的变化,中介者模式将多对多的相互作用转为一对多的相互作用。
Android中的Binder机制就是一种中介者模式。我们在开发多进程应用的时候,通过Binder这个媒介,实现IPC通信。在Android Framework层中也是如此,系统启动时,各种系统服务会向ServiceManager提交注册,即ServiceManager持有各种系统服务的引用 ,当我们需要获取系统的Service时,比如ActivityManager、WindowManager等(它们都是Binder),首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManager和Binder驱动就是中介者。
代理模式
定义:为其他类提供一种代理以控制这个对象的访问。
使用场景:当无法或者不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。
代理模式在Android中,我们在使用AIDL进行跨进称通信时,操作的对象其实是远程对象的代理,AIDL生成一个代理类,帮我们完成把参数写入到Parcelable对象等操作。
组合模式
定义:将对象组成成树形结构,以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
在Android中View的结构是树形结构,每个ViewGroup包含一系列的View,而ViewGroup本身又是View。这是Android中非常典型的组合模式。
适配器模式
定义:把一个类的接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
在Android开发当中,我们在使用ListView或者RecyclerView的时候,我们知道数据源和ListView是没有关系的,我们通过给ListView设置适配器,适配器中提供了getView方法把数据源转化为ListView需要的对象,View。然后完成工作。这就是适配器模式。
装饰模式
定义:动态的给一个对象添加额外的智者,就增加功能来说,装饰模式比子类继承的方式更灵活。
装饰模式是以对客户端透明的方式拓展对象的功能,是继承关系的一个替代方案,而代理模式则是给一个对象提供一个代理对象,并使用代理对象来控制对原有对象的引用。装饰模式为所装饰的对象增强功能,代理模式对代理的对象施加控制,不进行增强。
Android中,ContextWrapper是Context的装饰类。
享元模式
定义:使用享元对象有效地支持大量的细粒度对象。
享元模式主要是为了重用对象,在系统中存在大量相似对象时,可以使用享元模式。比如Java中的常量池,线程池等。
在Android中,每次我们获取Message时调用Message.obtain()其实就是从消息池中取出可重复使用的消息,避免产生大量的Message对象。
外观模式
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。
Android内部有很多复杂的功能比如startActivty、sendBroadcast、bindService等等,这些功能内部的实现非常复杂,如果你看了源码你就能感受得到,但是使用者无需关心它内部实现了什么,只需要使用这些统一的高层接口就可以了。
桥接模式
定义:将抽象部分与实现部分分离,使他们独立地进行变化。
在Android中,View的视图层级与执行真正的硬件绘制相关类之间的关系可以看作是一种桥接模式。对于一个View来说,它有两个维度的变化,一个是它的描述比如Button、TextView等等他们是View的描述维度上的变化,另一个维度就是将View真正绘制到屏幕上,这跟Display、HardwareLayer和Canvas有关。这两个维度可以看成是桥接模式的应用。
总结
讲道理,看到那么多的设计模式我懵比了很久。在开发过程中,我们要注意运用设计模式,写出更加好的软件。当然,绝对要记住,拒绝过度设计,不要为了设计而设计。