diff --git a/android/advance/binder.md b/android/advance/binder.md index 4cd42dab..b84ba97b 100644 --- a/android/advance/binder.md +++ b/android/advance/binder.md @@ -2,7 +2,7 @@ ### 1、概述 -Android系统中,涉及到多进程间的通信底层都是依赖于Binder IPC机制。例如当进程A中的Activity要向进程B中的Service通信,这便需要依赖于Binder IPC。不仅于此,整个Android系统架构中,大量采用了Binder机制作为IPC(进程间通信)方案。 +Android系统中,涉及到多进程间的通信底层都是依赖于Binder IPC机制。例如当进程A中的Activity要向进程B中的Service通信,这便需要依赖于Binder IPC。不仅于此,整个Android系统架构中,大量采用了Binder机制作为IPC(进程间通信,Interprocess Communication)方案。 当然也存在部分其他的IPC方式,如管道、SystemV、Socket等。那么Android为什么不使用这些原有的技术,而是要使开发一种新的叫Binder的进程间通信机制呢? @@ -10,25 +10,25 @@ Android系统中,涉及到多进程间的通信底层都是依赖于Binder IPC **性能方面** -在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。 +在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对于传统的Socket方式,更加高效。**Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。** **安全方面** -传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。 +传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信的IP地址是客户端手动填入,很容易进行伪造。然而,Binder机制从协议本身就支持对通信双方做身份校检,从而大大提升了安全性。 ### 2、 Binder #### IPC原理 -从进程角度来看IPC机制 +从进程角度来看IPC(Interprocess Communication)机制 ![img](http://upload-images.jianshu.io/upload_images/3985563-a3722ee387793114.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的,Client端与Server端进程往往采用ioctl等方法跟内核空间的驱动进行交互。 +每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。例如,对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间。当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的。Client端与Server端进程往往采用ioctl等方法与内核空间的驱动进行交互。 #### Binder原理 -Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各种服务。架构图如下所示: +Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及Binder驱动,其中ServiceManager用于管理系统中的各种服务。架构图如下所示: ![img](http://upload-images.jianshu.io/upload_images/3985563-5ff2c4816543c433.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) @@ -50,9 +50,9 @@ Binder通信采用C/S架构,从组件视角来说,包含Client、Server、Se **获取服务(getService)**:Client进程使用某个Service前,须先向ServiceManager中获取相应的Service。该过程:Client是客户端,ServiceManager是服务端。 -**使用服务**:Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:client是客户端,server是服务端。 +**使用服务**:Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:Client是客户端,Server是服务端。 -图中的Client,Server,Service Manager之间交互都是虚线表示,是由于它们彼此之间不是直接交互的,而是都通过与Binder驱动进行交互的,从而实现IPC通信方式。其中Binder驱动位于内核空间,Client,Server,Service Manager位于用户空间。Binder驱动和Service Manager可以看做是Android平台的基础架构,而Client和Server是Android的应用层,开发人员只需自定义实现client、Server端,借助Android的基本平台架构便可以直接进行IPC通信。 +图中的Client,Server,Service Manager之间交互都是虚线表示,是由于它们彼此之间不是直接交互的,而是都通过与Binder驱动进行交互的,从而实现IPC通信(Interprocess Communication)方式。其中Binder驱动位于内核空间,Client,Server,Service Manager位于用户空间。Binder驱动和Service Manager可以看做是Android平台的基础架构,而Client和Server是Android的应用层,开发人员只需自定义实现Client、Server端,借助Android的基本平台架构便可以直接进行IPC通信。 **Binder运行的实例解释** @@ -60,35 +60,35 @@ Binder通信采用C/S架构,从组件视角来说,包含Client、Server、Se ```java //获取WindowManager服务引用 -WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE); +WindowManager wm = (WindowManager) getSystemService(getApplication().WINDOW_SERVICE); //布局参数layoutParams相关设置略... -View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null); +View view = LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null); //添加view wm.addView(view, layoutParams); ``` -**注册服务(addService):**在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。这一步是系统自动完成的。 +**注册服务(addService):** 在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。这一步是系统自动完成的。 -**获取服务(getService):**客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,通常是Service引用的代理对象,对数据进行一些处理操作。即第2行代码中,得到的wm是WindowManager对象的引用。 +**获取服务(getService):** 客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,通常是Service引用的代理对象,对数据进行一些处理操作。即第2行代码中,得到的wm是WindowManager对象的引用。 -**使用服务:**通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。即第6行调用WindowManager的addView函数,将触发远程调用,调用的是运行在systemServer进程中的WindowManager的addView函数。 +**使用服务:** 通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。即第6行调用WindowManager的addView函数,将触发远程调用,调用的是运行在systemServer进程中的WindowManager的addView函数。 **使用服务的具体执行过程** ![img](http://upload-images.jianshu.io/upload_images/3985563-727dd63017d2113b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -1. client通过获得一个server的代理接口,对server进行调用。 -2. 代理接口中定义的方法与server中定义的方法是一一对应的。 -3. client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成Parcel对象。 -4. 代理接口将Parcel发送给内核中的binder driver。 -5. server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回。 -6. 整个的调用过程是一个同步过程,在server处理的时候,client会block住。**因此client调用过程不应在主线程。** +1. Client通过获得一个Server的代理接口,对Server进行调用。 +2. 代理接口中定义的方法与Server中定义的方法是一一对应的。 +3. Client调用某个代理接口中的方法时,代理接口的方法会将Client传递的参数打包成Parcel对象。 +4. 代理接口将Parcel发送给内核中的Binder Driver。 +5. Server会读取Binder Driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回。 +6. 整个的调用过程是一个同步过程,在Server处理的时候,Client会Block住。**因此Client调用过程不应在主线程。** ## AIDL的使用 ### 1.AIDL的简介 -AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数,来完成进程间通信。 +AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以在Android设备上两个进程之间进行进程间通信(Interprocess Communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数,来完成进程间通信。 **简言之,AIDL能够实现进程间通信,其内部是通过Binder机制来实现的,后面会具体介绍,现在先介绍AIDL的使用。** @@ -179,7 +179,7 @@ public class Book implements Parcelable { } ``` -由于AIDL只支持数据类型:基本类型(int,long,char,boolean等),String,CharSequence,List,Map,其他类型必须使用import导入,即使它们可能在同一个包里,比如上面的Book。 +由于AIDL只支持数据类型:基本类型(int,long,char,boolean等),String,CharSequence,List,Map,其他类型必须使用import导入,即使它们可能在同一个包里,比如上面的Book。 **最终IBookManager.aidl 的实现** @@ -233,47 +233,47 @@ public static abstract class Stub extends android.os.Binder implements com.lvr.a ```java //实现了AIDL的抽象函数 - private IBookManager.Stub mbinder = new IBookManager.Stub() { - @Override - public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { - //什么也不做 - } +private IBookManager.Stub mbinder = new IBookManager.Stub() { + @Override + public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { + //什么也不做 + } - @Override - public void addBook(Book book) throws RemoteException { - //添加书本 - if(!mBookList.contains(book)){ - mBookList.add(book); - } + @Override + public void addBook(Book book) throws RemoteException { + //添加书本 + if (!mBookList.contains(book)) { + mBookList.add(book); } + } - @Override - public List getBookList() throws RemoteException { - return mBookList; - } - }; + @Override + public List getBookList() throws RemoteException { + return mBookList; + } +}; ``` 当客户端连接服务端,服务端就会调用如下方法: ```java - public IBinder onBind(Intent intent) { - return mbinder; - } +public IBinder onBind(Intent intent) { + return mbinder; +} ``` 就会把Stub实现对象返回给客户端,该对象是个Binder对象,可以实现进程间通信。 本例就不真实模拟两个应用之间的通信,而是让Service另外开启一个进程来模拟进程间通信。 -```java - - - - - - +``` xml + + + + + + ``` `android:process=":remote"`设置为另一个进程。``是为了能让其他apk隐式bindService。**通过隐式调用的方式来连接service,需要把category设为default,这是因为,隐式调用的时候,intent中的category默认会被设置为default。** @@ -285,25 +285,25 @@ public static abstract class Stub extends android.os.Binder implements com.lvr.a 客户端需要做的事情比较简单,首先需要绑定服务端的Service。 ```java - Intent intentService = new Intent(); - intentService.setAction("com.lvr.aidldemo.MyService"); - intentService.setPackage(getPackageName()); - intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MyClient.this.bindService(intentService, mServiceConnection, BIND_AUTO_CREATE); - Toast.makeText(getApplicationContext(),"绑定了服务",Toast.LENGTH_SHORT).show(); +Intent intentService = new Intent(); +intentService.setAction("com.lvr.aidldemo.MyService"); +intentService.setPackage(getPackageName()); +intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +MyClient.this.bindService(intentService, mServiceConnection, BIND_AUTO_CREATE); +Toast.makeText(getApplicationContext(), "绑定了服务", Toast.LENGTH_SHORT).show(); ``` 将服务端返回的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。 ```java - if(mIBookManager!=null){ - try { - mIBookManager.addBook(new Book(18,"新添加的书")); - Toast.makeText(getApplicationContext(),mIBookManager.getBookList().size()+"",Toast.LENGTH_SHORT).show(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } +if (mIBookManager != null) { + try { + mIBookManager.addBook(new Book(18, "新添加的书")); + Toast.makeText(getApplicationContext(), mIBookManager.getBookList().size() + "", Toast.LENGTH_SHORT).show(); + } catch (RemoteException e) { + e.printStackTrace(); + } +} ``` ### 3.AIDL的工作原理 @@ -311,7 +311,7 @@ public static abstract class Stub extends android.os.Binder implements com.lvr.a Binder机制的运行主要包括三个部分:注册服务、获取服务和使用服务。 其中注册服务和获取服务的流程涉及C的内容,由于个人能力有限,就不予介绍了。 -本篇文章主要介绍使用服务时,AIDL的工作原理。 +本篇文章主要介绍使用服务时,AIDL的工作原理。 #### ①.Binder对象的获取 @@ -323,10 +323,11 @@ Binder是实现跨进程通信的基础,那么Binder对象在服务端和客 Binder中两个关键方法: ```java - public class Binder implement IBinder{ - void attachInterface(IInterface plus, String descriptor) - IInterface queryLocalInterface(Stringdescriptor) //从IBinder中继承而来 - .......................... +public class Binder implement IBinder { + void attachInterface(IInterface plus, String descriptor) + + IInterface queryLocalInterface(Stringdescriptor) //从IBinder中继承而来 + .......................... } ``` @@ -338,9 +339,8 @@ Binder具有被跨进程传输的能力是因为它实现了IBinder接口。系 并保存了IInterface对象。 ```java -public Stub() -{ -this.attachInterface(this, DESCRIPTOR); +public Stub() { + this.attachInterface(this, DESCRIPTOR); } ``` @@ -349,39 +349,38 @@ this.attachInterface(this, DESCRIPTOR); 通过bindService获得Binder对象 ```java - MyClient.this.bindService(intentService, mServiceConnection, BIND_AUTO_CREATE); +MyClient.this.bindService(intentService, mServiceConnection, BIND_AUTO_CREATE); ``` 然后通过Binder对象获得IInterface对象。 ```java private ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - //通过服务端onBind方法返回的binder对象得到IBookManager的实例,得到实例就可以调用它的方法了 - mIBookManager = IBookManager.Stub.asInterface(binder); - } + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + //通过服务端onBind方法返回的binder对象得到IBookManager的实例,得到实例就可以调用它的方法了 + mIBookManager = IBookManager.Stub.asInterface(binder); + } - @Override - public void onServiceDisconnected(ComponentName name) { - mIBookManager = null; - } - }; + @Override + public void onServiceDisconnected(ComponentName name) { + mIBookManager = null; + } +}; ``` 其中`asInterface(binder)`方法如下: ```java -public static com.lvr.aidldemo.IBookManager asInterface(android.os.IBinder obj) -{ -if ((obj==null)) { -return null; -} -android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); -if (((iin!=null)&&(iin instanceof com.lvr.aidldemo.IBookManager))) { -return ((com.lvr.aidldemo.IBookManager)iin); -} -return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj); +public static com.lvr.aidldemo.IBookManager asInterface(android.os.IBinder obj) { + if ((obj == null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin != null) && (iin instanceof com.lvr.aidldemo.IBookManager))) { + return ((com.lvr.aidldemo.IBookManager) iin); + } + return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj); } ``` @@ -394,17 +393,17 @@ return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj); 以addBook方法为例,调用该方法后,客户端线程挂起,等待唤醒: ```java -@Override public void addBook(com.lvr.aidldemo.Book book) throws android.os.RemoteException -{ -.......... -//第一个参数:识别调用哪一个方法的ID -//第二个参数:Book的序列化传入数据 -//第三个参数:调用方法后返回的数据 -//最后一个不用管 -mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); -_reply.readException(); -} -.......... + @Override public void addBook(com.lvr.aidldemo.Book book) throws android.os.RemoteException + { + .......... + //第一个参数:识别调用哪一个方法的ID + //第二个参数:Book的序列化传入数据 + //第三个参数:调用方法后返回的数据 + //最后一个不用管 + mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); + _reply.readException(); + } + .......... } ``` @@ -413,20 +412,18 @@ _reply.readException(); Proxy对象中的transact调用发生后,会引起系统的注意,系统意识到Proxy对象想找它的真身Binder对象(系统其实一直存着Binder和Proxy的对应关系)。于是系统将这个请求中的数据转发给Binder对象,Binder对象将会在onTransact中收到Proxy对象传来的数据,于是它从data中取出客户端进程传来的数据,又根据第一个参数确定想让它执行添加书本操作,于是它就执行了响应操作,并把结果写回reply。代码概略如下: ```java -case TRANSACTION_addBook: -{ -data.enforceInterface(DESCRIPTOR); -com.lvr.aidldemo.Book _arg0; -if ((0!=data.readInt())) { -_arg0 = com.lvr.aidldemo.Book.CREATOR.createFromParcel(data); -} -else { -_arg0 = null; -} -//这里调用服务端实现的addBook方法 -this.addBook(_arg0); -reply.writeNoException(); -return true; +case TRANSACTION_addBook: { + data.enforceInterface(DESCRIPTOR); + com.lvr.aidldemo.Book _arg0; + if ((0 != data.readInt())) { + _arg0 = com.lvr.aidldemo.Book.CREATOR.createFromParcel(data); + } else { + _arg0 = null; + } + //这里调用服务端实现的addBook方法 + this.addBook(_arg0); + reply.writeNoException(); + return true; } ``` diff --git a/android/advance/mvp.md b/android/advance/mvp.md index d0ca3d81..222ca856 100644 --- a/android/advance/mvp.md +++ b/android/advance/mvp.md @@ -4,7 +4,7 @@ 提到MVP,就必须要先介绍一下它的前辈MVC,因为MVP正是基于MVC的基础发展而来的。两个之间的关系也是源远流长。 -**MVC,全称Model-View-Controller,即模型-视图-控制器。**具体如下: +**MVC,全称Model-View-Controller,即模型-视图-控制器。** 具体如下: **View:对应于布局文件** diff --git a/android/advance/serializable.md b/android/advance/serializable.md index 7b547373..92a34f64 100644 --- a/android/advance/serializable.md +++ b/android/advance/serializable.md @@ -6,13 +6,10 @@ Serializable的作用是**为了保存对象的属性到本地文件、数据库 从上面的设计上我们就可以看出优劣了。 - **2、效率及选择** -Parcelable的性能比Serializable好,在内存开销方面较小,所以**在内存间数据传输时推荐使用Parcelable**,如activity间传输数据,而Serializable可将数据持久化方便保存,所以**在需要保存或网络传输数据时选择Serializable**,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化 - - +Parcelable的性能比Serializable好,在内存开销方面较小,所以**在内存间数据传输时推荐使用Parcelable**,如activity间传输数据,而Serializable可将数据持久化方便保存,所以**在需要保存或网络传输数据时选择Serializable**,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化。 **3、编程实现** @@ -72,4 +69,4 @@ in.readStringList(list); **4、高级功能上** -Serializable序列化不保存静态变量,可以使用Transient关键字对部分字段不进行序列化,也可以覆盖writeObject、readObject方法以实现序列化过程自定义 \ No newline at end of file +Serializable序列化不保存静态变量,可以使用Transient关键字对部分字段不进行序列化,也可以覆盖writeObject、readObject方法以实现序列化过程自定义。 \ No newline at end of file diff --git a/android/basis/HandlerThread.md b/android/basis/HandlerThread.md index c135ccfd..7a1fa49b 100644 --- a/android/basis/HandlerThread.md +++ b/android/basis/HandlerThread.md @@ -1,4 +1,4 @@ -**我们知道在Android系统中,我们执行完耗时操作都要另外开启子线程来执行,执行完线程以后线程会自动销毁。想象一下如果我们在项目中经常要执行耗时操作,如果经常要开启线程,接着又销毁线程,这无疑是很消耗性能的?那有什么解决方法呢?** +**我们知道在Android系统中,我们执行完耗时操作都要另外开启子线程来执行,执行完线程以后线程会自动销毁。想象一下如果我们在项目中经常要执行耗时操作,如果经常要开启线程,接着又销毁线程,这无疑是很消耗性能的?那有什么解决方法呢?** 1. 使用线程池 2. 使用HandlerThread @@ -12,7 +12,7 @@ ### 使用场景 -HandlerThread是Google帮我们封装好的,可以用来执行多个耗时操作,而不需要多次开启线程,里面是采用handler和Looper实现的 +HandlerThread是Google帮我们封装好的,可以用来执行多个耗时操作,而不需要多次开启线程,里面是采用Handler和Looper实现的。 > Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called. @@ -35,7 +35,7 @@ handlerThread.start(); 1. 将我们的handlerThread与Handler绑定在一起。 还记得是怎样将Handler与线程对象绑定在一起的吗?其实很简单,就是将线程的looper与Handler绑定在一起,代码如下: -```java +``` java mThreadHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { @@ -51,14 +51,15 @@ mThreadHandler = new Handler(mHandlerThread.getLooper()) { ### 完整测试代码如下 -```java +``` java public class MainActivity extends AppCompatActivity { - private TextView mTv; + private static final int MSG_UPDATE_INFO = 0x100; Handler mMainHandler = new Handler(); + private TextView mTv; private Handler mThreadHandler; - private static final int MSG_UPDATE_INFO = 0x100; private HandlerThread mHandlerThread; - private boolean isUpdate=true; + private boolean isUpdate = true; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -66,6 +67,7 @@ public class MainActivity extends AppCompatActivity { mTv = (TextView) findViewById(R.id.tv); initHandlerThread(); } + private void initHandlerThread() { mHandlerThread = new HandlerThread("xujun"); mHandlerThread.start(); @@ -73,12 +75,13 @@ public class MainActivity extends AppCompatActivity { @Override public void handleMessage(Message msg) { checkForUpdate(); - if(isUpdate){ + if (isUpdate) { mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO); } } }; } + /** * 模拟从服务器解析数据 */ @@ -98,18 +101,21 @@ public class MainActivity extends AppCompatActivity { e.printStackTrace(); } } + @Override protected void onResume() { - isUpdate=true; + isUpdate = true; super.onResume(); mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO); } + @Override protected void onPause() { super.onPause(); - isUpdate=false; + isUpdate = false; mThreadHandler.removeMessages(MSG_UPDATE_INFO); } + @Override protected void onDestroy() { super.onDestroy(); @@ -127,27 +133,31 @@ public class MainActivity extends AppCompatActivity { ## HandlerThread源码分析 -官方源代码如下,是基于sdk23的,可以看到,只有一百多行代码而已 +官方源代码如下,是基于sdk23的,可以看到,只有一百多行代码而已。 ```java public class HandlerThread extends Thread { int mPriority; int mTid = -1; Looper mLooper; + public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } + public HandlerThread(String name, int priority) { super(name); mPriority = priority; } + /** * Call back method that can be explicitly overridden if needed to execute some * setup before Looper loops. */ protected void onLooperPrepared() { } + @Override public void run() { mTid = Process.myTid(); @@ -165,6 +175,7 @@ public class HandlerThread extends Thread { Looper.loop(); mTid = -1; } + public Looper getLooper() { if (!isAlive()) { return null; @@ -180,6 +191,7 @@ public class HandlerThread extends Thread { } return mLooper; } + public boolean quit() { Looper looper = getLooper(); if (looper != null) { @@ -188,6 +200,7 @@ public class HandlerThread extends Thread { } return false; } + public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { @@ -196,6 +209,7 @@ public class HandlerThread extends Thread { } return false; } + /** * Returns the identifier of this thread. See Process.myTid(). */ @@ -208,19 +222,20 @@ public class HandlerThread extends Thread { ### 1)首先我们先来看一下它的构造方法 ```java - public HandlerThread(String name) { - super(name); - mPriority = Process.THREAD_PRIORITY_DEFAULT; - } - public HandlerThread(String name, int priority) { - super(name); - mPriority = priority; - } +public HandlerThread(String name) { + super(name); + mPriority = Process.THREAD_PRIORITY_DEFAULT; +} + +public HandlerThread(String name, int priority) { + super(name); + mPriority = priority; +} ``` 有两个构造方法,一个参数的和两个参数的,name代表当前线程的名称,priority为线程的优先级别 -### 2)接着我们来看一下run()方法,在run方法里面我们可以看到我们会初始化一个Looper,并设置线程的优先级别 +### 2)接着我们来看一下run()方法,在run方法里面我们可以看到我们会初始化一个Looper,并设置线程的优先级别 ```java public void run() { @@ -241,7 +256,7 @@ public void run() { } ``` -- 还记得我们前面我们说到使用HandlerThread的时候必须调用start()方法,接着才可以将我们的HandlerThread和我们的handler绑定在一起吗?其实原因就是我们是在run()方法才开始初始化我们的looper,而我们调用HandlerThread的start()方法的时候,线程会交给虚拟机调度,由虚拟机自动调用run方法 +- 还记得我们前面我们说到使用HandlerThread的时候必须调用`start()`方法,接着才可以将我们的HandlerThread和我们的handler绑定在一起吗?其实原因就是我们是在`run()`方法才开始初始化我们的looper,而我们调用HandlerThread的`start()`方法的时候,线程会交给虚拟机调度,由虚拟机自动调用run方法: ```java mHandlerThread.start(); @@ -256,7 +271,7 @@ mThreadHandler = new Handler(mHandlerThread.getLooper()) { }; ``` -- 这里我们为什么要使用锁机制和notifyAll();,原因我们可以从getLooper()方法中知道 +- 这里我们为什么要使用锁机制和`notifyAll()`;,原因我们可以从`getLooper()`方法中知道 ```java public Looper getLooper() { @@ -302,7 +317,7 @@ public boolean quitSafely() { } ``` -跟踪这两个方法容易知道只两个方法最终都会调用MessageQueue的quit(boolean safe)方法 +跟踪这两个方法容易知道只两个方法最终都会调用MessageQueue的`quit(boolean safe)`方法 ```java void quit(boolean safe) { @@ -326,7 +341,7 @@ void quit(boolean safe) { } ``` -不安全的会调用removeAllMessagesLocked();这个方法,我们来看这个方法是怎样处理的,其实就是遍历Message链表,移除所有信息的回调,并重置为null +不安全的会调用`removeAllMessagesLocked();`这个方法,我们来看这个方法是怎样处理的,其实就是遍历Message链表,移除所有信息的回调,并重置为null。 ```java private void removeAllMessagesLocked() { @@ -340,14 +355,14 @@ private void removeAllMessagesLocked() { } ``` -安全地会调用removeAllFutureMessagesLocked();这个方法,它会根据Message.when这个属性,判断我们当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,等待该消息处理处理完毕再退出该循环。因此说quitSafe()是安全的,而quit()方法是不安全的,因为quit方法不管是否正在处理消息,直接移除所有回调。 +安全地会调用`removeAllFutureMessagesLocked();`这个方法,它会根据Message.when这个属性,判断我们当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,等待该消息处理处理完毕再退出该循环。因此说`quitSafe()`是安全的,而`quit()`方法是不安全的,因为quit方法不管是否正在处理消息,直接移除所有回调。 ```java private void removeAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p != null) { - //判断当前队列中的消息是否正在处理这个消息,》没有的话,直接移除所有回调 + //判断当前队列中的消息是否正在处理这个消息,没有的话,直接移除所有回调 if (p.when > now) { removeAllMessagesLocked(); } else {//正在处理的话,等待该消息处理处理完毕再退出该循环 diff --git a/android/basis/IntentService.md b/android/basis/IntentService.md index 8896635b..833b8a4c 100644 --- a/android/basis/IntentService.md +++ b/android/basis/IntentService.md @@ -4,13 +4,13 @@ IntentService是Android里面的一个封装类,继承自四大组件之一的 ## 二、作用 -处理异步请求,实现多线程 +处理异步请求,实现多线程。 ## 三、 工作流程 ![](http://upload-images.jianshu.io/upload_images/944365-fa5bfe6dffa531ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -注意:若启动IntentService 多次,那么每个耗时操作则以队列的方式在 IntentService的onHandleIntent回调方法中依次执行,执行完自动结束。 +注意:若启动IntentService多次,那么每个耗时操作则以队列的方式在IntentService的onHandleIntent回调方法中依次执行,执行完自动结束。 ## 四、实现步骤 @@ -87,46 +87,38 @@ public class myIntentService extends IntentService { ```xml - - - - + + + + ``` - 步骤3:在Activity中开启Service服务 ```java -package com.example.carson_ho.demoforintentservice; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - //同一服务只会开启一个工作线程 - //在onHandleIntent函数里依次处理intent请求。 - - Intent i = new Intent("cn.scu.finch"); - Bundle bundle = new Bundle(); - bundle.putString("taskName", "task1"); - i.putExtras(bundle); - startService(i); - - Intent i2 = new Intent("cn.scu.finch"); - Bundle bundle2 = new Bundle(); - bundle2.putString("taskName", "task2"); - i2.putExtras(bundle2); - startService(i2); - - startService(i); //多次启动 - } +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + //同一服务只会开启一个工作线程 + //在onHandleIntent函数里依次处理intent请求。 + + Intent i = new Intent("cn.scu.finch"); + Bundle bundle = new Bundle(); + bundle.putString("taskName", "task1"); + i.putExtras(bundle); + startService(i); + + Intent i2 = new Intent("cn.scu.finch"); + Bundle bundle2 = new Bundle(); + bundle2.putString("taskName", "task2"); + i2.putExtras(bundle2); + startService(i2); + + startService(i); //多次启动 } +} ``` - 结果 @@ -161,22 +153,23 @@ public void onCreate() { } private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { super(looper); } -//IntentService的handleMessage方法把接收的消息交给onHandleIntent()处理 -//onHandleIntent()是一个抽象方法,使用时需要重写的方法 + // IntentService的handleMessage方法把接收的消息交给onHandleIntent()处理 + // onHandleIntent()是一个抽象方法,使用时需要重写的方法 @Override public void handleMessage(Message msg) { // onHandleIntent 方法在工作线程中执行,执行完调用 stopSelf() 结束服务。 - onHandleIntent((Intent)msg.obj); - //onHandleIntent 处理完成后 IntentService会调用 stopSelf() 自动停止。 + onHandleIntent((Intent) msg.obj); + //onHandleIntent 处理完成后 IntentService会调用 stopSelf() 自动停止。 stopSelf(msg.arg1); } } -////onHandleIntent()是一个抽象方法,使用时需要重写的方法 +// onHandleIntent()是一个抽象方法,使用时需要重写的方法 @WorkerThread protected abstract void onHandleIntent(Intent intent); ``` @@ -192,8 +185,8 @@ public int onStartCommand(Intent intent, int flags, int startId) { public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; -//把 intent 参数包装到 message 的 obj 中,然后发送消息,即添加到消息队列里 -//这里的Intent 就是启动服务时startService(Intent) 里的 Intent。 + //把 intent 参数包装到 message 的 obj 中,然后发送消息,即添加到消息队列里 + //这里的Intent 就是启动服务时startService(Intent) 里的 Intent。 msg.obj = intent; mServiceHandler.sendMessage(msg); } @@ -207,7 +200,6 @@ public void onDestroy() { - 总结 - ​ 从上面源码可以看出,IntentService本质是采用Handler & HandlerThread方式: @@ -219,14 +211,14 @@ public void onDestroy() { 因此我们通过复写方法onHandleIntent(),再在里面根据Intent的不同进行不同的线程操作就可以了 -**注意事项 工作任务队列是顺序执行的。** +**注意事项:工作任务队列是顺序执行的。** -> 如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行 +> 如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。 原因: 1. 由于onCreate() 方法只会调用一次,所以只会创建一个工作线程; -2. 当多次调用 startService(Intent) 时(onStartCommand也会调用多次)其实并不会创建新的工作线程,只是把消息加入消息队列中等待执行,**所以,多次启动 IntentService 会按顺序执行事件** +2. 当多次调用 startService(Intent) 时(onStartCommand也会调用多次)其实并不会创建新的工作线程,只是把消息加入消息队列中等待执行,**所以,多次启动 IntentService 会按顺序执行事件**; 3. 如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。 ## 七、使用场景 @@ -254,12 +246,6 @@ public void onDestroy() { - IntentService内部采用了HandlerThread实现,作用类似于后台线程; -- 与后台线程相比, - - IntentService是一种后台服务 - - ,优势是:优先级高(不容易被系统杀死),从而保证任务的执行 +- 与后台线程相比,IntentService是一种后台服务,优势是:优先级高(不容易被系统杀死),从而保证任务的执行。 > 对于后台线程,若进程中没有活动的四大组件,则该线程的优先级非常低,容易被系统杀死,无法保证任务的执行 - -# \ No newline at end of file diff --git a/android/basis/dalvik-art.md b/android/basis/dalvik-art.md index 479f5fcc..8e198c1a 100644 --- a/android/basis/dalvik-art.md +++ b/android/basis/dalvik-art.md @@ -1,6 +1,6 @@ ## 一、什么是Dalvik虚拟机 -Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的Java应用程序的运行。dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Google对其进行了特定的优化,使得Dalvik具有高效、简洁、节省资源的特点。从Android系统架构图知,Dalvik虚拟机运行在Android的运行时库层。 +Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的Java应用程序的运行。dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Google对其进行了特定的优化,使得Dalvik具有**高效、简洁、节省资源**的特点。从Android系统架构图知,Dalvik虚拟机运行在Android的运行时库层。 Dalvik作为面向Linux、为嵌入式操作系统设计的虚拟机,主要负责完成对象生命周期管理、堆栈管理、线程管理、安全和异常管理,以及垃圾回收等。另外,Dalvik早期并没有JIT编译器,直到Android2.2才加入了对JIT的技术支持。 @@ -58,7 +58,7 @@ dex格式文件使用共享的、特定类型的常量池机制来节省内存 1.Java编译器对工程本身的java代码进行编译,这些java代码有三个来源:app的源代码,由资源文件生成的R文件(aapt工具),以及有aidl文件生成的java接口文件(aidl工具)。产出为.class文件。 -①.用AAPT编译R.java文件 +①用AAPT编译R.java文件 ②编译AIDL的java文件 @@ -76,7 +76,7 @@ dex格式文件使用共享的、特定类型的常量池机制来节省内存 #### 什么是ART: -ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time (JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运 行。ART则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。 +ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time (JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。 #### ART优点: @@ -94,40 +94,40 @@ ART代表Android Runtime,其处理应用程序执行的方式完全不同于Da **预编译** -在dalvik中,如同其他大多数JVM一样,都采用的是JIT来做及时翻译(动态翻译),将dex或odex中并排的dalvik code(或者叫smali指令集)**运行态**翻译成native code去执行.JIT的引入使得dalvik提升了3~6倍的性能。 +在dalvik中,如同其他大多数JVM一样,都采用的是JIT来做及时翻译(动态翻译),将dex或odex中并排的dalvik code(或者叫smali指令集)**运行态**翻译成native code去执行。JIT的引入使得dalvik提升了3~6倍的性能。 -而在ART中,完全抛弃了dalvik的JIT,使用了AOT直接在安装时将其完全翻译成native code.这一技术的引入,使得虚拟机执行指令的速度又一重大提升 +而在ART中,完全抛弃了dalvik的JIT,使用了AOT直接在安装时将其完全翻译成native code。这一技术的引入,使得虚拟机执行指令的速度又一重大提升。 **垃圾回收机制** -首先介绍下dalvik的GC的过程.主要有有四个过程: +首先介绍下dalvik的GC的过程。主要有有四个过程: -1. 当gc被触发时候,其会去查找所有活动的对象,这个时候整个程序与虚拟机内部的所有线程就会挂起,这样目的是在较少的堆栈里找到所引用的对象.需要注意的是这个回收动作和应用程序**非并发**。 -2. gc对符合条件的对象进行标记 -3. gc对标记的对象进行回收 -4. 恢复所有线程的执行现场继续运行 +1. 当gc被触发时候,其会去查找所有活动的对象,这个时候整个程序与虚拟机内部的所有线程就会挂起,这样目的是在较少的堆栈里找到所引用的对象; + - 注意:这个回收动作和应用程序**非并发**; +2. gc对符合条件的对象进行标记; +3. gc对标记的对象进行回收; +4. 恢复所有线程的执行现场继续运行。 -**dalvik这么做的好处是,当pause了之后,GC势必是相当快速的.但是如果出现GC频繁并且内存吃紧势必会导致UI卡顿,掉帧.操作不流畅等。** +**dalvik这么做的好处是,当pause了之后,GC势必是相当快速的。但是如果出现GC频繁并且内存吃紧势必会导致UI卡顿、掉帧、操作不流畅等。** -后来ART改善了这种GC方式 , **主要的改善点在将其非并发过程改变成了部分并发.还有就是对内存的重新分配管理**。 +后来ART改善了这种GC方式, 主要的改善点在将其**非并发过程**改成了**部分并发**,还有就是对**内存的重新分配管理**。 当ART GC发生时: -1. GC将会锁住Java堆,扫描并进行标记 -2. 标记完毕释放掉Java堆的锁,并且挂起所有线程 -3. GC对标记的对象进行回收 -4. 恢复所有线程的执行现场继续运行 -5. 重复2-4直到结束 +1. GC将会锁住Java堆,扫描并进行标记; +2. 标记完毕释放掉Java堆的锁,并且挂起所有线程; +3. GC对标记的对象进行回收; +4. 恢复所有线程的执行现场继续运行; +5. **重复2-4直到结束。** -可以看出整个过程做到了部分并发使得时间缩短.据官方测试数据说gc效率提高2倍 +可以看出整个过程做到了部分并发使得时间缩短。据官方测试数据说GC效率提高2倍。 **提高内存使用,减少碎片化** -**Dalvik内存管理特点是:内存碎片化严重**,当然这也是Mark and Sweep算法带来的弊端 +**Dalvik内存管理特点是:内存碎片化严重**,当然这也是Mark and Sweep算法带来的弊端。 ![img](http://upload-images.jianshu.io/upload_images/3985563-f170d48f08992b3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -可以看出每次gc后内存千疮百孔,本来连续分配的内存块变得碎片化严重,之后再分配进入的对象再进行内存寻址变得困难。 +可以看出每次GC后内存千疮百孔,本来连续分配的内存块变得碎片化严重,之后再分配进入的对象再进行内存寻址变得困难。 -**ART的解决:**在ART中,它将Java分了一块空间命名为**Large-Object-Space**,这块内存空间的引入用来专门存放large object。同时ART又引入了moving collector的技术,即将不连续的物理内存块进行对齐.对齐了后内存碎片化就得到了很好的解决.Large-Object-Space的引入一是因为moving collector对大块内存的位移时间成本太高,而且提高内存的利用率 -根官方统计,ART的内存利用率提高10倍了左右。 \ No newline at end of file +**ART的解决:**在ART中,它将Java分了一块空间命名为**Large-Object-Space**,这块内存空间的引入用来专门存放large object。同时ART又引入了moving collector的技术,即将不连续的物理内存块进行对齐。对齐了后内存碎片化就得到了很好的解决。Large-Object-Space的引入是因为moving collector对大块内存的位移时间成本太高。根官方统计,ART的内存利用率提高10倍了左右,大大提高了内存的利用率。 \ No newline at end of file diff --git a/android/basis/decorview.md b/android/basis/decorview.md index 34b9732b..26ad7471 100644 --- a/android/basis/decorview.md +++ b/android/basis/decorview.md @@ -10,37 +10,43 @@ Window是视图的承载器,内部持有一个 DecorView,而这个DecorView ### DecorView -DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,**在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。**具体情况和Android版本及主体有关,以其中一个布局为例,如下所示: +DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,**在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。** 具体情况和Android版本及主体有关,以其中一个布局为例,如下所示: -```Xml +```xml + android:fitsSystemWindows="true" + android:orientation="vertical"> - + + - + + + android:gravity="center_vertical" /> - + android:foreground="?android:attr/windowContentOverlay" + android:foregroundGravity="fill_horizontal|top" /> ``` @@ -55,7 +61,7 @@ ViewGroup rootView = (ViewGroup) content.getChildAt(0); ViewRoot可能比较陌生,但是其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。 -ViewRoot对应**ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带**,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。 +ViewRoot对应**ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带**,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。 ViewRoot并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的**名义上的父视图**。RootView继承了Handler类,可以接收事件并分发,Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的。 @@ -73,56 +79,55 @@ ViewRoot并不属于View树的一份子。从源码实现上来看,它既非Vi ```java public void setContentView(@LayoutRes int layoutResID) { - getWindow().setContentView(layoutResID); - initWindowDecorActionBar(); - } + getWindow().setContentView(layoutResID); + initWindowDecorActionBar(); +} ``` 可以看到实际是交给Window装载视图。**下面来看看Activity是怎么获得Window对象的?** ```java - final void attach(Context context, ActivityThread aThread, - Instrumentation instr, IBinder token, int ident, - Application application, Intent intent, ActivityInfo info, - CharSequence title, Activity parent, String id, - NonConfigurationInstances lastNonConfigurationInstances, - Configuration config, String referrer, IVoiceInteractor voiceInteractor, - Window window) { - .................................................................. +final void attach(Context context, ActivityThread aThread, + Instrumentation instr, IBinder token, int ident, + Application application, Intent intent, ActivityInfo info, + CharSequence title, Activity parent, String id, + NonConfigurationInstances lastNonConfigurationInstances, + Configuration config, String referrer, IVoiceInteractor voiceInteractor, + Window window) { + .................................................................. mWindow = new PhoneWindow(this, window);//创建一个Window对象 mWindow.setWindowControllerCallback(this); mWindow.setCallback(this);//设置回调,向Activity分发点击或状态改变等事件 mWindow.setOnWindowDismissedCallback(this); - ................................................................. + ................................................................. mWindow.setWindowManager( - (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), - mToken, mComponent.flattenToString(), + (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), + mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//给Window设置WindowManager对象 - .................................................................... - } + .................................................................... +} ``` 在Activity中的attach()方法中,生成了PhoneWindow实例。既然有了Window对象,那么我们就可以**设置DecorView给Window对象了。** ```java - public void setContentView(int layoutResID) { - if (mContentParent == null) {//mContentParent为空,创建一个DecroView - installDecor(); - } else { - mContentParent.removeAllViews();//mContentParent不为空,删除其中的View - } - mLayoutInflater.inflate(layoutResID, mContentParent);//为mContentParent添加子View,即Activity中设置的布局文件 - final Callback cb = getCallback(); - if (cb != null && !isDestroyed()) { - cb.onContentChanged();//回调通知,内容改变 - } +public void setContentView(int layoutResID) { + if (mContentParent == null) {//mContentParent为空,创建一个DecroView + installDecor(); + } else { + mContentParent.removeAllViews();//mContentParent不为空,删除其中的View + } + mLayoutInflater.inflate(layoutResID, mContentParent);//为mContentParent添加子View,即Activity中设置的布局文件 + final Callback cb = getCallback(); + if (cb != null && !isDestroyed()) { + cb.onContentChanged();//回调通知,内容改变 } +} ``` -看了下来,可能有一个疑惑:**mContentParent到底是什么?** -就是前面布局中`@android:id/content`所对应的FrameLayout。 +看了下来,可能有一个疑惑:**mContentParent到底是什么?** 就是前面布局中`@android:id/content`所对应的FrameLayout。 -**通过上面的流程我们大致可以了解先在PhoneWindow中创建了一个DecroView,其中创建的过程中可能根据Theme不同,加载不同的布局格式,例如有没有Title,或有没有ActionBar等,然后再向mContentParent中加入子View,即Activity中设置的布局。到此位置,视图一层层嵌套添加上了。** +**通过上面的流程我们大致可以了解先在PhoneWindow中创建了一个DecroView,其中创建的过程中可能根据Theme不同,加载不同的布局格式,例如有没有Title,或有没有ActionBar等,然后再向mContentParent中加入子View,即Activity中设置的布局。到此位置,视图一层层嵌套添加上了。** 下面具体来看看`installDecor();`方法,**怎么创建的DecroView,并设置其整体布局?** @@ -147,9 +152,9 @@ private void installDecor() { 再来看看 generateDecor() ```java - protected DecorView generateDecor() { - return new DecorView(getContext(), -1); - } +protected DecorView generateDecor() { + return new DecorView(getContext(), -1); +} ``` 很简单,创建了一个DecorView。 @@ -158,54 +163,58 @@ private void installDecor() { ```java protected ViewGroup generateLayout(DecorView decor) { - // 从主题文件中获取样式信息 - TypedArray a = getWindowStyle(); - ................... - if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { - requestFeature(FEATURE_NO_TITLE); - } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { - // Don't allow an action bar if there is no title. - requestFeature(FEATURE_ACTION_BAR); - } - ................ - // 根据主题样式,加载窗口布局 - int layoutResource; - int features = getLocalFeatures(); - // System.out.println("Features: 0x" + Integer.toHexString(features)); - if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { - layoutResource = R.layout.screen_swipe_dismiss; - } else if(...){ - ... - } + // 从主题文件中获取样式信息 + TypedArray a = getWindowStyle(); - View in = mLayoutInflater.inflate(layoutResource, null); //加载layoutResource + ................... - //往DecorView中添加子View,即文章开头介绍DecorView时提到的布局格式,那只是一个例子,根据主题样式不同,加载不同的布局。 - decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - mContentRoot = (ViewGroup) in; + if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { + requestFeature(FEATURE_NO_TITLE); + } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { + // Don't allow an action bar if there is no title. + requestFeature(FEATURE_ACTION_BAR); + } - ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 这里获取的就是mContentParent - if (contentParent == null) { - throw new RuntimeException("Window couldn't find content container view"); - } + ................ - if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { - ProgressBar progress = getCircularProgressBar(false); - if (progress != null) { - progress.setIndeterminate(true); - } - } + // 根据主题样式,加载窗口布局 + int layoutResource; + int features = getLocalFeatures(); + // System.out.println("Features: 0x" + Integer.toHexString(features)); + if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { + layoutResource = R.layout.screen_swipe_dismiss; + } else if(...){ + ... + } - if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { - registerSwipeCallbacks(); - } + View in = mLayoutInflater.inflate(layoutResource, null);//加载layoutResource - // Remaining setup -- of background and title -- that only applies - // to top-level windows. - ... + //往DecorView中添加子View,即文章开头介绍DecorView时提到的布局格式,那只是一个例子,根据主题样式不同,加载不同的布局。 + decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mContentRoot = (ViewGroup) in; - return contentParent; + ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);// 这里获取的就是mContentParent + if (contentParent == null) { + throw new RuntimeException("Window couldn't find content container view"); } + + if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { + ProgressBar progress = getCircularProgressBar(false); + if (progress != null) { + progress.setIndeterminate(true); + } + } + + if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { + registerSwipeCallbacks(); + } + + // Remaining setup -- of background and title -- that only applies + // to top-level windows. + ... + + return contentParent; +} ``` 虽然比较复杂,但是逻辑还是很清楚的。先从主题中获取样式,然后根据样式,加载对应的布局到DecorView中,然后从中获取mContentParent。获得到之后,可以回到上面的代码,为mContentParent添加View,即Activity中的布局。 @@ -231,14 +240,14 @@ private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { if (a != null) { //这里面执行了Activity.onResume() handleResumeActivity(r.token, false, r.isForward, - !r.activity.mFinished && !r.startsNotResumed); + !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { try { - r.activity.mCalled = false; - //执行Activity.onPause() - mInstrumentation.callActivityOnPause(r.activity); - } + r.activity.mCalled = false; + //执行Activity.onPause() + mInstrumentation.callActivityOnPause(r.activity); + } } } } @@ -247,39 +256,41 @@ private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { 重点看下handleResumeActivity(),在这其中,DecorView将会显示出来,同时重要的一个角色:ViewRoot也将登场。 ```java -final void handleResumeActivity(IBinder token, - boolean clearHide, boolean isForward, boolean reallyResume) { - - //这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的 - ActivityClientRecord r = performResumeActivity(token, clearHide); - - if (r != null) { - final Activity a = r.activity; - if (r.window == null && !a.mFinished && willBeVisible) { - r.window = r.activity.getWindow(); - View decor = r.window.getDecorView(); - //decor对用户不可见 - decor.setVisibility(View.INVISIBLE); - ViewManager wm = a.getWindowManager(); - WindowManager.LayoutParams l = r.window.getAttributes(); - a.mDecor = decor; - - l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - - if (a.mVisibleFromClient) { - a.mWindowAdded = true; - //被添加进WindowManager了,但是这个时候,还是不可见的 - wm.addView(decor, l); - } +final void handleResumeActivity(IBinder token, boolean clearHide, + boolean isForward, boolean reallyResume) { + + //这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的 + ActivityClientRecord r = performResumeActivity(token, clearHide); + + if (r != null) { + final Activity a = r.activity; + if (r.window == null && !a.mFinished && willBeVisible) { + r.window = r.activity.getWindow(); + View decor = r.window.getDecorView(); + //decor对用户不可见 + decor.setVisibility(View.INVISIBLE); + ViewManager wm = a.getWindowManager(); + WindowManager.LayoutParams l = r.window.getAttributes(); + a.mDecor = decor; + + l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + + if (a.mVisibleFromClient) { + a.mWindowAdded = true; + //被添加进WindowManager了,但是这个时候,还是不可见的 + wm.addView(decor, l); + } - if (!r.activity.mFinished && willBeVisible + if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { - //在这里,执行了重要的操作,使得DecorView可见 - if (r.activity.mVisibleFromClient) { - r.activity.makeVisible(); - } - } + //在这里,执行了重要的操作,使得DecorView可见 + if (r.activity.mVisibleFromClient) { + r.activity.makeVisible(); + } } + } + } +} ``` 当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的。 @@ -287,12 +298,12 @@ final void handleResumeActivity(IBinder token, ```java void makeVisible() { if (!mWindowAdded) { - ViewManager wm = getWindowManager(); - wm.addView(mDecor, getWindow().getAttributes());//将DecorView添加到WindowManager - mWindowAdded = true; - } - mDecor.setVisibility(View.VISIBLE);//DecorView可见 + ViewManager wm = getWindowManager(); + wm.addView(mDecor, getWindow().getAttributes());//将DecorView添加到WindowManager + mWindowAdded = true; } + mDecor.setVisibility(View.VISIBLE);//DecorView可见 +} ``` 到此DecorView便可见,显示在屏幕中。但是在这其中,`wm.addView(mDecor, getWindow().getAttributes());`起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。 @@ -314,31 +325,33 @@ public final class WindowManagerImpl implements WindowManager { ```java public void addView(View view, ViewGroup.LayoutParams params, - Display display, Window parentWindow) { + Display display, Window parentWindow) { - final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; - ...... - synchronized (mLock) { + final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; - ViewRootImpl root; - //实例化一个ViewRootImpl对象 - root = new ViewRootImpl(view.getContext(), display); - view.setLayoutParams(wparams); + ...... - mViews.add(view); - mRoots.add(root); - mParams.add(wparams); - } - ...... + synchronized (mLock) { - try { - //将DecorView交给ViewRootImpl - root.setView(view, wparams, panelParentView); - }catch (RuntimeException e) { - } + ViewRootImpl root; + //实例化一个ViewRootImpl对象 + root = new ViewRootImpl(view.getContext(), display); + view.setLayoutParams(wparams); - } - } + mViews.add(view); + mRoots.add(root); + mParams.add(wparams); + } + + ...... + + try { + //将DecorView交给ViewRootImpl + root.setView(view, wparams, panelParentView); + } catch (RuntimeException e) { + + } +} ``` 看到其中实例化了ViewRootImpl对象,然后调用其setView()方法。其中setView()方法经过一些列折腾,最终调用了performTraversals()方法,**然后依照下图流程层层调用,完成绘制,最终界面才显示出来。** diff --git a/android/basis/lrucache.md b/android/basis/lrucache.md index 6c58c676..fcc5f954 100644 --- a/android/basis/lrucache.md +++ b/android/basis/lrucache.md @@ -2,7 +2,7 @@ 一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想其添加缓存,这个时候就需要删除一些旧的缓存并添加新的缓存。 -因此LRU(Least Recently Used)缓存算法便应运而生,LRU是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。 +因此LRU(Least Recently Used)缓存算法便应运而生,LRU是最近最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些最近最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。 ## 二、LruCache的使用 @@ -17,20 +17,21 @@ LruCache是个泛型类,主要算法原理是把最近使用的对象用强引 LruCache的使用非常简单,我们就已图片缓存为例。 ```java - int maxMemory = (int) (Runtime.getRuntime().totalMemory()/1024); - int cacheSize = maxMemory/8; - mMemoryCache = new LruCache(cacheSize){ - @Override - protected int sizeOf(String key, Bitmap value) { - return value.getRowBytes()*value.getHeight()/1024; - } - }; +int maxMemory = (int) (Runtime.getRuntime().totalMemory() / 1024); +int cacheSize = maxMemory / 8; +mMemoryCache = new LruCache(cacheSize) { + @Override + protected int sizeOf(String key, Bitmap value) { + return value.getRowBytes() * value.getHeight() / 1024; + } +}; ``` ①设置LruCache缓存的大小,一般为当前进程可用容量的1/8。 +
②重写sizeOf方法,计算出要缓存的每张图片的大小。 -**注意:**缓存的总容量和每个缓存对象的大小所用单位要一致。 +**注意:** 缓存的总容量和每个缓存对象的大小所用单位要一致。 ### 三、LruCache的实现原理 @@ -42,17 +43,17 @@ LruCache的核心思想很好理解,就是要维护一个缓存对象列表, 那么这个队列到底是由谁来维护的,前面已经介绍了是由LinkedHashMap来维护。 -而LinkedHashMap是由数组+双向链表的数据结构来实现的。其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的对按照一定顺序排列起来。 +而LinkedHashMap是由数组+双向链表的数据结构来实现的。其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的对按照一定顺序排列起来。 通过下面构造函数来指定LinkedHashMap中双向链表的结构是访问顺序还是插入顺序。 ```java public LinkedHashMap(int initialCapacity, - float loadFactor, - boolean accessOrder) { - super(initialCapacity, loadFactor); - this.accessOrder = accessOrder; - } + float loadFactor, + boolean accessOrder) { + super(initialCapacity, loadFactor); + this.accessOrder = accessOrder; +} ``` 其中accessOrder设置为true则为访问顺序,为false,则为插入顺序。 @@ -62,32 +63,32 @@ public LinkedHashMap(int initialCapacity, ```java public static final void main(String[] args) { - LinkedHashMap map = new LinkedHashMap<>(0, 0.75f, true); - map.put(0, 0); - map.put(1, 1); - map.put(2, 2); - map.put(3, 3); - map.put(4, 4); - map.put(5, 5); - map.put(6, 6); - map.get(1); - map.get(2); - - for (Map.Entry entry : map.entrySet()) { - System.out.println(entry.getKey() + ":" + entry.getValue()); - - } - } + LinkedHashMap map = new LinkedHashMap<>(0, 0.75f, true); + map.put(0, 0); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + map.put(4, 4); + map.put(5, 5); + map.put(6, 6); + map.get(1); + map.get(2); + + for (Map.Entry entry : map.entrySet()) { + System.out.println(entry.getKey() + ":" + entry.getValue()); + + } +} ``` 输出结果: -> 0:0 -> 3:3 -> 4:4 -> 5:5 -> 6:6 -> 1:1 +> 0:0
+> 3:3
+> 4:4
+> 5:5
+> 6:6
+> 1:1
> 2:2 即最近访问的最后输出,那么这就正好满足的LRU缓存算法的思想。**可见LruCache巧妙实现,就是利用了LinkedHashMap的这种数据结构。** @@ -95,13 +96,13 @@ public static final void main(String[] args) { 下面我们在LruCache源码中具体看看,怎么应用LinkedHashMap来实现缓存的添加,获得和删除的。 ```java - public LruCache(int maxSize) { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize <= 0"); - } - this.maxSize = maxSize; - this.map = new LinkedHashMap(0, 0.75f, true); - } +public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap(0, 0.75f, true); +} ``` 从LruCache的构造函数中可以看到正是用了LinkedHashMap的访问顺序。 @@ -111,30 +112,30 @@ public static final void main(String[] args) { ```java public final V put(K key, V value) { //不可为空,否则抛出异常 - if (key == null || value == null) { - throw new NullPointerException("key == null || value == null"); - } - V previous; - synchronized (this) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + V previous; + synchronized (this) { //插入的缓存对象值加1 - putCount++; + putCount++; //增加已有缓存的大小 - size += safeSizeOf(key, value); + size += safeSizeOf(key, value); //向map中加入缓存对象 - previous = map.put(key, value); + previous = map.put(key, value); //如果已有缓存对象,则缓存大小恢复到之前 - if (previous != null) { - size -= safeSizeOf(key, previous); - } - } + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } //entryRemoved()是个空方法,可以自行实现 - if (previous != null) { - entryRemoved(false, key, previous, value); - } + if (previous != null) { + entryRemoved(false, key, previous, value); + } //调整缓存大小(关键方法) - trimToSize(maxSize); - return previous; - } + trimToSize(maxSize); + return previous; +} ``` 可以看到put()方法并没有什么难点,重要的就是在添加过缓存对象后,调用 trimToSize()方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法。 @@ -142,33 +143,33 @@ public final V put(K key, V value) { **trimToSize()方法** ```java - public void trimToSize(int maxSize) { - //死循环 - while (true) { - K key; - V value; - synchronized (this) { - //如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常 - if (size < 0 || (map.isEmpty() && size != 0)) { - throw new IllegalStateException(getClass().getName() - + ".sizeOf() is reporting inconsistent results!"); - } - //如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环 - if (size <= maxSize || map.isEmpty()) { - break; - } - //迭代器获取第一个对象,即队尾的元素,近期最少访问的元素 - Map.Entry toEvict = map.entrySet().iterator().next(); - key = toEvict.getKey(); - value = toEvict.getValue(); - //删除该对象,并更新缓存大小 - map.remove(key); - size -= safeSizeOf(key, value); - evictionCount++; - } - entryRemoved(true, key, value, null); - } - } +public void trimToSize(int maxSize) { + //死循环 + while (true) { + K key; + V value; + synchronized (this) { + //如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常 + if (size < 0 || (map.isEmpty() && size != 0)) { + throw new IllegalStateException(getClass().getName() + + ".sizeOf() is reporting inconsistent results!"); + } + //如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环 + if (size <= maxSize || map.isEmpty()) { + break; + } + //迭代器获取第一个对象,即队尾的元素,近期最少访问的元素 + Map.Entry toEvict = map.entrySet().iterator().next(); + key = toEvict.getKey(); + value = toEvict.getValue(); + //删除该对象,并更新缓存大小 + map.remove(key); + size -= safeSizeOf(key, value); + evictionCount++; + } + entryRemoved(true, key, value, null); + } +} ``` trimToSize()方法不断地删除LinkedHashMap中队尾的元素,即近期最少访问的,直到缓存大小小于最大值。 @@ -182,50 +183,50 @@ trimToSize()方法不断地删除LinkedHashMap中队尾的元素,即近期最 ```java public final V get(K key) { //key为空抛出异常 - if (key == null) { - throw new NullPointerException("key == null"); - } + if (key == null) { + throw new NullPointerException("key == null"); + } - V mapValue; - synchronized (this) { + V mapValue; + synchronized (this) { //获取对应的缓存对象 //get()方法会实现将访问的元素更新到队列头部的功能 - mapValue = map.get(key); - if (mapValue != null) { - hitCount++; - return mapValue; - } - missCount++; - } + mapValue = map.get(key); + if (mapValue != null) { + hitCount++; + return mapValue; + } + missCount++; + } ``` 其中LinkedHashMap的get()方法如下: ```Java public V get(Object key) { - LinkedHashMapEntry e = (LinkedHashMapEntry)getEntry(key); - if (e == null) - return null; - //实现排序的关键方法 - e.recordAccess(this); - return e.value; - } + LinkedHashMapEntry e = (LinkedHashMapEntry)getEntry(key); + if (e == null) + return null; + //实现排序的关键方法 + e.recordAccess(this); + return e.value; +} ``` 调用recordAccess()方法如下: ```Java - void recordAccess(HashMap m) { - LinkedHashMap lm = (LinkedHashMap)m; - //判断是否是访问排序 - if (lm.accessOrder) { - lm.modCount++; - //删除此元素 - remove(); - //将此元素移动到队列的头部 - addBefore(lm.header); - } - } +void recordAccess(HashMap m) { + LinkedHashMap lm = (LinkedHashMap)m; + //判断是否是访问排序 + if (lm.accessOrder) { + lm.modCount++; + //删除此元素 + remove(); + //将此元素移动到队列的头部 + addBefore(lm.header); + } +} ``` **由此可见LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。** \ No newline at end of file diff --git a/computer-networks/http.md b/computer-networks/http.md index 1f14fcca..92e1c8fb 100644 --- a/computer-networks/http.md +++ b/computer-networks/http.md @@ -37,7 +37,8 @@ IGMP是Internet组管理协议。它用来把一个UDP数据报多播到多个 TCP为两台主机提供高可靠性的数据通信。它所做的工作包括把应用程序交给它的数据分成合适的小块交给下面的网络层,确认接收到的分组,设置发送最后确认分组的超时时钟等。由于运输层提供了高可靠性的端到端的通信,因此应用层可以忽略所有这些细节。为了提供可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。 -UDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。一个数据报是指从发送方传输到接收方的一个信息单元(例如,发送方指定的一定字节数的信息)。UDP协议任何必需的可靠性必须由应用层来提供。 +UDP则为应用层提供一种非常简单的服务。它只是把称作数据报的分组从一台主机发送到另一台主机,但并不保证该数据报能到达另一端。一个数据报是指从发送方传输到接收方的一个信息单元(例如,发送方指定的一定字节数的信息)。UDP协议任何必需的可靠性必须由应用层来提供。 + \(4\). 应用层 应用层决定了向用户提供应用服务时通信的活动。TCP/IP 协议族内预存了各类通用的应用服务。包括 HTTP,FTP(File Transfer Protocol,文件传输协议),DNS(Domain Name System,域名系统)服务。 @@ -77,20 +78,21 @@ TCP是面向连接的,无论哪一方向另一方发送数据之前,都必 ![](http://upload-images.jianshu.io/upload_images/3985563-f2fe3775bd2678c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - - -第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN\_SEND状态,等待服务器的确认; - -第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1\(Sequence Number+1\);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN\_RECV状态; - -第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。 +| 回合 | 说明 | +| ---- | ------------------ | +| 第一次握手 | 建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN\_SEND状态,等待服务器的确认; | +| 第二次握手 | 服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x + 1\(Sequence Number + 1\);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN + ACK报文段)中,一并发送给客户端,此时服务器进入SYN\_RECV状态; | +| 第三次握手 | 客户端收到服务器的SYN + ACK报文段。然后将Acknowledgment Number设置为y + 1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。 | ##### 为什么要三次握手 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。 -**具体例子:**“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。” +**具体例子:** ——“已失效的连接请求报文段”的产生在这样一种情况下: + +client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。 + +例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。” ### 四、HTTP协议 @@ -100,20 +102,19 @@ TCP是面向连接的,无论哪一方向另一方发送数据之前,都必 **四个基于:** -**请求与响应:**客户端发送请求,服务器端响应数据 +**请求与响应:** 客户端发送请求,服务器端响应数据 -**无状态的:**协议对于事务处理没有记忆能力,客户端第一次与服务器建立连接发送请求时需要进行一系列的安全认证匹配等,因此增加页面等待时间,当客户端向服务器端发送请求,服务器端响应完毕后,两者断开连接,也不保存连接状态,一刀两断!恩断义绝!从此路人!下一次客户端向同样的服务器发送请求时,由于他们之前已经遗忘了彼此,所以需要重新建立连接。 +**无状态的:** 协议对于事务处理没有记忆能力,客户端第一次与服务器建立连接发送请求时需要进行一系列的安全认证匹配等,因此增加页面等待时间,当客户端向服务器端发送请求,服务器端响应完毕后,两者断开连接,也不保存连接状态,一刀两断!恩断义绝!从此路人!下一次客户端向同样的服务器发送请求时,由于他们之前已经遗忘了彼此,所以需要重新建立连接。 -**应用层:**Http是属于应用层的协议,配合TCP/IP使用。 +**应用层:** Http是属于应用层的协议,配合TCP/IP使用。 -**TCP/IP:**Http使用TCP作为它的支撑运输协议。HTTP客户机发起一个与服务器的TCP连接,一旦连接建立,浏览器(客户机)和服务器进程就可以通过套接字接口访问TCP。 +**TCP/IP:** Http使用TCP作为它的支撑运输协议。HTTP客户机发起一个与服务器的TCP连接,一旦连接建立,浏览器(客户机)和服务器进程就可以通过套接字接口访问TCP。 **针对无状态的一些解决策略:** 有时需要对用户之前的HTTP通信状态进行保存,比如执行一次登陆操作,在30分钟内所有的请求都不需要再次登陆。于是引入了Cookie技术。 -HTTP/1.1想出了持久连接(HTTP keep-alive)方法。其特点是,只要任意一端没有明确提出断开连接,则保持TCP连接状态,在请求首部字段中的Connection: keep-alive即为表明使用了持久连接。 -等等还有很多。。。。。。 +HTTP/1.1想出了持久连接(HTTP keep-alive)方法。其特点是,只要任意一端没有明确提出断开连接,则保持TCP连接状态,在请求首部字段中的Connection: keep-alive即为表明使用了持久连接。等等还有很多…… 下面开始讲解重头戏:HTTP请求报文,响应报文,对应于上述步骤的2,3,4,5,6。 @@ -141,7 +142,7 @@ HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD **请求地址** -URL:统一资源定位符,是一种自愿位置的抽象唯一识别方法。 +URL:统一资源定位符,是一种自愿位置的抽象唯一识别方法。 组成:<协议>://<主机>:<端口>/<路径> @@ -214,12 +215,13 @@ HTTP响应报文主要由状态行、响应头部、空行以及响应数据组 **状态码** -状态代码为3位数字。 -1xx:指示信息--表示请求已接收,继续处理。 -2xx:成功--表示请求已被成功接收、理解、接受。 -3xx:重定向--要完成请求必须进行更进一步的操作。 -4xx:客户端错误--请求有语法错误或请求无法实现。 -5xx:服务器端错误--服务器未能实现合法的请求。 +状态代码为3位数字。 + + - 1xx:指示信息——表示请求已接收,继续处理。 + - 2xx:成功——表示请求已被成功接收、理解、接受。 + - 3xx:重定向——要完成请求必须进行更进一步的操作。 + - 4xx:客户端错误——请求有语法错误或请求无法实现。 + - 5xx:服务器端错误——服务器未能实现合法的请求。 下面列举几个常见的: @@ -274,22 +276,18 @@ HTTP响应报文主要由状态行、响应头部、空行以及响应数据组 ### 七、TCP四次挥手 -当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那对于TCP的断开连接,这里就有了神秘的“四次分手”。 +当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那对于TCP的断开连接,这里就有了神秘的“四次分手”。 ![](http://upload-images.jianshu.io/upload_images/3985563-c1c59148f8b26c43.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - - -第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number,向主机2发送一个FIN报文段;此时,主机1进入FIN\_WAIT\_1状态;这表示主机1没有数据要发送给主机2了; - -第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN\_WAIT\_2状态;主机2告诉主机1,我“同意”你的关闭请求; - -第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST\_ACK状态; - -第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME\_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。 +| 回合 | 说明 | +| ------ | ------------------ | +| 第一次分手 | 主机1(可以使客户端,也可以是服务器端),设置Sequence Number,向主机2发送一个FIN报文段;此时,主机1进入FIN\_WAIT\_1状态;这表示主机1没有数据要发送给主机2了; | +| 第二次分手 | 主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN\_WAIT\_2状态;主机2告诉主机1,我“同意”你的关闭请求; | +| 第三次分手 | 主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST\_ACK状态; | +| 第四次分手 | 主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME\_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。 | ##### 为什么要四次分手 diff --git a/computer-networks/tcpip.md b/computer-networks/tcpip.md index b2d3af5f..70f2ed0a 100644 --- a/computer-networks/tcpip.md +++ b/computer-networks/tcpip.md @@ -17,7 +17,7 @@ TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含 -TCP/IP协议族按照层次由上到下,层层包装。最上面的是应用层,这里面有http,ftp,等等我们熟悉的协议。而第二层则是传输层,著名的TCP和UDP协议就在这个层次。第三层是网络层,IP协议就在这里,它负责对数据加上IP地址和其他的数据以确定传输的目标。第四层是数据链路层,这个层次为待传送的数据加入一个以太网协议头,并进行CRC编码,为最后的数据传输做准备。 +TCP/IP协议族按照层次由上到下,层层包装。最上面的是应用层,这里面有http,ftp,等等我们熟悉的协议。而第二层则是传输层,著名的TCP和UDP协议就在这个层次。第三层是网络层,IP协议就在这里,它负责对数据加上IP地址和其他的数据以确定传输的目标。第四层是数据链路层,这个层次为待传送的数据加入一个以太网协议头,并进行CRC编码,为最后的数据传输做准备。 @@ -47,7 +47,7 @@ TCP/IP协议族按照层次由上到下,层层包装。最上面的是应用 ### 二、数据链路层 -物理层负责0、1比特流与物理设备电压高低、光的闪灭之间的互换。 数据链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的\(MAC,物理地址,一个主机会有一个MAC地址\)。 +物理层负责0、1比特流与物理设备电压高低、光的闪灭之间的互换。数据链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的\(MAC,物理地址,一个主机会有一个MAC地址\)。 @@ -57,16 +57,16 @@ TCP/IP协议族按照层次由上到下,层层包装。最上面的是应用 -* 封装成帧: 把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。 -* 透明传输:零比特填充、转义字符。 -* 可靠传输: 在出错率很低的链路上很少用,但是无线链路WLAN会保证可靠传输。 -* 差错检测\(CRC\):接收者检测错误,如果发现差错,丢弃该帧。 +* 封装成帧:把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。 +* 透明传输:零比特填充、转义字符。 +* 可靠传输:在出错率很低的链路上很少用,但是无线链路WLAN会保证可靠传输。 +* 差错检测\(CRC\):接收者检测错误,如果发现差错,丢弃该帧。 ### 三、网络层 #### 1.IP协议 -IP协议是TCP/IP协议的核心,所有的TCP,UDP,ICMP,IGCP的数据都以IP数据格式传输。要注意的是,IP不是可靠的协议,这是说,IP协议没有提供一种数据未传达以后的处理机制,这被认为是上层协议:TCP或UDP要做的事情。 +IP协议是TCP/IP协议的核心,所有的TCP,UDP,ICMP,IGCP的数据都以IP数据格式传输。要注意的是,IP不是可靠的协议,这是说,IP协议没有提供一种数据未传达以后的处理机制,这被认为是上层协议——TCP或UDP要做的事情。 ##### 1.1 IP地址 @@ -74,9 +74,9 @@ IP协议是TCP/IP协议的核心,所有的TCP,UDP,ICMP,IGCP的数据都 32位IP地址分为网络位和地址位,这样做可以减少路由器中路由表记录的数目,有了网络地址,就可以限定拥有相同网络地址的终端都在同一个范围内,那么路由表只需要维护一条这个网络地址的方向,就可以找到相应的这些终端了。 -A类IP地址: 1.0.0.0~127.0.0.0 -B类IP地址:128.0.0.0~191.255.255.255 -C类IP地址:192.0.0.0~223.255.255.255 + - A类IP地址:1.0.0.0~127.0.0.0 + - B类IP地址:128.0.0.0~191.255.255.255 + - C类IP地址:192.0.0.0~223.255.255.255 ##### 1.2 IP协议头 @@ -88,7 +88,8 @@ C类IP地址:192.0.0.0~223.255.255.255 -这里只介绍:八位的TTL字段。这个字段规定该数据包在穿过多少个路由之后才会被抛弃。某个IP数据包每穿过一个路由器,该数据包的TTL数值就会减少1,当该数据包的TTL成为零,它就会被自动抛弃。 +这里只介绍:八位的TTL(Time To Live)字段。这个字段规定该数据包在穿过多少个路由之后才会被抛弃。某个IP数据包每穿过一个路由器,该数据包的TTL数值就会减少1,当该数据包的TTL成为零,它就会被自动抛弃。 + 这个字段的最大值也就是255,也就是说一个协议包也就在路由器里面穿行255次就会被抛弃了,根据系统的不同,这个数字也不一样,一般是32或者是64。 #### 2.ARP及RARP协议 @@ -97,7 +98,7 @@ ARP 是根据IP地址获取MAC地址的一种协议。 ARP(地址解析)协议是一种解析协议,本来主机是完全不知道这个IP对应的是哪个主机的哪个接口,当主机要发送一个IP包的时候,会首先查一下自己的ARP高速缓存(就是一个IP-MAC地址对应表缓存)。 -如果查询的IP-MAC值对不存在,那么主机就向网络发送一个ARP协议广播包,这个广播包里面就有待查询的IP地址,而直接收到这份广播的包的所有主机都会查询自己的IP地址,如果收到广播包的某一个主机发现自己符合条件,那么就准备好一个包含自己的MAC地址的ARP包传送给发送ARP广播的主机。 +如果查询的IP-MAC值对不存在,那么主机就向网络发送一个ARP协议广播包,这个广播包里面就有待查询的IP地址,而直接收到这份广播的包的所有主机都会查询自己的IP地址。如果收到广播包的某一个主机发现自己符合条件,那么就准备好一个包含自己的MAC地址的ARP包传送给发送ARP广播的主机。 而广播主机拿到ARP包后会更新自己的ARP缓存(就是存放IP-MAC对应表的地方)。发送广播的主机就会用新的ARP缓存数据准备好数据链路层的的数据包发送工作。 @@ -134,7 +135,7 @@ ping程序来计算间隔时间,并计算有多少个包被送达。用户就 Traceroute是用来侦测主机到目的主机之间所经路由情况的重要工具,也是最便利的工具。 -Traceroute的原理是非常非常的有意思,它收到到目的主机的IP后,首先给目的主机发送一个TTL=1的UDP数据包,而经过的第一个路由器收到这个数据包以后,就自动把TTL减1,而TTL变为0以后,路由器就把这个包给抛弃了,并同时产生 一个主机不可达的ICMP数据报给主机。主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机,然后刺激第二个路由器给主机发ICMP数据 报。如此往复直到到达目的主机。这样,traceroute就拿到了所有的路由器IP。 +Traceroute的原理非常非常有意思。它收到到目的主机的IP后,首先给目的主机发送一个TTL=1(Time To Live)的UDP数据包,而经过的第一个路由器收到这个数据包以后,就自动把TTL减1,而TTL变为0以后,路由器就把这个包给抛弃了,并同时产生一个主机不可达的ICMP数据报给主机。主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机,然后刺激第二个路由器给主机发ICMP数据报。如此往复直到到达目的主机。这样,traceroute就拿到了所有的路由器IP。 @@ -158,7 +159,7 @@ TCP/UDP都是是传输层协议,但是两者具有不同的特性,同时也 ##### 面向报文 -面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。 +面向报文的传输方式是应用层交给UDP多长的报文,UDP发送多长的报文,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率;若太短,会使IP数据报太小。 ##### 面向字节流 @@ -168,12 +169,9 @@ TCP/UDP都是是传输层协议,但是两者具有不同的特性,同时也 TCP和UDP协议的一些应用 - - ![](http://upload-images.jianshu.io/upload_images/3985563-d68974689d48cee9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - #### 什么时候应该使用TCP? @@ -199,20 +197,21 @@ TCP是面向连接的,无论哪一方向另一方发送数据之前,都必 ![](http://upload-images.jianshu.io/upload_images/3985563-cd5a153e44696643.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - - -**第一次握手:** 建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN\_SEND状态,等待服务器的确认; - -**第二次握手:** 服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1\(Sequence Number+1\);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN\_RECV状态; - -**第三次握手:** 客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。 +| 回合 | 说明 | +| ------ | ---------- | +| **第一次握手** | 建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN\_SEND状态,等待服务器的确认; | +| **第二次握手** | 服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x + 1\(Sequence Number + 1\);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN\_RECV状态; | +| **第三次握手** | 客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y + 1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。 | ##### 为什么要三次握手? 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。 -具体例子:“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。” +具体例子——“已失效的连接请求报文段”的产生在这样一种情况下: + +client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。 + +例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。” #### 2.四次挥手 @@ -221,19 +220,12 @@ TCP是面向连接的,无论哪一方向另一方发送数据之前,都必 ![](http://upload-images.jianshu.io/upload_images/3985563-6fdf680296f3acd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - - - - - -**第一次分手:** 主机1(可以使客户端,也可以是服务器端),设置Sequence Number,向主机2发送一个FIN报文段;此时,主机1进入FIN\_WAIT\_1状态;这表示主机1没有数据要发送给主机2了; - -**第二次分手:** 主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN\_WAIT\_2状态;主机2告诉主机1,我“同意”你的关闭请求; - -**第三次分手:** 主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST\_ACK状态; - -**第四次分手** 主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME\_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。 +| 回合 | 说明 | +| ----- | ------------- | +| **第一次分手** | 主机1(可以使客户端,也可以是服务器端),设置Sequence Number,向主机2发送一个FIN报文段;此时,主机1进入FIN\_WAIT\_1状态;这表示主机1没有数据要发送给主机2了; | +| **第二次分手** | 主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为 Sequence Number加1;主机1进入FIN\_WAIT\_2状态;主机2告诉主机1,我“同意”你的关闭请求; | +| **第三次分手** | 主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST\_ACK状态; | +| **第四次分手** | 主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME\_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL(MSL:Maximum Segment Lifetime,报文段最大生存时间,详情请见下文介绍)后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。 | ##### 为什么要四次分手? @@ -247,9 +239,9 @@ MSL:报文段最大生存时间,它是任何报文段被丢弃前在网络 * 保证TCP协议的全双工连接能够可靠关闭 * 保证这次连接的重复数据段从网络中消失 -第一点:如果主机1直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致主机2没有收到主机1最后回复的ACK。那么主机2就会在超时之后继续发送FIN,此时由于主机1已经CLOSED了,就找不到与重发的FIN对应的连接。所以,主机1不是直接进入CLOSED,而是要保持TIME\_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。 +第一点:如果主机1直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致主机2没有收到主机1最后回复的ACK。那么主机2就会在超时之后继续发送FIN,此时由于主机1已经CLOSED了,就找不到与重发的FIN对应的连接。所以,主机1不是直接进入CLOSED,而是要保持TIME\_WAIT状态。当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。 -第二点:如果主机1直接CLOSED,然后又再向主机2发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达主机2,由于新连接和老连接的端口号是一样的,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME\_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。 +第二点:如果主机1直接CLOSED,然后又再向主机2发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,**如果前一次连接的某些数据仍然滞留在网络中**(称为Lost Duplicate),这些延迟数据在建立新连接之后才到达主机2,由于新连接和老连接的端口号是一样的,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接要在TIME\_WAIT状态等待2倍MSL,保证本次连接的所有数据都从网络中消失。 ### 九、TCP流量控制 @@ -257,7 +249,7 @@ MSL:报文段最大生存时间,它是任何报文段被丢弃前在网络 利用**滑动窗口机制**可以很方便地在TCP连接上实现对发送方的流量控制。 -设A向B发送数据。在连接建立时,B告诉了A:“我的接收窗口是 rwnd = 400 ”\(这里的 rwnd 表示 receiver window\) 。因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值。请注意,TCP的窗口单位是字节,不是报文段。假设每一个报文段为100字节长,而数据报文段序号的初始值设为1。大写ACK表示首部中的确认位ACK,小写ack表示确认字段的值ack。 +设A向B发送数据。在连接建立时,B告诉了A:“我的接收窗口是 rwnd = 400 ”\(这里的 rwnd 表示 receiver window\) 。因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值。请注意,TCP的窗口单位是字节,不是报文段。假设每一个报文段为100字节长,而数据报文段序号的初始值设为1。**大写ACK表示首部中的确认位ACK,小写ack表示确认字段的值ack。** @@ -272,7 +264,7 @@ MSL:报文段最大生存时间,它是任何报文段被丢弃前在网络 从图中可以看出,B进行了三次流量控制。第一次把窗口减少到 rwnd = 300 ,第二次又减到了 rwnd = 100 ,最后减到 rwnd = 0 ,即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。B向A发送的三个报文段都设置了 ACK = 1 ,只有在ACK=1时确认号字段才有意义。 -TCP为每一个连接设有一个持续计时器\(persistence timer\)。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口控测报文段(携1字节的数据),那么收到这个报文段的一方就重新设置持续计时器。 +TCP为每一个连接设有一个持续计时器\(persistence timer\)。当TCP连接中的发送方收到接收方的零窗口通知时,发送方就启动持续计时器。若持续计时器设置的时间到期,发送方就发送一个零窗口控测报文段(携1字节的数据)给接收方。如果接收方可以接收数据,就重新开始发送数据;如果接收方不能接收数据,就重新设置持续计时器。 ### 十、TCP拥塞控制 @@ -284,10 +276,11 @@ TCP为每一个连接设有一个持续计时器\(persistence timer\)。只要TC ##### 慢开始算法: -当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。 -因此,较好的方法是 先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。 +当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。 + +因此,较好的方法是,先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。 -通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。 +通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS(Maximum Segment Size,最大报文长度)的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值(底数为2的指数增长规律)。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。 ![](http://upload-images.jianshu.io/upload_images/3985563-470235b1e99d8111.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) @@ -318,7 +311,7 @@ TCP为每一个连接设有一个持续计时器\(persistence timer\)。只要TC ##### 拥塞避免 -让拥塞窗口cwnd缓慢地增大,即每经过**一个往返时间RTT**就把发送方的**拥塞窗口cwnd加1,而不是加倍**。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。 +让拥塞窗口cwnd缓慢地增大,即每经过**一个往返时间RTT**就把发送方的**拥塞窗口cwnd加1,而不是加倍**。这样拥塞窗口cwnd按线性增长规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。 ![](http://upload-images.jianshu.io/upload_images/3985563-8bbeee141535e125.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) @@ -361,7 +354,7 @@ TCP为每一个连接设有一个持续计时器\(persistence timer\)。只要TC 显然,接收方不能确认M4,因为M4是收到的失序报文段。根据 可靠传输原理,接收方可以什么都不做,也可以在适当时机发送一次对M2的确认。 -但按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了 接收方的四个对M2的确认,其中后三个都是重复确认。 +但按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样做可以让发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了接收方的四个对M2的确认,其中后三个都是重复确认。 **快重传算法还规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3,而不必 继续等待M3设置的重传计时器到期。** diff --git a/data-structure/tree/b-tree.md b/data-structure/tree/b-tree.md index 5d9d385e..7d47bf0b 100644 --- a/data-structure/tree/b-tree.md +++ b/data-structure/tree/b-tree.md @@ -8,7 +8,7 @@ ## 二、B树(B-树) -为方便描述,下面一律用B数这个名称。B树是一种多路平衡搜索树(非二叉),若其是M路,则: +为方便描述,下面一律用B树这个名称。B树是一种多路平衡搜索树(非二叉),若其是M路,则: 1. 任意非叶子节点最多可以有M个子女,且M>2; 2. 根节点的子女数为[2,M]; @@ -83,4 +83,4 @@ B*树又是B+树的变种,其与B+树的区别有: 1. B*树在B+树的非根和非叶子节点再增加指向兄弟节点的指针 2. B*树规定非叶子节点的键值个数至少为(2/3)*M,这样每个节点的使用率就从B+树的1/2上升到2/3,所以空间使用率更高。 -B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;B*树分配新结点的概率比B+树要低,空间使用率更高; \ No newline at end of file +B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;B*树分配新结点的概率比B+树要低,空间使用率更高; diff --git a/design-mode/Builder-Pattern/Abstract-Factory.md b/design-mode/Builder-Pattern/Abstract-Factory.md index 0e483d41..136d6363 100644 --- a/design-mode/Builder-Pattern/Abstract-Factory.md +++ b/design-mode/Builder-Pattern/Abstract-Factory.md @@ -12,9 +12,9 @@ 为了更清晰地理解工厂方法模式,需要先引入两个概念: -**产品等级结构 :**产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。 +**产品等级结构:** 产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。 -**产品族 :**在抽象工厂模式中,**产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品**,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。 +**产品族:** 在抽象工厂模式中,**产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品**,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。 **当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。** @@ -59,136 +59,133 @@ Product:具体产品 **抽象产品: 苹果系列** ```java - public interface Apple - { - void AppleStyle(); - } +public interface Apple +{ + void AppleStyle(); +} ``` **抽象产品: 三星系列** ```java public interface Sumsung - { - void BangziStyle(); - } +{ + void BangziStyle(); +} ``` **具体产品:iphone** ```java - public class iphone implements Apple - { - public void AppleStyle() - { - Console.WriteLine("Apple's style: iPhone!"); - } - } +public class iphone implements Apple +{ + public void AppleStyle() + { + Console.WriteLine("Apple's style: iPhone!"); + } +} ``` **具体产品:ipad** ```java - public class ipad implements Apple +public class ipad implements Apple +{ + public void AppleStyle() { - - public void AppleStyle() - { - Console.WriteLine("Apple's style: iPad!"); - } - - } + Console.WriteLine("Apple's style: iPad!"); + } +} ``` **具体产品:note2** ```java - public class note2 implements Sumsung - { - public void BangziStyle() - { - Console.WriteLine("Bangzi's style : Note2!"); - } - - } +public class note2 implements Sumsung +{ + public void BangziStyle() + { + Console.WriteLine("Bangzi's style : Note2!"); + } +} ``` **具体产品:tabs** ```java public class Tabs implements Sumsung - { - public void BangziStyle() - { - Console.WriteLine("Bangzi's style : Tab!"); - } - } +{ + public void BangziStyle() + { + Console.WriteLine("Bangzi's style : Tab!"); + } +} ``` **抽象工厂** ```java - public interface Factory - { - Apple createAppleProduct(); - Sumsung createSumsungProduct(); - } +public interface Factory +{ + Apple createAppleProduct(); + Sumsung createSumsungProduct(); +} ``` **手机工厂** ```java public class Factory_Phone implements Factory - { - public Apple createAppleProduct() - { - return new iphone(); - } - - public Sumsung createSumsungProduct() - { - return new note2(); - } - } +{ + public Apple createAppleProduct() + { + return new iphone(); + } + + public Sumsung createSumsungProduct() + { + return new note2(); + } +} ``` **pad工厂** ```java public class Factory_Pad implements Factory - { - public Apple createAppleProduct() - { - return new ipad(); - } - - public Sumsung createSumsungProduct() - { - return new Tabs(); - } - } +{ + public Apple createAppleProduct() + { + return new ipad(); + } + + public Sumsung createSumsungProduct() + { + return new Tabs(); + } +} ``` **客户端调用** ```java public static void Main(string[] args) - { - //采购商要一台iPad和一台Tab - Factory factory = new Factory_Pad(); - Apple apple = factory.createAppleProduct(); - apple.AppleStyle(); - Sumsung sumsung = factory.createSumsungProduct(); - sumsung.BangziStyle(); - - //采购商又要一台iPhone和一台Note2 - factory = new Factory_Phone(); - apple = factory.createAppleProduct(); - apple.AppleStyle(); - sumsung = factory.createSumsungProduct(); - sumsung.BangziStyle(); - Console.ReadKey(); - } +{ + //采购商要一台iPad和一台Tab + Factory factory = new Factory_Pad(); + Apple apple = factory.createAppleProduct(); + apple.AppleStyle(); + Sumsung sumsung = factory.createSumsungProduct(); + sumsung.BangziStyle(); + + //采购商又要一台iPhone和一台Note2 + factory = new Factory_Phone(); + apple = factory.createAppleProduct(); + apple.AppleStyle(); + sumsung = factory.createSumsungProduct(); + sumsung.BangziStyle(); + Console.ReadKey(); +} ``` 抽象工厂可以通过多态,来动态设置不同的工厂,生产不同的产品,同时每个工厂中的产品又不属于同一个产品等级结构。 diff --git a/design-mode/Builder-Pattern/Builder-Pattern.md b/design-mode/Builder-Pattern/Builder-Pattern.md index 14122d48..5533258d 100644 --- a/design-mode/Builder-Pattern/Builder-Pattern.md +++ b/design-mode/Builder-Pattern/Builder-Pattern.md @@ -190,32 +190,32 @@ public class Test { 在Android源码中,我们最常用到的Builder模式就是AlertDialog.Builder, 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 : ```java - //显示基本的AlertDialog - private void showDialog(Context context) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setIcon(R.drawable.icon); - builder.setTitle("Title"); - builder.setMessage("Message"); - builder.setPositiveButton("Button1", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - setTitle("点击了对话框上的Button1"); - } - }); - builder.setNeutralButton("Button2", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - setTitle("点击了对话框上的Button2"); - } - }); - builder.setNegativeButton("Button3", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - setTitle("点击了对话框上的Button3"); - } - }); - builder.create().show(); // 构建AlertDialog, 并且显示 - } +//显示基本的AlertDialog +private void showDialog(Context context) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setIcon(R.drawable.icon); + builder.setTitle("Title"); + builder.setMessage("Message"); + builder.setPositiveButton("Button1", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + setTitle("点击了对话框上的Button1"); + } + }); + builder.setNeutralButton("Button2", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + setTitle("点击了对话框上的Button2"); + } + }); + builder.setNegativeButton("Button3", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + setTitle("点击了对话框上的Button3"); + } + }); + builder.create().show(); // 构建AlertDialog, 并且显示 +} ``` 结果 : [![result](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis/raw/master/builder/mr.simple/images/result.png)](https://github.com/simple-android-framework-exchange/android_design_patterns_analysis/blob/master/builder/mr.simple/images/result.png) @@ -334,55 +334,55 @@ public class AlertDialog extends Dialog implements DialogInterface { 可以看到,通过Builder来设置AlertDialog中的title, message, button等参数, 这些参数都存储在类型为AlertController.AlertParams的成员变量P中,AlertController.AlertParams中包含了与之对应的成员变量。在调用Builder类的create函数时才创建AlertDialog, 并且将Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码段。我们看看apply函数的实现 : ```java - public void apply(AlertController dialog) { - if (mCustomTitleView != null) { - dialog.setCustomTitle(mCustomTitleView); - } else { - if (mTitle != null) { - dialog.setTitle(mTitle); - } - if (mIcon != null) { - dialog.setIcon(mIcon); - } - if (mIconId >= 0) { - dialog.setIcon(mIconId); - } - if (mIconAttrId > 0) { - dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); - } - } - if (mMessage != null) { - dialog.setMessage(mMessage); - } - if (mPositiveButtonText != null) { - dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, - mPositiveButtonListener, null); - } - if (mNegativeButtonText != null) { - dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, - mNegativeButtonListener, null); - } - if (mNeutralButtonText != null) { - dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, - mNeutralButtonListener, null); - } - if (mForceInverseBackground) { - dialog.setInverseBackgroundForced(true); - } - // For a list, the client can either supply an array of items or an - // adapter or a cursor - if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { - createListView(dialog); - } - if (mView != null) { - if (mViewSpacingSpecified) { - dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, - mViewSpacingBottom); - } else { - dialog.setView(mView); - } - } +public void apply(AlertController dialog) { + if (mCustomTitleView != null) { + dialog.setCustomTitle(mCustomTitleView); + } else { + if (mTitle != null) { + dialog.setTitle(mTitle); + } + if (mIcon != null) { + dialog.setIcon(mIcon); + } + if (mIconId >= 0) { + dialog.setIcon(mIconId); + } + if (mIconAttrId > 0) { + dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); } + } + if (mMessage != null) { + dialog.setMessage(mMessage); + } + if (mPositiveButtonText != null) { + dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, + mPositiveButtonListener, null); + } + if (mNegativeButtonText != null) { + dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, + mNegativeButtonListener, null); + } + if (mNeutralButtonText != null) { + dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, + mNeutralButtonListener, null); + } + if (mForceInverseBackground) { + dialog.setInverseBackgroundForced(true); + } + // For a list, the client can either supply an array of items or an + // adapter or a cursor + if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { + createListView(dialog); + } + if (mView != null) { + if (mViewSpacingSpecified) { + dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, + mViewSpacingBottom); + } else { + dialog.setView(mView); + } + } +} ``` 实际上就是把P中的参数挨个的设置到AlertController中, 也就是AlertDialog中的mAlert对象。从AlertDialog的各个setter方法中我们也可以看到,实际上也都是调用了mAlert对应的setter方法。在这里,Builder同时扮演了上文中提到的builder、ConcreteBuilder、Director的角色,简化了Builder模式的设计。 diff --git a/design-mode/Builder-Pattern/Factory-Method.md b/design-mode/Builder-Pattern/Factory-Method.md index fcd3f05c..8a1a14ae 100644 --- a/design-mode/Builder-Pattern/Factory-Method.md +++ b/design-mode/Builder-Pattern/Factory-Method.md @@ -62,99 +62,99 @@ **抽象的产品接口ILight** ```java - public interface ILight - { - void TurnOn(); - void TurnOff(); - } +public interface ILight +{ + void TurnOn(); + void TurnOff(); +} ``` **具体的产品类:BulbLight** ```java - public class BulbLight implements ILight +public class BulbLight implements ILight +{ + public void TurnOn() + { + Console.WriteLine("BulbLight turns on."); + } + public void TurnOff() { - public void TurnOn() - { - Console.WriteLine("BulbLight turns on."); - } - public void TurnOff() - { - Console.WriteLine("BulbLight turns off."); - } + Console.WriteLine("BulbLight turns off."); } +} ``` **具体的产品类:TubeLight** ```java - public class TubeLight implements ILight +public class TubeLight implements ILight +{ + public void TurnOn() { - public void TurnOn() - { - Console.WriteLine("TubeLight turns on."); - } - - public void TurnOff() - { - Console.WriteLine("TubeLight turns off."); - } + Console.WriteLine("TubeLight turns on."); + } + public void TurnOff() + { + Console.WriteLine("TubeLight turns off."); } + +} ``` **抽象的工厂类** ```java public interface ICreator - { - ILight CreateLight(); - } +{ + ILight CreateLight(); +} ``` **具体的工厂类:BulbCreator** ```java - public class BulbCreator implements ICreator +public class BulbCreator implements ICreator +{ + public ILight CreateLight() { - public ILight CreateLight() - { - return new BulbLight(); - } - + return new BulbLight(); } + +} ``` **具体的工厂类:TubeCreator** ```java - public class TubeCreator implements ICreator +public class TubeCreator implements ICreator +{ + public ILight CreateLight() { - public ILight CreateLight() - { - return new TubeLight(); - } + return new TubeLight(); } +} ``` **客户端调用** ```java static void Main(string[] args) - { - //先给我来个灯泡 - ICreator creator = new BulbCreator(); - ILight light = creator.CreateLight(); - light.TurnOn(); - light.TurnOff(); - - //再来个灯管看看 - creator = new TubeCreator(); - light = creator.CreateLight(); - light.TurnOn(); - light.TurnOff(); - - } +{ + //先给我来个灯泡 + ICreator creator = new BulbCreator(); + ILight light = creator.CreateLight(); + light.TurnOn(); + light.TurnOff(); + + //再来个灯管看看 + creator = new TubeCreator(); + light = creator.CreateLight(); + light.TurnOn(); + light.TurnOff(); + +} ``` 通过一个引用变量ICreator来创建产品对象,创建何种产品对象由指向的具体工厂类决定。通过工厂方法模式,将具体的应用逻辑和产品的创建分离开,促进松耦合。 @@ -195,7 +195,4 @@ JDBC中的工厂方法: Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=DB;user=sa;password="); Statement statement=conn.createStatement(); ResultSet rs=statement.executeQuery("select * from UserInfo"); -``` - - - +``` \ No newline at end of file diff --git a/java/basis/abstract_interface.md b/java/basis/abstract_interface.md index 11fdd41d..e5ed2a58 100644 --- a/java/basis/abstract_interface.md +++ b/java/basis/abstract_interface.md @@ -52,7 +52,7 @@ interface InterfaceA { } ``` -在 Java 8 之前,接口与其实现类之间的 **耦合度** 太高了(**tightly coupled**),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为Jjava 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。 +在 Java 8 之前,接口与其实现类之间的 **耦合度** 太高了(**tightly coupled**),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为Java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。 ## 一般性理解 @@ -69,4 +69,4 @@ interface InterfaceA { 4. 抽象类里可以没有抽象方法。 5. 接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。 6. 接口中没有 `this` 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。 -7. 抽象类不能在Java 8 的 lambda 表达式中使用。 \ No newline at end of file +7. 抽象类不能在Java 8 的 lambda 表达式中使用。 diff --git a/java/basis/annotation.md b/java/basis/annotation.md index 60437599..161f1049 100644 --- a/java/basis/annotation.md +++ b/java/basis/annotation.md @@ -147,13 +147,13 @@ public @interface MyAnnotataion{ ```java public class AnnotationDemo { - @AuthorAnno(name="lvr", website="hello", revision=1) + @MyAnnotataion(name="lvr", website="hello", revision=1) public static void main(String[] args) { System.out.println("I am main method"); } @SuppressWarnings({ "unchecked", "deprecation" }) - @AuthorAnno(name="lvr", website="hello", revision=2) + @MyAnnotataion(name="lvr", website="hello", revision=2) public void demo(){ System.out.println("I am demo method"); } diff --git a/java/basis/java-nio.md b/java/basis/java-nio.md index 30632be2..7a173a1d 100644 --- a/java/basis/java-nio.md +++ b/java/basis/java-nio.md @@ -315,7 +315,7 @@ while(keyIterator.hasNext()) { 上述循环会迭代key集合,针对每个key我们单独判断他是处于何种就绪状态。 -注意keyIterater.remove\(\)方法的调用,Selector本身并不会移除SelectionKey对象,这个操作需要我们收到执行。当下次channel处于就绪是,Selector任然会把这些key再次加入进来。 +注意keyIterater.remove\(\)方法的调用,Selector本身并不会移除SelectionKey对象,这个操作需要我们手动执行。当下次channel处于就绪是,Selector任然会把这些key再次加入进来。 SelectionKey.channel返回的channel实例需要强转为我们实际使用的具体的channel类型,例如ServerSocketChannel或SocketChannel. diff --git a/java/basis/throwable.md b/java/basis/throwable.md index 7decb66a..6bd71c4e 100644 --- a/java/basis/throwable.md +++ b/java/basis/throwable.md @@ -10,7 +10,7 @@ Java异常机制用到的几个关键字:**try、catch、finally、throw、thr • **catch** -- 用于捕获异常。catch用来捕获try语句块中发生的异常。 -• **finally** -- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。 +• **finally** -- finally语句块总是会被执行。它主要用于回收在try块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。 • **throw** -- 用于抛出异常。 @@ -172,7 +172,7 @@ RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异 **(01) 运行时异常** **定义**: RuntimeException及其子类都被称为运行时异常。 -**特点**: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。 +**特点**: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fast机制产生的ConcurrentModificationException异常等,都属于运行时异常。 虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。 @@ -194,4 +194,4 @@ RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异 当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。 -按照Java惯例,我们是不应该是实现任何新的Error子类的! \ No newline at end of file +按照Java惯例,我们是不应该实现任何新的Error子类的! diff --git a/operating-system/memory-management.md b/operating-system/memory-management.md index 1a378112..94bf0045 100644 --- a/operating-system/memory-management.md +++ b/operating-system/memory-management.md @@ -5,11 +5,11 @@ ![img](http://upload-images.jianshu.io/upload_images/3985563-b187c13549a822a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -**首次适应(First Fit)算法:**空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。 +**首次适应(First Fit)算法:** 空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。 -**最佳适应(Best Fit)算法:**空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区。 +**最佳适应(Best Fit)算法:** 空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区。 -**最坏适应(Worst Fit)算法:**又称最大适应(Largest Fit)算法,空闲分区以容量递减的次序链接。找到第一个能满足要求的空闲分区,也就是挑选出最大的分区。 +**最坏适应(Worst Fit)算法:** 又称最大适应(Largest Fit)算法,空闲分区以容量递减的次序链接。找到第一个能满足要求的空闲分区,也就是挑选出最大的分区。 ## 二、基本分页储存管理方式 @@ -51,18 +51,18 @@ 1. 多次性:一个作业可以分多次被调入内存。多次性是虚拟存储特有的属性 2. 对换性:作业运行过程中存在换进换出的过程(换出暂时不用的数据换入需要的数据) -3. 虚拟性:虚拟性体现在其从逻辑上扩充了内存的容量(可以运行实际内存需求比物理内存大的应用程序)。虚拟性是虚拟存储器的最重要特征也是其最终目标。虚拟性建立在多次性和对换性的基础上行,多次性和对换性又建立在离散分配的基础上 +3. 虚拟性:虚拟性体现在其从逻辑上扩充了内存的容量(可以运行实际内存需求比物理内存大的应用程序)。虚拟性是虚拟存储器的最重要特征也是其最终目标。虚拟性建立在多次性和对换性的基础上行,多次性和对换性又建立在离散分配的基础上。 ## 五、页面置换算法 -**最佳置换算法:**只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。 +**最佳置换算法:** 只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。 -**先进先出置换算法:**简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面。 +**先进先出置换算法:** 简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面。 -**最近最久未使用算法LRU:**算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。 +**最近最久未使用算法LRU:** 算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。 -**时钟算法clock(也被称为是最近未使用算法NRU):**页面设置一个访问位,并将页面链接为一个环形队列,页面被访问的时候访问位设置为1。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面。 +**时钟算法clock(也被称为是最近未使用算法NRU):** 页面设置一个访问位,并将页面链接为一个环形队列,页面被访问的时候访问位设置为1。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面。 -**改进型Clock算法:**在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问位和修改位都是0的页面,其次是访问位为0修改位为1的页面。 +**改进型Clock算法:** 在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问位和修改位都是0的页面,其次是访问位为0修改位为1的页面。 -**最少使用算法LFU:**设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。 \ No newline at end of file +**最少使用算法LFU:** 设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。 \ No newline at end of file diff --git a/operating-system/process-thread.md b/operating-system/process-thread.md index 726499ad..ff087bbd 100644 --- a/operating-system/process-thread.md +++ b/operating-system/process-thread.md @@ -8,21 +8,21 @@ 阻塞状态,又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。 -**注意区别就绪状态和等待状态:**就绪状态是指进程仅缺少处理机,只要获得处理机资源就立即执行;而等待状态是指进程需要其他资源(除了处理机)或等待某一事件。 +**注意区别就绪状态和等待状态:** 就绪状态是指进程仅缺少处理机,只要获得处理机资源就立即执行;而等待状态是指进程需要其他资源(除了处理机)或等待某一事件。 -**就绪状态 -> 运行状态:**处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。 +**就绪状态 -> 运行状态:** 处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。 -**运行状态 -> 就绪状态:**处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就 、 绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。 +**运行状态 -> 就绪状态:** 处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就 、 绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。 -**运行状态 -> 阻塞状态:**当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。 +**运行状态 -> 阻塞状态:** 当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。 -**阻塞状态 -> 就绪状态:**当进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。 +**阻塞状态 -> 就绪状态:** 当进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。 ## 二、进程与线程的区别 -**进程:**进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位(具有动态、并发、独立、异步的特性,以及就绪、执行、阻塞3种状态);引入进程是为了使多个程序可以并发的执行,以提高系统的资源利用率和吞吐量。 +**进程:** 进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位(具有动态、并发、独立、异步的特性,以及就绪、执行、阻塞3种状态);引入进程是为了使多个程序可以并发的执行,以提高系统的资源利用率和吞吐量。 -**线程:**是比进程更小的可独立运行的基本单位,可以看做是轻量级的进程(具有轻型实体,独立调度分派单位,可并发执行,共享进程资源等属性);引入目的是为了减少程序在并发执行过程中的开销,使OS的并发效率更高。 +**线程:** 是比进程更小的可独立运行的基本单位,可以看做是轻量级的进程(具有轻型实体,独立调度分派单位,可并发执行,共享进程资源等属性);引入目的是为了减少程序在并发执行过程中的开销,使OS的并发效率更高。 两者的对比: @@ -76,7 +76,7 @@ 反之,**当程序运行在级特权级上时,就可以称之为运行在内核态。** -虽然用户态下和内核态下工作的程序有很多差别,**但最重要的差别就在于特权级的不同,即权力的不同。**运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。 +虽然用户态下和内核态下工作的程序有很多差别,**但最重要的差别就在于特权级的不同,即权力的不同。** 运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。 当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。 @@ -116,12 +116,12 @@ ## 七、进程调度算法 -**先来先服务调度算法FCFS:**既可以作为作业调度算法也可以作为进程调度算法;按作业或者进程到达的先后顺序依次调度;因此对于长作业比较有利; +**先来先服务调度算法FCFS:** 既可以作为作业调度算法也可以作为进程调度算法;按作业或者进程到达的先后顺序依次调度;因此对于长作业比较有利; -**短作业优先调度算法SJF:**作业调度算法,算法从就绪队列中选择估计时间最短的作业进行处理,直到得出结果或者无法继续执行;缺点:不利于长作业;未考虑作业的重要性;运行时间是预估的,并不靠谱 ; +**短作业优先调度算法SJF:** 作业调度算法,算法从就绪队列中选择估计时间最短的作业进行处理,直到得出结果或者无法继续执行;缺点:不利于长作业;未考虑作业的重要性;运行时间是预估的,并不靠谱 ; -**高相应比算法HRN:**响应比=(等待时间+要求服务时间)/要求服务时间; +**高响应比算法HRN:** 响应比=(等待时间+要求服务时间)/要求服务时间; -**时间片轮转调度RR:**按到达的先后对进程放入队列中,然后给队首进程分配CPU时间片,时间片用完之后计时器发出中断,暂停当前进程并将其放到队列尾部,循环 ; +**时间片轮转调度RR:** 按到达的先后对进程放入队列中,然后给队首进程分配CPU时间片,时间片用完之后计时器发出中断,暂停当前进程并将其放到队列尾部,循环 ; -**多级反馈队列调度算法:**目前公认较好的调度算法;设置多个就绪队列并为每个队列设置不同的优先级,第一个队列优先级最高,其余依次递减。优先级越高的队列分配的时间片越短,进程到达之后按FCFS放入第一个队列,如果调度执行后没有完成,那么放到第二个队列尾部等待调度,如果第二次调度仍然没有完成,放入第三队列尾部…。只有当前一个队列为空的时候才会去调度下一个队列的进程。 \ No newline at end of file +**多级反馈队列调度算法:** 目前公认较好的调度算法;设置多个就绪队列并为每个队列设置不同的优先级,第一个队列优先级最高,其余依次递减。优先级越高的队列分配的时间片越短,进程到达之后按FCFS放入第一个队列,如果调度执行后没有完成,那么放到第二个队列尾部等待调度,如果第二次调度仍然没有完成,放入第三队列尾部……只有当前一个队列为空的时候才会去调度下一个队列的进程。 \ No newline at end of file diff --git a/operating-system/summarize.md b/operating-system/summarize.md index a9716da7..f659661b 100644 --- a/operating-system/summarize.md +++ b/operating-system/summarize.md @@ -29,15 +29,22 @@ ### 1.操作系统的四个特性 -**并发:**同一段时间内多个程序执行(注意区别并行和并发,前者是同一时刻的多个事件,后者是同一时间段内的多个事件) -**共享:**系统中的资源可以被内存中多个并发执行的进线程共同使用 -**虚拟:**通过时分复用(如分时系统)以及空分复用(如虚拟内存)技术实现把一个物理实体虚拟为多个 -**异步:**系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进 +**并发:** 同一段时间内多个程序执行(注意区别并行和并发,前者是同一时刻的多个事件,后者是同一时间段内的多个事件) + +**共享:** 系统中的资源可以被内存中多个并发执行的进线程共同使用 + +**虚拟:** 通过时分复用(如分时系统)以及空分复用(如虚拟内存)技术实现把一个物理实体虚拟为多个 + +**异步:** 系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进 ### 2.操作系统的主要功能 -**处理机管理:**处理机分配都是以进程为单位,所以处理机管理也被看做是进程管理。包括进程控制,进程同步,进程通信和进程调度 -**存储器管理(或者内存管理):**内存分配,内存保护,地址映射,内存扩充 -**设备管理:**管理所有外围设备,包括完成用户的IO请求;为用户进程分配IO设备;提高IO设备利用率;提高IO速度;方便IO的使用 -**文件管理:**管理用户文件和系统文件,方便使用同时保证安全性。包括:磁盘存储空间管理,目录管理,文件读写管理以及文件共享和保护 -**提供用户接口:**程序接口(如API)和用户接口(如GUI) \ No newline at end of file +**处理机管理:** 处理机分配都是以进程为单位,所以处理机管理也被看做是进程管理。包括进程控制,进程同步,进程通信和进程调度 + +**存储器管理(或者内存管理):** 内存分配,内存保护,地址映射,内存扩充 + +**设备管理:** 管理所有外围设备,包括完成用户的IO请求;为用户进程分配IO设备;提高IO设备利用率;提高IO速度;方便IO的使用 + +**文件管理:** 管理用户文件和系统文件,方便使用同时保证安全性。包括:磁盘存储空间管理,目录管理,文件读写管理以及文件共享和保护 + +**提供用户接口:** 程序接口(如API)和用户接口(如GUI)