精华内容
下载资源
问答
  • 关于数据共享的另一个话题便是数据更新通知机制了,即如果一个应用程序对共享数据做了修改,它应该如何通知其它正在使用这些共享数据的应用程序呢?本文将分析Content Provider的共享数据更新通知机制,为读者解答这...

            在Android系统中,应用程序组件Content Provider为不同的应用程序实现数据共享提供了基础设施,它主要通过Binder进程间通信机制和匿名共享内存机制来实现的。关于数据共享的另一个话题便是数据更新通知机制了,即如果一个应用程序对共享数据做了修改,它应该如何通知其它正在使用这些共享数据的应用程序呢?本文将分析Content Provider的共享数据更新通知机制,为读者解答这个问题。

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

            Android应用程序组件Content Provider中的数据更新通知机制和Android系统中的广播(Broadcast)通知机制的实现思路是相似的。在Android的广播机制中,首先是接收者对自己感兴趣的广播进行注册,接着当发送者发出这些广播时,接收者就会得到通知了。更多关于Android系统的广播机制的知识,可以参考前面Android系统中的广播(Broadcast)机制简要介绍和学习计划这一系列文章。然而,Content Provider中的数据监控机制与Android系统中的广播机制又有三个主要的区别,一是前者是通过URI来把通知的发送者和接收者关联在一起的,而后者是通过Intent来关联的,二是前者的通知注册中心是由ContentService服务来扮演的,而后者是由ActivityManagerService服务来扮演的,三是前者负责接收数据更新通知的类必须要继承ContentObserver类,而后者要继承BroadcastReceiver类。之所以会有这些区别,是由于Content Proivder组件的数据共享功能本身就是建立在URI的基础之上的,因此专门针对URI来设计另外一套通知机制会更实用和方便,而Android系统的广播机制是一种更加通用的事件通知机制,它的适用范围会更广泛一些。

            与分析Android系统的广播机制类似,我们把Content Provider的数据更新机制划分为三个单元进行分析,第一个单元是ContentService的启动过程,第二个单元是监控数据变化的ContentObserver的注册过程,第二个单元是数据更新通知的发送过程。

            与前面两篇文章Android应用程序组件Content Provider的启动过程源代码分析Android应用程序组件Content Provider在应用程序之间共享数据的原理分析一样,本文仍然以Android应用程序组件Content Provider应用实例这篇文章介绍的应用程序为例来分析Content Provider的数据更新机制。

            1. ContentService的启动过程分析

            前面提到,在Content Provider的数据更新通知机制中,ContentService扮演者ContentObserver的注册中心的角色,因此,它必须要系统启动的时候就启动起来,以便后面启动起来的应用程序可以使用它。在前面这篇文章Android系统进程Zygote启动过程的源代码分析中,我们提到,Android系统进程Zygote在启动的时候,在启动一个System进程来加载系统的一些关键服务,而ContentService就这些关键服务之一了。在System进程中,负责加载系统关键服务的类为SystemServer类,它定义在frameworks/base/services/java/com/android/server/SystemServer.java文件中,它会通过启动一个线程SystemThread来加载这些关键服务:

    class ServerThread extends Thread {
    	......
    
    	@Override
    	public void run() {
    		......
    
    		Looper.prepare();
    
    		// Critical services...
    		try {
    			......
    
    			ContentService.main(context,
    				factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);
    
    			......
    
    		}catch (RuntimeException e) {
    			......
    		}
    
    		......
    
    		Looper.loop();
    		......
    	}
    }
            ContentService类定义在frameworks/base/core/java/android/content/ContentService.java文件中,它的main函数的实现如下所示:

    public final class ContentService extends IContentService.Stub {
    	......
    
    	public static IContentService main(Context context, boolean factoryTest) {
    		ContentService service = new ContentService(context, factoryTest);
    		ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
    		return service;
    	}
    
    	......
    }

            从这里我们就可以看到,在ContentService类的main函数中,会创建一个ContentService实例,然后把它添加到ServiceManager中去,这样,ContentService服务就启动起来了,  其它地方可以通过ServiceManager来获得它的一个远程接口来使用它提供的服务。

            2. ContentObserver的注册过程分析

            在前面这篇文章Android应用程序组件Content Provider应用实例介绍的应用程序Acticle中,主窗口MainActivity在创建的时候,会调用应用程序上下文的ContentResolver接口来注册一个自定义的ContentObserver来监控ArticlesProvider这个Content Provider中的数据变化:

    public class MainActivity extends Activity implements View.OnClickListener, AdapterView.OnItemClickListener {
    	......
    
    	private ArticleAdapter adapter = null;
    	private ArticleObserver observer = null;
    
    	......
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		......
    
    		observer = new ArticleObserver(new Handler());
    		getContentResolver().registerContentObserver(Articles.CONTENT_URI, true, observer);
    
    		......
    	}
    
    	private class ArticleObserver extends ContentObserver {
    		public ArticleObserver(Handler handler) {
    			super(handler);
    		}
    
    		@Override
    		public void onChange (boolean selfChange) {
    			adapter.notifyDataSetChanged();
    		}
    	}
    
    	......
    }
            从ContentObserver继承下来的子类必须要实现onChange函数。当这个ContentObserver子类负责监控的数据发生变化时,ContentService就会调用它的onChange函数来处理,参数selfChange表示这个变化是否是由自己引起的,在我们这个情景中,不需要关注这个参数的值。在这个应用程序中,ArticleObserver继承了ContentObserver类,它负责监控的URI是Articles.CONTENT_URI,它的值为"content://shy.luo.providers.articles/item",这个值是在这篇文章 Android应用程序组件Content Provider应用实例介绍的应用程序ActiclesProvider中的Articles.java文件中定义的。当所有以Articles.CONTENT_URI为前缀的URI对应的数据发生改变时,ContentService都会调用这个ArticleObserver类的onChange函数来处理。在ArticleObserver类的onChange函数中,执行的操作就是重新获取ActiclesProvider中的数据来更新界面上的文章信息列表。

            在ArticleObserver类的构造函数中,有一个参数handler,它的类型为Handler,它是从MainActivity类的onCreate函数中创建并传过来的。通过前面这篇文章Android应用程序消息处理机制(Looper、Handler)分析的学习,我们知道,这个handler是用来分发和处理消息用的。由于MainActivity类的onCreate函数是在应用程序的主线程中被调用的,因此,这个handler参数就是和应用程序主线程的消息循环关联在一起的。在后面我们分析数据更新通知的发送过程时,便会看到这个handler参数是如何使用的了。

            下面我们就开始分析注册ArticleObserver来监控ActiclesProvider中的数据变化的过程,首先来看一下这个过程的时序图,然后再详细分析每一个步骤:


            Step 1. ContentResolver.registerContentObserver

            这个函数定义在frameworks/base/core/java/android/content/ContentResolver.java文件中:

    public abstract class ContentResolver {
    	......
    
    	public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
    		ContentObserver observer)
    	{
    		try {
    			getContentService().registerContentObserver(uri, notifyForDescendents,
    				observer.getContentObserver());
    		} catch (RemoteException e) {
    		}
    	}
    
    	......
    }
            当参数notifyForDescendents为true时,表示要监控所有以uri为前缀的URI对应的数据变化。这个函数做了三件事情,一是调用getContentService函数来获得前面已经启动起来了的ContentService远程接口,二是调用从参数传进来的ContentObserver对象observer的getContentObserver函数来获得一个Binder对象,三是通过调用这个ContentService远程接口的registerContentObserver函数来把这个Binder对象注册到ContentService中去。

            Step 2.ContentResolver.getContentService

            这个函数定义在frameworks/base/core/java/android/content/ContentResolver.java文件中:

    public abstract class ContentResolver {
    	......
    
    	public static IContentService getContentService() {
    		if (sContentService != null) {
    			return sContentService;
    		}
    		IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
    		......
    		sContentService = IContentService.Stub.asInterface(b);
    		......
    		return sContentService;
    	}
    
    	private static IContentService sContentService;
    	......
    }
            在ContentResolver类中,有一个静态成员变量sContentService,开始时它的值为null。当ContentResolver类的getContentService函数第一次被调用时,它便会通过ServiceManager类的getService函数来获得前面已经启动起来了的ContentService服务的远程接口,然后把它保存在sContentService变量中。这样,当下次ContentResolver类的getContentService函数再次被调用时,就可以直接把这个ContentService远程接口返回给调用者了。

            Step 3. ContentObserver.getContentObserver

            这个函数定义在frameworks/base/core/java/android/database/ContentObserver.java文件中:

    public abstract class ContentObserver {
    	......
    
    	private Transport mTransport;
    
    	......
    
    	private static final class Transport extends IContentObserver.Stub {
    		ContentObserver mContentObserver;
    
    		public Transport(ContentObserver contentObserver) {
    			mContentObserver = contentObserver;
    		}
    
    		......
    	}
    
    	......
    
    	public IContentObserver getContentObserver() {
    		synchronized(lock) {
    			if (mTransport == null) {
    				mTransport = new Transport(this);
    			}
    			return mTransport;
    		}
    	}
    
    	......
    }
            ContentObserver类的getContentObserver函数返回的是一个成员变量mTransport,它的类型为ContentObserver的内部类Transport。从Transport类的定义我们可以知道,它有一个成员变量mContentObserver,用来保存与对应的ContentObserver对象。同时我们还可以看出,ContentObserver类的成员变量mTransport是一个Binder对象,它是要传递给ContentService服务的,以便当ContentObserver所监控的数据发生变化时,ContentService服务可以通过这个Binder对象通知相应的ContentObserver它监控的数据发生变化了。

            Step 4. ContentService.registerContentObserver

            这个函数定义在frameworks/base/core/java/android/content/ContentService.java文件中:

    public final class ContentService extends IContentService.Stub {
    	......
    
    	private final ObserverNode mRootNode = new ObserverNode("");
    
    	......
    
    	public void registerContentObserver(Uri uri, boolean notifyForDescendents,
    			IContentObserver observer) {
    		......
    
    		synchronized (mRootNode) {
    			mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode);
    			......
    		}
    	}
    
    	......
    }
            它调用了ContentService类的成员变量mRootNode的addObserverLocked函数来注册这个ContentObserver对象observer。成员变量mRootNode的类型为ContentService在内部定义的一个类ObserverNode。

            Step 5. ObserverNode.addObserverLocked

            这个函数定义在frameworks/base/core/java/android/content/ContentService.java文件中:

    public final class ContentService extends IContentService.Stub {
    	......
    
    	public static final class ObserverNode {
    		......
    
    		private String mName;
    		private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
    		private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
    
    		public ObserverNode(String name) {
    			mName = name;
    		}
    
    		private String getUriSegment(Uri uri, int index) {
    			if (uri != null) {
    				if (index == 0) {
    					return uri.getAuthority();
    				} else {
    					return uri.getPathSegments().get(index - 1);
    				}
    			} else {
    				return null;
    			}
    		}
    
    		private int countUriSegments(Uri uri) {
    			if (uri == null) {
    				return 0;
    			}
    			return uri.getPathSegments().size() + 1;
    		}
    
    		public void addObserverLocked(Uri uri, IContentObserver observer,
    				boolean notifyForDescendents, Object observersLock) {
    			addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock);
    		}
    
    		private void addObserverLocked(Uri uri, int index, IContentObserver observer,
    				boolean notifyForDescendents, Object observersLock) {
    			// If this is the leaf node add the observer
    			if (index == countUriSegments(uri)) {
    				mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock));
    				return;
    			}
    
    			// Look to see if the proper child already exists
    			String segment = getUriSegment(uri, index);
    			if (segment == null) {
    				throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
    			}
    			int N = mChildren.size();
    			for (int i = 0; i < N; i++) {
    				ObserverNode node = mChildren.get(i);
    				if (node.mName.equals(segment)) {
    					node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, observersLock);
    					return;
    				}
    			}
    
    			// No child found, create one
    			ObserverNode node = new ObserverNode(segment);
    			mChildren.add(node);
    			node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, observersLock);
    		}
    
    		......
    	}
    
    	......
    }
            从这里我们就可以看出,注册到ContentService中的ContentObserver按照树形来组织,树的节点类型为ObserverNode,而树的根节点就为ContentService类的成员变量mRootNode。每一个ObserverNode节点都对应一个名字,它是从URI中解析出来的。

            在我们这个情景中,传进来的uri为"content://shy.luo.providers.articles/item",从Step 3调用mRootNode的addObserverLocked函数来往树上增加一个ObserverNode节点时,传进来的参数index的值为0,而调用countUriSegments("content://shy.luo.providers.articles/item")函数的返回值为2,不等于index的值,因此就会往下执行,而通过调用getUriSegment("content://shy.luo.providers.articles/item", 0)函数得到的返回值为"shy.luo.providers.articles"。假设这里是第一次调用树的根节点mRootNode来增加"content://shy.luo.providers.articles/item"这个URI,那么在接下来的for循环中,就不会在mRootNode的孩子节点列表mChildren中找到与名称"shy.luo.providers.articles"对应的ObserverNode,于是就会以"shy.luo.providers.articles"为名称来创建一个新的ObserverNode,并增加到mRootNode的孩子节点列表mChildren中去,并以这个新的ObserverNode来开始新一轮的addObserverLocked函数调用。

            第二次进入到addObserverLocked函数时,countUriSegments("content://shy.luo.providers.articles/item")的值仍为2,而index的值为1,因此就会往下执行,这时候通过调用getUriSegment("content://shy.luo.providers.articles/item", 1)函数得到的返回值为"item"。假设这时候在以"shy.luo.providers.articles/item"为名称的ObserverNode中不存在名称为"item"的孩子节点,于是又会以"item"为名称来创建一个新的ObserverNode,并以这个新的ObserverNode来开始新一轮的addObserverLocked函数调用。

            第三次进入到addObserverLocked函数时,countUriSegments("content://shy.luo.providers.articles/item")的值仍为2,而index的值也为2,因此就会新建一个ObserverEntry对象,并保存在这个以"item"为名称的ObserverNode的ContentObserver列表mObervers中。

            最终我们得到的树形结构如下所示:

            mRootNode("")

                -- ObserverNode("shy.luo.providers.articles")

                    --ObserverNode("item") , which has a ContentObserver in mObservers  
           这样,ContentObserver的注册过程就完成了。

           3. 数据更新通知的发送过程

           在前面这篇文章Android应用程序组件Content Provider应用实例介绍的应用程序Acticle中,当调用ArticlesAdapter类的insertArticle往ArticlesProvider中增加一个文章信息条目时:

    public class ArticlesAdapter {
    	......
    
    	public long insertArticle(Article article) {
    		ContentValues values = new ContentValues();
    		values.put(Articles.TITLE, article.getTitle());
    		values.put(Articles.ABSTRACT, article.getAbstract());
    		values.put(Articles.URL, article.getUrl());
    
    		Uri uri = resolver.insert(Articles.CONTENT_URI, values);
    		String itemId = uri.getPathSegments().get(1);
    
    		return Integer.valueOf(itemId).longValue();
    	}
    
    	......
    }
            便会进入到应用程序ArticlesProvider中的ArticlesProvider类的insert函数中:

    public class ArticlesProvider extends ContentProvider {
    	......
    
    	@Override
    	public Uri insert(Uri uri, ContentValues values) {
    		if(uriMatcher.match(uri) != Articles.ITEM) {
    			throw new IllegalArgumentException("Error Uri: " + uri);
    		}
    
    		SQLiteDatabase db = dbHelper.getWritableDatabase();
    
    		long id = db.insert(DB_TABLE, Articles.ID, values);
    		if(id < 0) {
    			throw new SQLiteException("Unable to insert " + values + " for " + uri);
    		}
    
    		Uri newUri = ContentUris.withAppendedId(uri, id);
    		resolver.notifyChange(newUri, null);
    
    		return newUri;
    	}
    
    	......
    }
            从上面传来的参数uri的值为"content://shy.luo.providers.articles/item"。假设当这个函数把数据成功增加到SQLite数据库之后,返回来的id值为n,于是通过调用ContentUris.withAppendedId("content://shy.luo.providers.articles/item", n)得到的newUri的值就为"content://shy.luo.providers.articles/item/n"。这时候就会调用下面语句来通知那些注册了监控"content://shy.luo.providers.articles/item/n"这个URI的ContentObserver,它监控的数据发生变化了:

    resolver.notifyChange(newUri, null);
            下面我们就开始分析这个数据变化通知的发送过程,首先来看一下这个过程的时序图,然后再详细分析每一个步骤:


            Step 1. ContentResolver.notifyChange
            这个函数定义在frameworks/base/core/java/android/content/ContentResolver.java文件中:

    public abstract class ContentResolver {
    	......
    
    	public void notifyChange(Uri uri, ContentObserver observer) {
    		notifyChange(uri, observer, true /* sync to network */);
    	}
    
    	public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
    		try {
    			getContentService().notifyChange(
    				uri, observer == null ? null : observer.getContentObserver(),
    				observer != null && observer.deliverSelfNotifications(), syncToNetwork);
    		} catch (RemoteException e) {
    		}
    	}
    
    	......
    }
            这里调用了ContentService的远接程口来调用它的notifyChange函数来发送数据更新通知。

            Step 2. ContentService.notifyChange

            这个函数定义在frameworks/base/core/java/android/content/ContentService.java文件中:

    public final class ContentService extends IContentService.Stub {
    	......
    
    	public void notifyChange(Uri uri, IContentObserver observer,
    			boolean observerWantsSelfNotifications, boolean syncToNetwork) {
    		......
    
    		try {
    			ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
    			synchronized (mRootNode) {
    				mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
    					calls);
    			}
    			final int numCalls = calls.size();
    			for (int i=0; i<numCalls; i++) {
    				ObserverCall oc = calls.get(i);
    				try {
    					oc.mObserver.onChange(oc.mSelfNotify);
    					......
    				} catch (RemoteException ex) {
    					......
    				}
    			}
    			......
    		} finally {
    			......
    		}
    	}
    
    	......
    }
            这个函数主要做了两件事情,第一件事情是调用ContentService的成员变量mRootNode的collectObserverLocked函数来收集那些注册了监控"content://shy.luo.providers.articles/item/n"这个URI的ContentObserver,第二件事情是分别调用了这些ContentObserver的onChange函数来通知它们监控的数据发生变化了。

            Step 3. ObserverNode.collectObserversLocked

            这个函数定义在frameworks/base/core/java/android/content/ContentService.java文件中:

    public final class ContentService extends IContentService.Stub {
    	......
    
    	public static final class ObserverNode {
    		......
    
    		private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
    				boolean selfNotify, ArrayList<ObserverCall> calls) {
    			int N = mObservers.size();
    			IBinder observerBinder = observer == null ? null : observer.asBinder();
    			for (int i = 0; i < N; i++) {
    				ObserverEntry entry = mObservers.get(i);
    
    				// Don't notify the observer if it sent the notification and isn't interesed
    				// in self notifications
    				if (entry.observer.asBinder() == observerBinder && !selfNotify) {
    					continue;
    				}
    
    				// Make sure the observer is interested in the notification
    				if (leaf || (!leaf && entry.notifyForDescendents)) {
    					calls.add(new ObserverCall(this, entry.observer, selfNotify));
    				}
    			}
    		}
    
    		public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
    				boolean selfNotify, ArrayList<ObserverCall> calls) {
    			String segment = null;
    			int segmentCount = countUriSegments(uri);
    			if (index >= segmentCount) {
    				// This is the leaf node, notify all observers
    				collectMyObserversLocked(true, observer, selfNotify, calls);
    			} else if (index < segmentCount){
    				segment = getUriSegment(uri, index);
    				// Notify any observers at this level who are interested in descendents
    				collectMyObserversLocked(false, observer, selfNotify, calls);
    			}
    
    			int N = mChildren.size();
    			for (int i = 0; i < N; i++) {
    				ObserverNode node = mChildren.get(i);
    				if (segment == null || node.mName.equals(segment)) {
    					// We found the child,
    					node.collectObserversLocked(uri, index + 1, observer, selfNotify, calls);
    					if (segment != null) {
    						break;
    					}
    				}
    			}
    		}
    	}
    }
            第一次调用collectObserversLocked时,是在mRootNode的这个ObserverNode节点中进行收集ContentObserver的。这时候传进来的uri的值为"content://shy.luo.providers.articles/item/n",index的值为0。调用countUriSegments("content://shy.luo.providers.articles/item/n")函数得到的返回值为3,于是就会调用下面语句:

    segment = getUriSegment("content://shy.luo.providers.articles/item/n",0);
    // Notify any observers at this level who are interested in descendents
    collectMyObserversLocked(false, observer, selfNotify, calls);
            这里得到的segment为"shy.luo.providers.articles"。在我们这个情景中,假设mRootNode这个节点中没有注册ContentObserver,于是调用collectMyObserversLocked函数就不会收集到ContentObserver。

            在接下来的for循环中,在mRootNode的孩子节点列表mChildren中查找名称等于"shy.luo.providers.articles"的OberverNode节点。在上面分析ContentObserver的注册过程时,我们已经往mRootNode的孩子节点列表mChildren中增加了一个名称为"shy.luo.providers.articles"的OberverNode节点,因此,这里会成功找到它,并且调用它的collectObserversLocked函数来继续收集ContentObserver。

            第二次进入到collectObserversLocked函数时,是在名称为"shy.luo.providers.articles"的OberverNode节点中收集ContentObserver的。这时候传来的uri值不变,但是index的值为1,于是执行下面语句:

    segment = getUriSegment("content://shy.luo.providers.articles/item/n",1);
    // Notify any observers at this level who are interested in descendents
    collectMyObserversLocked(false, observer, selfNotify, calls);
            这里得到的segment为"item"。在我们这个情景中,我们没有在名称为"shy.luo.providers.articles"的OberverNode节点中注册有ContentObserver,因此这里调用collectMyObserversLocked函数也不会收集到ContentObserver。

            在接下来的for循环中,在名称为"shy.luo.providers.articles"的ObserverNode节点的孩子节点列表mChildren中查找名称等于"item"的OberverNode节点。在上面分析ContentObserver的注册过程时,我们已经往名称为"shy.luo.providers.articles"的ObserverNode节点的孩子节点列表mChildren中增加了一个名称为"item"的OberverNode节点,因此,这里会成功找到它,并且调用它的collectObserversLocked函数来继续收集ContentObserver。

            第三次进入到collectObserversLocked函数时,是在名称为"shy.luo.providers.articles"的OberverNode节点的子节点中名称为"item"的ObserverNode节点中收集ContentObserver的。这时候传来的uri值不变,但是index的值为2,于是执行下面语句:

    segment = getUriSegment("content://shy.luo.providers.articles/item/n",2);
    // Notify any observers at this level who are interested in descendents
    collectMyObserversLocked(false, observer, selfNotify, calls);
            这里得到的segment为"n"。前面我们已经在名称为"shy.luo.providers.articles"的OberverNode节点的子节点中名称为"item"的ObserverNode节点中注册了一个ContentObserver,即ArticlesObserver,因此这里调用collectMyObserversLocked函数会收集到这个ContentObserver。注意,这次调用collectMyObserversLocked函数时,虽然传进去的参数leaf为false,但是由于我们注册ArticlesObserver时,指定了notifyForDescendents参数为true,因此,这里可以把它收集回来。

            在接下来的for循环中,继续在该节点的子节点列表mChildren中查找名称等于"n"的OberverNode节点。在我们这个情景中,不存在这个名称为"n"的子节点了,于是收集ContentObserver的工作就结束了,收集结果是只有一个ContentObserver,即我们在前面注册的ArticlesObserver。

            返回到Step 2中,调用下面语句来通知相应的ContentObserver,它们监控的数据发生变化了:

    for (int i=0; i<numCalls; i++) {
    	ObserverCall oc = calls.get(i);
    	try {
    		oc.mObserver.onChange(oc.mSelfNotify);
    		......
    	} catch (RemoteException ex) {
    		......
    	}
    }
            前面我们在分析ContentObserver的注册过程的Step 3时,介绍到注册到ContentService服务中的ContentObserver是一个在ContentObserver内部定义的一个类Transport的对象的远程接口,于是这里调用这个接口的onChange函数时,就会进入到ContentObserver的内部类Transport的onChange函数中去。

            Step 4. Transport.onChange

            这个函数定义在frameworks/base/core/java/android/database/ContentObserver.java文件中:

    public abstract class ContentObserver {
    	......
    
    	private static final class Transport extends IContentObserver.Stub {
    		ContentObserver mContentObserver;
    	
    		......
    
    		public void onChange(boolean selfChange) {
    			ContentObserver contentObserver = mContentObserver;
    			if (contentObserver != null) {
    				contentObserver.dispatchChange(selfChange);
    			}
    		}
    
    		......
    	}
    
    	......
    }
            前面我们在分析ContentObserver的注册过程的Step 3时,把ArticlesObserver这个ContentObserver保存在了这个Transport对象的mContentObserver成员变量中,因此,会调用它的dispatchChange函数来执行数据更新通知的操作。

            Step 5. ContentObserver.dispatchChange

            这个函数定义在frameworks/base/core/java/android/database/ContentObserver.java文件中:

    public abstract class ContentObserver {
    	......
    
    	public final void dispatchChange(boolean selfChange) {
    		if (mHandler == null) {
    			onChange(selfChange);
    		} else {
    			mHandler.post(new NotificationRunnable(selfChange));
    		}
    	}
    }
            在前面分析ArticlesObserver的注册过程时,我们以应用程序Article的主线程的消息循环创建了一个Handler,并且以这个Handler来创建了这个ArticlesObserver,这个Handler就保存在ArticlesObserver的父类ContentObserver的成员变量mHandler中。因此,这里的mHandler不为null,于是把这个数据更新通知封装成了一个消息,放到应用程序Article的主线程中去处理,最终这个消息是由NotificationRunnable类的run函数来处理的。

            Step 6. NotificationRunnable.run

            这个函数定义在frameworks/base/core/java/android/database/ContentObserver.java文件中:

    public abstract class ContentObserver {
    	......
    
    	private final class NotificationRunnable implements Runnable {
    		private boolean mSelf;
    
    		public NotificationRunnable(boolean self) {
    			mSelf = self;
    		}
    
    		public void run() {
    			ContentObserver.this.onChange(mSelf);
    		}
    	}
    
    	......
    }
            这个函数就直接调用ContentObserver的子类的onChange函数来处理这个数据更新通知了。在我们这个情景中,这个ContentObserver子类便是ArticlesObserver了。

            Step 7. ArticlesObserver.onChange

            这个函数定义在前面一篇文章Android应用程序组件Content Provider应用实例介绍的应用程序Artilce源代码工程目录下,在文件为packages/experimental/Article/src/shy/luo/article/MainActivity.java中:

    public class MainActivity extends Activity implements View.OnClickListener, AdapterView.OnItemClickListener {
    	......
    
    	private class ArticleObserver extends ContentObserver {
    		......
    
    		@Override
    		public void onChange (boolean selfChange) {
    			adapter.notifyDataSetChanged();
    		}
    	}
    
    	......
    }
           这里它要执行的操作便是更新界面上的ListView列表中的文章信息了,以便反映ArticlesProvider中的最新数据。

         这样,Android应用程序组件Content Provider的共享数据更新通知机制就分析完了,整个Android应用程序组件Content Provider的学习也结束了,重新学习请回到Android应用程序组件Content Provider简要介绍和学习计划一文。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    展开全文
  • android handlerthread 通知机制

    千次阅读 2011-07-20 12:44:09
    android handlerthread 通知机制 自从涉足android之日起,越来越觉得android深不可测,每个模块,每种机制都能让你琢磨很一段时间,内部的封装实在精深。早就想做handlerthread进程的研究,写点东西出来,可都...

    android handlerthread 通知机制

             自从涉足android之日起,越来越觉得android深不可测,每个模块,每种机制都能让你琢磨很一段时间,内部的封装实在精深。早就想做handlerthread进程的研究,写点东西出来,可都半途而废,终于还是抽空写了点早就应该写的东西,这方面的资料也还算不少,但都不是很全面,深入理解Android消息处理机制对于应用程序开发非常重要,也可以让你对线程同步有更加深刻的认识。下面将全面分析android中的消息处理机制,以供大家学习参考,也方便自己以后的回顾。

        本文解析Android如何利用Handler/Thread/Looper以及MessageQueue来实现消息机制的内部实现,了解了它的内部实现机理,对我们以后分析android模块有莫大的帮助。要了解handlerthread的工作过程,就要先分析looper,handler和Thread这三个类在消息处理机制中的作用,从名字就可以猜出个大概来,handlerThread自然会与Thread和Handler有关联,至于looper,那是一个消息处理循环主体。

        其实HandlerThread就是Thread的子类,一个线程,在线程中进行looper的消息分发/处理循环,而handler主要用来传递消息,并被调用来处理消息,所以要清楚分析android的消息处理机制,要从三个方面来分别做分析:

        1)android消息处理机制中相关类代码解析

        2)android消息处理机制中各类之间关系

        3)以线程的角度分析,使用该机制的技巧和注意的问题

    为了方便参考代码和理清后面的关系,我以代码中重要部分穿插解释来进行说明。

     

    一,相关类代码解析

    (1)looper

        所在文件looper.java, looper: 消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper,类的定义如下所示:

    public class  Looper {

       //与打印调试信息相关的变量。

        private static final boolean DEBUG = false;

        private static final boolean localLOGV =DEBUG ? Config.LOGD : Config.LOGV;

     

        // sThreadLocal.get() will return nullunless you've called prepare().

        //线程本地存储功能的封装,一个线程内有一个内部存储空间,线程相关的东西就存储    //到,这个线程的存储空间中,就不用放在堆上而进行同步操作了。

        private static final ThreadLocal sThreadLocal = new ThreadLocal();

     

        // MessageQueue,消息队列,就是存放消息的地方,handler就是往这里添加消息的

        final MessageQueue mQueue;

        volatile boolean mRun;

     

        //设置looper的那个线程,其实就是当前线程

        Thread mThread;

        private Printer mLogging = null;

     

        //代表一个UI Process的主线程

        private static Looper mMainLooper = null;

       

         /** Initialize the current thread as alooper.

          * This gives you a chance to createhandlers that then reference

          * this looper, before actually startingthe loop. Be sure to call

          * {@link #loop()} after calling thismethod, and end it by calling

          * {@link #quit()}.

          */

     

        //这里的Prepare函数就是设置looper的函数,一个线程只能设一个looper,而且要 //注意looper类的构造函数是私有函数,looper外的类就是通过这个函数来实例化  //looper对象的

        public static final void prepare() {

            if (sThreadLocal.get() != null) {

                throw newRuntimeException("Only one Looper may be created per thread");

            }

            sThreadLocal.set(new Looper());

        }

       

        /** Initialize the current thread as alooper, marking it as an application's main

         * looper. The main looper for your application is created by the Androidenvironment,

         * so you should never need to call this function yourself.

         * {@link #prepare()}

         */

     

     /*由framework设置的UI程序的主消息循环,注意,这个主消息循环是不会主动退出的提个醒,注意这里的mylooper()函数,后面有用到*/

        public static final voidprepareMainLooper() {

            prepare();

            setMainLooper(myLooper());

     

            if (Process.supportsProcesses()) {

                myLooper().mQueue.mQuitAllowed =false;

            }

        }

     

        private synchronized static voidsetMainLooper(Looper looper) {

            mMainLooper = looper;

        }

       

        /** Returns the application's main looper,which lives in the main thread of the application.

         */

        public synchronized static final LoopergetMainLooper() {

            return mMainLooper;

        }

     

        /**

         * Run the message queue in this thread. Be sure to call

         * {@link #quit()} to end the loop.

         */

        /* 这个函数尤为重要,是一个无限循环体,不断从MessageQueue中取得消息,并分发处理,如果消息队列中没有消息了,它会调用wait()函数阻塞起来 */

        public static final void loop() {

     

            //从该线程中取looper对象,这个函数会频繁用到

            Looper me = myLooper();

            MessageQueue queue = me.mQueue;

     

            while (true) {

                Message msg = queue.next(); //取消息队列中的一个消息,可能会阻塞

                //if (!me.mRun) {

                //是否需要退出?

                //    break;

                //}

                if (msg != null) {

                    if (msg.target == null) {

                        // No target is a magicidentifier for the quit message.

                        return;

                    }

                    if (me.mLogging!= null)me.mLogging.println(

                           ">>>>> Dispatching to " + msg.target + ""

                            + msg.callback +": " + msg.what

                            );

                   msg.target.dispatchMessage(msg);/*消息派遣函数,调用handler的一个消息派遣函数,用来处理消息,注意:这里不一定会用到handler的消息处理函数,在handler的部分会有介绍*/

     

                    if (me.mLogging!= null)me.mLogging.println(

                            "<<<<<Finished to    " + msg.target +" "

                            + msg.callback);

                    msg.recycle();  //过时消息的回收

                }

            }

        }

     

        /**

         * Return the Looper object associated withthe current thread.  Returns

         * null if the calling thread is notassociated with a Looper.

         */

     

        //就是这个函数,返回和线程相关的looper

        public static final Looper myLooper() {

            return (Looper)sThreadLocal.get();

        }

     

    ......

       

        /**

         * Return the {@link MessageQueue} objectassociated with the current

         * thread. This must be called from a thread running a Looper, or a

         * NullPointerException will be thrown.

         */

        public static final MessageQueue myQueue(){

            return myLooper().mQueue;

        }

        /*建一个新的looper对象,内部分配一个消息队列,设置mRun为true,注意这是私有函数,外界无法创建looper对象的原因*/

        private Looper() {

            mQueue = new MessageQueue();

            mRun = true;

            mThread = Thread.currentThread();

        }

     

        public void quit() {

            Message msg = Message.obtain();

            // NOTE: By enqueueing directly intothe message queue, the

            // message is left with a nulltarget.  This is how we know it is

            // a quit message.

            mQueue.enqueueMessage(msg, 0);

        }

     

        /**

         * Return the Thread associated with thisLooper.

         */

        public Thread getThread() {

            return mThread;

        }

        ......

        ......

    }

     

    (2)handler

        所在文件handler.java,handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等,定义如下:

    class Handler{

    ..........

    //handler默认构造函数

    public Handler() {

            if (FIND_POTENTIAL_LEAKS) {

                final Class<? extendsHandler> klass = getClass();

                if ((klass.isAnonymousClass() ||klass.isMemberClass() || klass.isLocalClass()) &&

                        (klass.getModifiers() &Modifier.STATIC) == 0) {

                    Log.w(TAG, "The followingHandler class should be static or leaks might occur: " +

                        klass.getCanonicalName());

                }

            }

    //再次用到该函数,获取本线程的looper对象

            mLooper = Looper.myLooper();

            if (mLooper == null) {

                throw new RuntimeException(

                    "Can't create handlerinside thread that has not called Looper.prepare()");

            }

     

        /*这里是消息通知机制的妙处所在,也是looper和handler的密切联系所在,这样他们就共用一个 Queue了,有木有!这样的话,handler就有理由添加消息到共用队列里了*/

            mQueue = mLooper.mQueue;

            mCallback = null;

        }

     

        /*handler有四个构造函数,上面的是无参构造函数,looper可以是线程自己的也可以由你来提供*/

            public Handler(Looperlooper) {

            mLooper = looper;

            mQueue = looper.mQueue;

            mCallback = null;

        }

     

    public Handler(Looper looper,Callback callback) {

            mLooper = looper;

            mQueue = looper.mQueue;

            mCallback = callback;

        }

     

    //消息发送函数,调用了内部的一个sendMessageDelayed

    public final boolean sendMessage(Messagemsg)

        {

            return sendMessageDelayed(msg, 0);

        }

     

        //调用sendMessageAtTime,这里只是进行一次相对时间和绝对时间的转换

     public final booleansendMessageDelayed(Message msg, long delayMillis)

        {

            if (delayMillis < 0) {

                delayMillis = 0;

            }

            return sendMessageAtTime(msg,SystemClock.uptimeMillis() + delayMillis);

        }

     

     

    public booleansendMessageAtTime(Message msg, long uptimeMillis)

        {

            boolean sent = false;

            MessageQueue queue = mQueue;

            if (queue != null) {

     

    /*把消息的target设置为自己,然后加入到消息队列中,所以当looper类中的loop()函数调用msg.target.dispatchmessage(),就是是调用这个handler的dispatchmessage()*/

                msg.target = this;

                sent = queue.enqueueMessage(msg,uptimeMillis); /*实际上添加到looper类中的成员变量mQueue的队列中*/

            }

            else {

                RuntimeException e = newRuntimeException(

                    this + "sendMessageAtTime() called with no mQueue");

                Log.w("Looper",e.getMessage(), e);

            }

            return sent;

    }

     

    //就是这个函数,looper中的loop()函数里面是不是有一个该函数的调用

    public voiddispatchMessage(Message msg) {

    /*如果msg本身设置了callback,则直接交给这个callback处理了,所以我前面说它不一定真的调用handler的handleMessage()函数*/

            if (msg.callback != null) {

                handleCallback(msg);

            } else {

    //如果判断msg.callback为空,该handler的callback有定义,就用它了,注意这里的handleMessage()是handler的,在handler里面该函数什么都不做

              if (mCallback != null) {

                    if(mCallback.handleMessage(msg)) {

                        return;

                    }

               }

    //这里是handler的派生类,所以handler的子类要重载该函数

                handleMessage(msg);

            }

        }

    ..........

    }

     

    (3)Thread

        为什么在开发过程中我们需要重新创建线程呢?大家知道我们创建的Service、Activity以及Broadcast均是一个主线程处理,这里我们可以理解为UI线程。但是在操作一些耗时操作时,比如 I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们可以考虑使用Thread线程来解决。

             每个线程都与looper对象一一对应,对于从事过J2ME开发的程序员来说Thread比较简单,直接匿名创建重写run方法,调用start方法执行即可。

        所在的文件Thread.java,线程负责调度整个消息循环,即消息循环的执行场所,代码如下:

    public class Thread implementsRunnable {

     

    private staticfinal int NANOS_PER_MILLI = 1000000;

     

    /* some of theseare accessed directly by the VM; do not rename them */

        volatile VMThread vmThread;

        volatile ThreadGroup group;

        volatile boolean daemon;

        volatile String name;

        volatile int priority;

        volatile long stackSize;

        Runnable target;

        private static int count = 0;

     

    ……

    ……

    /**

         * Constructs a new {@code Thread} with a{@code Runnable} object and name

         * provided. The new {@code Thread} willbelong to the same {@code

         * ThreadGroup} as the {@code Thread}calling this constructor.

         *

         * @param runnable

         *            a {@code Runnable} whose method<code>run</code> will be

         *            executed by the new {@code Thread}

         * @param threadName

         *            the name for the {@code Thread}being created

         *

         * @see java.lang.ThreadGroup

         * @see java.lang.Runnable

         *

         * @since Android 1.0

         */

        /*Thread的构造函数实在是多,这里不必一一列举,总之,他们都是通过调用Create()函数来实现*/

        public Thread(Runnable runnable, StringthreadName) {

            if (threadName == null) {

                throw new NullPointerException();

            }

     

            create(null, runnable, threadName, 0);

        }

    /**

         * Constructs a new {@code Thread} with no{@code Runnable} object and the

         * name provided. The new {@code Thread}will belong to the same {@code

         * ThreadGroup} as the {@code Thread}calling this constructor.

         *

         * @param threadName

         *            the name for the {@code Thread}being created

         *

         * @see java.lang.ThreadGroup

         * @see java.lang.Runnable

         *

         * @since Android 1.0

         */

        /*因为经常会用到这个构造函数,所以在这里列出来,我们后面以Wifi为例说明时就是用的这个函数*/

        public Thread(String threadName) {

            if (threadName == null) {

                throw new NullPointerException();

            }

     

            create(null, null, threadName, 0);

        }

     

    /*这是参数最全的构造函数

    publicThread(ThreadGroup group, Runnable runnable, String threadName, long stackSize){

            if (threadName == null) {

                throw new NullPointerException();

            }

            create(group, runnable, threadName,stackSize);

        }

     

    ……

    ……

     

    /**

         * Initializes a new, existing Threadobject with a runnable object,

         * the given name and belonging to theThreadGroup passed as parameter.

         * This is the method that the severalpublic constructors delegate their

         * work to.

         *

         * @param group ThreadGroup to which thenew Thread will belong

         * @param runnable a java.lang.Runnablewhose method <code>run</code> will

         *       be executed by the new Thread

         * @param threadName Name for the Threadbeing created

         * @param stackSize Platform dependentstack size

         * @throws SecurityException if<code>group.checkAccess()</code> fails

         *        with a SecurityException

         * @throws IllegalThreadStateException if<code>group.destroy()</code> has

         *        already been done

         * @see java.lang.ThreadGroup

         * @see java.lang.Runnable

         * @see java.lang.SecurityException

         * @see java.lang.SecurityManager

         */

        private void create(ThreadGroup group,Runnable runnable, String threadName, long stackSize) {

            SecurityManager smgr =System.getSecurityManager();

            if (smgr != null) {

                if (group == null) {

                    group = smgr.getThreadGroup();

                }

     

                /*

                 * Freaky security requirement: Ifthe Thread's class is actually

                 * a subclass of Thread and ittries to override either

                 * getContextClassLoader() orsetContextClassLoader(), the

                 * SecurityManager has to allowthis.

                 */

                if (getClass() != Thread.class) {

                    Class[] signature = new Class[]{ ClassLoader.class };

     

                    try {

                       getClass().getDeclaredMethod("getContextClassLoader",signature);

                        smgr.checkPermission(newRuntimePermission("enableContextClassLoaderOverride"));

                    } catch (NoSuchMethodExceptionex) {

                        // Ignore. Just interestedin the method's existence.

                    }

     

                    try {

                       getClass().getDeclaredMethod("setContextClassLoader",signature);

                        smgr.checkPermission(newRuntimePermission("enableContextClassLoaderOverride"));

                    } catch (NoSuchMethodExceptionex) {

                        // Ignore. Just interested in the method'sexistence.

                    }

                }

            }

        //获得当前线程

            Thread currentThread =Thread.currentThread();

            if (group == null) {

                group =currentThread.getThreadGroup();

            }

     

            group.checkAccess();

            if (group.isDestroyed()) {

                throw newIllegalThreadStateException("Group already destroyed");

            }

     

            this.group = group;

     

            synchronized (Thread.class) {

                id = ++Thread.count;

            }

     

            if (threadName == null) {

                this.name = "Thread-" +id;

            } else {

                this.name = threadName;

            }

     

    /*runnable是一个接口,声明了一个Run()函数,Thread实现了该函数,它实际上真正的线程体*/

            this.target = runnable;

            this.stackSize = stackSize;//线程的堆栈大小

     

            this.priority =currentThread.getPriority();//线程的优先级

     

            this.contextClassLoader =currentThread.contextClassLoader;

     

            // Transfer overInheritableThreadLocals.

            if (currentThread.inheritableValues !=null) {

                inheritableValues

                        = newThreadLocal.Values(currentThread.inheritableValues);

            }

     

            // store current AccessControlContextas inherited context for this thread

            SecurityUtils.putContext(this,AccessController.getContext());

     

            // add ourselves to our ThreadGroup ofchoice

            this.group.addThread(this); //将当前线程加入线程组

        }  

    ……

    ……

     

    /**

         * Starts the new Thread of execution. The<code>run()</code> method of

         * the receiver will be called by thereceiver Thread itself (and not the

         * Thread calling<code>start()</code>).

         *

         * @throws IllegalThreadStateException ifthe Thread has been started before

         *

         * @see Thread#run

         *

         * @since Android 1.0

         */

    //这个函数用来启动一个新的线程,不过要注意的是,调用该函数后,线程并未真正运行,run()函数才是真正的线程体,这时它只是做好了运行准备,待系统在适当时机运行*/

    public synchronizedvoid start() {

            if (hasBeenStarted) {

                throw newIllegalThreadStateException("Thread already started."); // TODOExternalize?

            }

     

            hasBeenStarted = true;

     

            VMThread.create(this, stackSize);

        }

     

    ……

    ……

        对于Thread类,上面只列出了几个重要函数,在讨论本文话题中,其实只需要知道一个线程的创建和启动方法就行了,Thread对于本文话题的重要部分在其子类的run()线程体代码的编写上。

     

    (4)简单的例子

        有了上面的讲解你是不是有所理解了呢?好,我们先来创建一个非常简单的例子,只写出重要部分,不花时间考虑代码的完整和严谨性,以使例子更易于理解:

     

    /*根据上面代码的讲解,我们很容易将looper和thread联系起来,并且建立looper和handler的联系,首先我们要从Thread派生一个出一个子类*/

    class HandleThread extendsThread{

        pubic HandleThread(){

            super(“xxx”);

    }

        Looper myLooper = null;

     

    //重载run函数

        run(){

            Looper.prepare();//将Looper设置到这个线程中

            myLooper = Looper.myLooper();

            myLooper.loop();开启消息循环

    }

    ……

    ……

    }

    ......

    ......

     

    //创建一个线程

    onCreate(...){

    ……

    ……

        HandleThread  threadtest= new HandleThread();  //定义一个测试线程

        threadtest.start(); //启动测试线程

     

        Looper looper = threadtest.myLooper;    //再次用到myLooper获得线程拥有的looper

     

    /*将looper作为参数创建handler对象,从前面内容,知道Handlertest对象可以直接操作looper的消息队列,因为它们共用一个mQueue*/

    Handler Handlertest = new Handler(looper);

       Handlertest.sendMessage(...);//向mQueue消息队列添加消息

    ……

    ……

    }

        通过上面的例子,相信你已经对这三个类和他们之间的关系有一定的了解了吧,对于MessageQueue类,说白了,就是用来表示一个队列的类,后面会有图示,就不用过多介绍了。

     

    (5)handlerthread

        我们上面的例子是超级简单的,在android中handlerThread类会稍微复杂一些,下面就来看看它的代码:

    /**

     * Copyright (C) 2006 TheAndroid Open Source Project

     *

     * Licensed under theApache License, Version 2.0 (the "License");

     * you may not use thisfile except in compliance with the License.

     * You may obtain a copyof the License at

     *

     *     http://www.apache.org/licenses/LICENSE-2.0

     *

     * Unless required byapplicable law or agreed to in writing, software

     * distributed under theLicense is distributed on an "AS IS" BASIS,

     * WITHOUT WARRANTIES ORCONDITIONS OF ANY KIND, either express or implied.

     * See the License forthe specific language governing permissions and

     * limitations under theLicense.

     */

     

    package android.os;

     

    /***

     * Handy class forstarting a new thread that has a looper. The looper can then be

     * used to create handlerclasses. Note that start() must still be called.

     */

    public class HandlerThread extends Thread {

        private intmPriority;  //线程优先级

        private int mTid =-1;  //线程ID

        private LoopermLooper; //looper

     

        //常用的handlerthread构造函数

        publicHandlerThread(String name) {

            super(name);

            mPriority =Process.THREAD_PRIORITY_DEFAULT;

        }

       

        /***

         * Constructs aHandlerThread.

         * @param name

         * @param priorityThe priority to run the thread at. The value supplied must be from

         * {@linkandroid.os.Process} and not from java.lang.Thread.

         */

        publicHandlerThread(String name, int priority) {

            super(name);

            mPriority =priority;

        }

       

        /***

         * Call back methodthat can be explicitly over ridden if needed to execute some

         * setup beforeLooper loops.

         */

        protected void onLooperPrepared() {

        }

     

        //线程主体,创建了looper对象,并进入消息处理循环

        public void run() {

            mTid =Process.myTid();

            Looper.prepare();   //初始化looper

            synchronized(this) {

                mLooper =Looper.myLooper();

                notifyAll();    //唤醒原语

            }

           Process.setThreadPriority(mPriority);

           onLooperPrepared();

            Looper.loop();  //消息处理循环

            mTid = -1;

        }

       

        /***

         * This methodreturns the Looper associated with this thread. If this thread not been started

         * or for any reasonis isAlive() returns false, this method will return null. If this thread

         * has been started,this method will block until the looper has been initialized. 

         * @return Thelooper.

         */

        public Looper getLooper() {

            if (!isAlive()) {

                return null;

            }

           

            // If the threadhas been started, wait until the looper has been created.

            synchronized(this) {

                while(isAlive() && mLooper == null) {

                    try {

                       wait();

                    } catch(InterruptedException e) {

                    }

                }

            }

            return mLooper;

        }

       

        /***

         * Ask the currentlyrunning looper to quit.  If the threadhas not

         * been started orhas finished (that is if {@link #getLooper} returns

         * null), then falseis returned.  Otherwise the looper isasked to

         * quit and true isreturned.

         */

        public boolean quit(){

            Looper looper =getLooper();

            if (looper !=null) {

               looper.quit();

                return true;

            }

            return false;

        }

       

        /***

         * Returns theidentifier of this thread. See Process.myTid().

         */

        public intgetThreadId() {

            return mTid;

        }

    }

        介绍过了handlerthread,那么下面就可以来探讨挖掘出他们在android消息处理机制中,各自扮演的角色和相互的联系了。

     

    二,android消息处理机制中各类之间关系

    (一)MessageQueue,looper和handler的职责及相互之间的关系。

    (1)职责描述:

    Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。

    Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。

    MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

    Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。这个类用于为线程执行一个消息循环。Looper的实例与一个线程绑定,但是线程默认是没有Looper对象与其绑定的,可以通过在线程上调用Looper.prepare()绑定一个Looper对象到当前线程,之后调用Looper.loop()方法执行消息循环,直到循环被停止(遇到一个target=null的消息,就会退出当前消息循环)。

     

    (2)关系描述:

    Handler,Looper和MessageQueue就是简单的三角关系,如下图示。

    Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue。而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue。

    所以,多个Handler都可以共享同一Looper和MessageQueue了。当然,这些Handler也就必须要运行在同一个线程里。


    图2.1.1 Handler,Looper和MessageQueue关系图


             下图是从Froyo从抽取出的HandlerThread、Looper和MessageQueue的关系图。他们被定义在package android.os。

     

    图示2.1.2 HandlerThread/Looper/MessageQueue实例关系

     

    (二)looper和handlerThread之间联系的动态建立过程(重点)

       

     

    图示2.2.1 messageQueue,looper和handlerthread实例化过程

     

        HandlerThread是一个Thread的子类,在它被实例化并执行start()之后,它的run()方法会在它自己的线程空间执行。上面我们已经提到了Looper的实例化是通过Looper::prepare实现的,图中可以看到Looper::prepare()正是在HandlerThread::run()中被调用而实例化的。而MessageQueue是在Looper的私有构造函数Looper()中实例化的,这些可以回头参考上面的代码。至此,messageQueue,looper和handlerthread实例化过程结束,后面调用loop()进入消息处理循环。当然,如果你想在工作线程中(这个子线程)处理消息的话,在进入消息处理循环之前,你也可以定义并初始化handler对象。

        总结一下,HandlerThread是被显式地通过new创建的实例,而与它绑定在一起的Looper是在HandlerThread的执行过程中被实例化的,相应的MessageQueue也是在这个过程中实例化的。

        上图说是它们实例化过程,其实并不完全是,刚才提到了loop()这个函数,它可是android消息机制里面非常重要的一个函数,前面第一部分代码中有说过它的工作内容,这里我们用工作流程图来分析:

     

    图示2.2.2 Looper::loop()函数的工作流程图

     

        这里是不是一目了然了,Looper::loop()是这个消息处理的核心,图中的所有序列是发生在一个无限循环中的。mQueue:MessageQueue是Looper保留的一份引用,通过它的next()[序列1]获取 MessageQueue中的下一个要处理的消息,这个过程中如果没有相应的消息,执行它的线程会用this.wait()释放它所拥有的MessageQueue的对象锁而等待。一旦有消息到来[序列2],Looper会用获得的Message的Handler(msg.target)来分发处理消息[序列3&4]。消息处理完之后,还要回收[序列5]。

     

    (三)以Wifi模块为例,代码分析android的消息机制

        在Looper::loop()消息处理的顺序图里看到了Handler,这个消息分发处理的参与者。下面结合WifiHandler这个具体的Handler看它如何参与到消息的发送、分发和处理的。

        (1)handler的实例化,WifiService中定义了如下的WifiHandler。

    图示2.3.1: handler和wifihandler类静态关系图

     

        由图示可以看出Handler的构造函数是需要Looper的,从上面的分析知道,Looper是在HandlerThread执行中实例化的, HandlerThread保留着Looper的应用mLooper,并可通过getLooper()被外面 获取。而Handler的mQueue: MessageQueue可以通过mLooper.mQueue获得。所以,Wifi的HandlerThread,WifiHandler可以这样实例化: 

    {

    ......

        HandlerThread wifiThread = new HandlerThread("WifiService");  

        wifiThread.start();  

        mWifiHandler = new WifiHandler(wifiThread.getLooper()); 

    ......

    }

        (2)消息发送

     

    图示2.3.2:消息发送

     

        通过Message::obtain()可以建立起Message和Target/Handler,what之间的关系,并得到一个Message msg[序列1&2];然后通过msg.sendToTarget()就可以用Handler来具体发送消息了[序列3&4];通过一系 列的调用,最后会通过MessageQueue::enqueueMessage()把消息放到mMessages上[序列7],还会通过 this.notify()通知正在等待新消息的线程,重新拥有MessageQueue的对象锁,而处理该消息。

    图示2.3.3:MessageQueue与Message的关系

     

             (4)消息处理

        具体处理消息,整个框架的最后消息的处理是通过Handler::handleMessager(msg: Message)来完成。所以如果有自己具体要处理的消息,只要override Handler的handleMessage(msg: Message)方法,并加入自己特定的对消息的处理即可。要处理消息,就看看Message的属性里都有什么。

    图示2.3.4:message类图

     

             重点关注public属性what是区分具体什么消息;可以带参数arg1, arg2。至此,消息的发送,分发和处理就介绍完毕了。

     

    三,以线程的角度分析,使用该机制的技巧和注意的问题

        在android中,Activity,Service属于主线程,在主线程中才能更新UI,如toast等。其他线程中不能直接使用更新UI,这时可以使用Handler来处理,Handler可以在Activity和Service中定义。

        熟悉Windows编程的朋友可能知道Windows程序是消息驱动的,并且有全局的消息循环系统。而Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制。实际上谷歌参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制。Android通过 Looper、Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。本文深入介绍一下 Android消息处理系统原理。

        Android系统中Looper负责管理线程的消息队列和消息循环,具体实现请参考上面的Looper的源码。 可以通过Loop.myLooper()得到当前线程的Looper对象(这是我们一再强调的重点),通过调用Loop.getMainLooper()可以获得当前进程的主线程的Looper对象。

        前面提到Android系统的消息队列和消息循环都是针对具体线程的,一个线程可以存在(当然也可以不存在)一个消息队列和一个消息循环(Looper)。特定线程的消息只能分发给本线程,不能进行跨线程,跨进程通讯。但是创建的工作线程默认是没有消息循环和消息队列的,如果想让该线程具有消息队列和消息循环功能,需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。

        如下例 所示:

    class LooperThread extends Thread {

        public HandlermHandler;

        public void run() {

            Looper.prepare();

            mHandler = newHandler(Looper.myLooper()) {

                public voidhandleMessage(Message msg) {

                // processincoming messages here

                }

            };

            Looper.loop();

        }

    }

        这样你的线程就具有了消息处理机制了,在Handler中进行消息处理。

        Activity是一个UI线程,运行于主线程中,Android系统在启动的时候会为Activity创建一个消息队列和消息循环(Looper)。详细实现请参考ActivityThread.java文件。 Handler的作用是把消息加入特定的(Looper)消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定则利用当前线程的Looper创建。

        一个Activity中可以创建多个工作线程或者其他的组件,如果这些线程或者组件把他们的消息放入Activity的主线程消息队列,那么该消息就会在主线程中处理了。因为主线程一般负责界面的更新操作,并且Android系统中的weget不是线程安全的,所以这种方式可以很好的实现 Android界面更新。在Android系统中这种方式有着广泛的运用。

        那么另外一个线程怎样把消息放入主线程的消息队列呢?答案是通过Handle对象,只要Handler对象以主线程中创建,并用主线程的的Looper创建,那么调用 Handler的sendMessage等接口,将会把消息放入队列都将是放入主线程的消息队列(这是handler与looper共用一个MessageQueue的结果)。并且将会在Handler主线程中调用该handler 的handleMessage接口来处理消息。

        这里面涉及到线程同步问题,请先参考如下例子来理解Handler对象的线程模型:

    (1)首先创建MyHandler工程。

    (2)在MyHandler.java中加入如下的代码:

    package com.simon;

    import android.app.Activity;

    import android.os.Bundle;

    import android.os.Message;

    import android.util.Log;

    import android.os.Handler;

    public class MyHandler extends Activity {

        static final StringTAG = "Handler";

        Handler h = newHandler(){ //创建处理对象,并定义handleMessage()函数

            public voidhandleMessage (Message msg)
            {
                switch(msg.what)
                {
                    case HANDLER_TEST:
                    Log.d(TAG, "Thehandler thread id = " + Thread.currentThread().getId() + "\n");
                    break;
                }
            }
        };

    static final int HANDLER_TEST = 1;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "The main thread id =" + Thread.currentThread().getId() + "\n");

        newmyThread().start(); //创建子线程对象,并启动子线程

        setContentView(R.layout.main);
    }

    class myThread extends Thread

    {

    //这个线程计较简单,Run()函数只是调用了在主线程中生成的处理类对象的一个发送消息的函数,没有单独生成looper进入循环

        public void run()
        {
            Message msg = new Message();

            msg.what =HANDLER_TEST;
            h.sendMessage(msg);
            Log.d(TAG, "The worker threadid = " + Thread.currentThread().getId() + "\n");
        }
    }
    }

        在这个例子中我们主要是打印,这种处理机制各个模块的所处的线程情况。如下是机器运行结果:

        DEBUG/Handler(302):The main thread id = 1
        DEBUG/Handler(302): The worker threadid = 8
        DEBUG/Handler(302): The handler threadid = 1

        我们可以看出消息处理是在主线程中处理的,在消息处理函数中可以安全的调用主线程中的任何资源,包括刷新界面。工作线程和主线程运行在不同的线程中,所以必须要注意这两个线程间的竞争关系。

        上例中,你可能注意到在工作线程中访问了主线程handler对象,并在调用handler的对象向消息队列加入了一个消息。这个过程中会不会出现消息队列数据不一致问题呢?答案是handler对象不会出问题,因为handler对象管理的Looper对象是线程安全的,不管是加入消息到消息队列 和从队列读出消息都是有同步对象保护的。上例中没有修改handler对象,所以handler对象不可能会出现数据不一致的问题。

        通过上面的分析,我们可以得出如下结论:

        1、如果通过工作线程刷新界面,推荐使用handler对象来实现。

        2、注意工作线程和主线程之间的竞争关系。推荐handler对象在主线程中构造完成(并且启动工作线程之后不要再修改之,否则会出现数据不一致),然后在工作线程中可以放心的调用发送消息SendMessage等接口。

        3、除了2所述的hanlder对象之外的任何主线程的成员变量如果在工作线程中调用,仔细考虑线程同步问题。如果有必要需要加入同步对象保护该变量。

        4、handler对象的handleMessage接口将会在主线程中调用。在这个函数可以放心的调用主线程中任何变量和函数,进而完成更新UI的任务。

        5、Android很多API也利用Handler这种线程特性,作为一种回调函数的变种,来通知调用者。这样Android框架就可以在其线程中将消息发送到调用者的线程消息队列之中,不用担心线程同步的问题。

        6. 下面再从线程的角度看一下,消息处理过程中参与的线程,以及这些线程之间的同步。显然的,这里有线程HandlerThread的参 与,而且Looper::loop()就是执行在HandlerThread的run()方法里,也就是在HandlerThread里执行,这也就是说 消息的分发处理和执行是在HandlerThread的线程上下文中。另外,还有至少一个线程存在,也就是创建了HandlerThread的线程B,以 及执行消息发送的线程C,B和C有可能是同一线程。

        消息的发送是在另外一个线程里,就是因为有了多个线程的存在,才有了线程的同步操作。可再次关注一下实现线程同步的Java原语wait()/notify()。

     

     

    参考资料:

    http://www.android123.com.cn/androidkaifa/422.html

    http://www.android123.com.cn/androidkaifa/422.html

    http://www.th7.cn/Article/bc/Android/201102/20110227210039.html

     


    展开全文
  • 为更好推动政务信息资源整合共享,根据《国务院关于印发政务信息资源共享管理暂行办法的通知》(国发〔2016〕51号)、《国务院关于印发“十三五”国家信息化规划的通知》(国发〔2016〕73号)等有关要求,国家又制定...


    为更好推动政务信息资源整合共享,根据《国务院关于印发政务信息资源共享管理暂行办法的通知》(国发〔2016〕51号)、《国务院关于印发“十三五”国家信息化规划的通知》(国发〔2016〕73号)等有关要求,国家又制定了《政务信息系统整合共享实施方案》(国办发〔2017〕39号)。《管理办法》是专门针对信息资源共享的指导性文件,提出了信息资源共享的原则和要求,而《实施方案》的出台,有望解决困扰我国政务信息化发展多年的老大难问题,从根本上改变“各自为政、条块分割、烟囱林立、信息孤岛”的局面,让信息多跑路,让群众少跑腿,让治理更有效,让人民更有获得感。

    1

    政务信息资源整合共享的难点分析


    (一)政务服务标准不统一,缺乏统一规划





    当前我国各地区各部门信息资源建设分散,缺乏统一的技术标准规范。各地区各部门建设的信息资源分布在各系统数据库中,信息资源分布碎片化,维护更新缺乏统一管理,查询定位效率低。同时,我国关于政务信息资源建设相关的标准规范少,各地区各部门缺乏统一的技术指导和标准约束。不同部门、不同地区、不同层级的政府之间采用的数据标准和分类方法五花八门,极大增加了数据共享的时间和成本。


    (二)政务服务平台不统一,业务难协同




    信息资源整合共享利用服务缺乏统一平台。目前,各级政府部门业务应用系统建设取得了一定的成绩,这些系统有的以办公业务系统为主,有的侧重专业应用服务,也有的提供信息资源服务,但还不能满足信息资源整合共享利用服务的需求。在国家层面,信息资源共享以点对点数据交换、数据报送或门户网站的数据公布等方式为主,尚未建设统一的信息资源整合共享利用服务平台。


    (三)政务服务信息不共享,数据难应用




    跨地区跨部门的信息资源建设缺乏共享机制。目前,我国政府信息资源共享共用机制在政策法规、标准规范、数据公开等方面存在不足。实际应用中,需要的信息可能分布在不同部门,虽然部分地区部门已形成局部范围内的数据交换共享机制,但尚未形成大范围的信息资源普遍共享应用。


    各层级各部门政务服务数据不同源、信息分散、难以共享问题严重。各个部门的信息化系统往往只根据自身的业务需求开发建设,而对与其他部门实现信息资源共享和业务协同考虑不足,政务数据资源呈现“纵强横弱”的现象,即只能在本部门条线内上下贯通,却无法与其他部门的信息资源实现横向共享,重复采集、一数多源、数据打架的情况十分普遍。各级政府部门占据着全社会 80%的信息资源,长期以来由于缺少有效的开发与管理,政府的信息资源散落在各个独立的政府部门,既没有作为资源开发,也没有让这些宝贵的信息资源为经济社会发展服务。


    2

    推进政务信息资源整合共享工作的几点思考



    首先,要建立信息资源共建共享服务机制。

    一方面建立信息资源共建工作机制,从组织领导、协调联络、任务分工、考核评价等方面制定健全的工作机制,协调好部门关系,合理分配工作任务,围绕数据建设、管理等内容,有效落实工作责任,做好监督考评,保证信息资源开发利用工作顺利开展。

    另一方面建立信息资源共享服务机制。从权限管理、维护更新和服务模式等方面制定信息资源共享服务机制,形成切实可行的长效共享服务机制。


    其次,要加强共享支撑能力建设

    一是加快建设我国电子政务内网政府系统业务网,进一步提升我国电子政务外网的服务能力,为各类政府专网应用迁移和网络对接提供安全可靠的网络支撑。

    二是加快建设完善我国电子政务内网和外网数据共享交换平台,逐步构建多级互联的数据共享交换平台体系,为各类政务信息实现汇聚交换和互认共享提供便捷高效的平台支撑。

    三是加快推进我国政务信息共享网站建设,为政府部门间跨地区、跨层级的信息共享与业务协同打造“公共服务窗口”。


    最后,要构建政务信息共享标准体系。建立健全政务信息资源数据采集、数据质量、目录分类与管理、共享交换接口、共享交换服务、多级共享平台对接、平台运行管理、网络安全保障等方面的标准,推动标准试点应用工作。研究制定统一信息资源分类指标体系。信息分类对数据组织管理和服务具有重要作用,是数据交换和共享的保障。


    目前,信息资源分类方式众多,任何单一的信息资源分类都有一定的适用范围和局限性,不能满足所有信息资源的分类需要。因此,应加紧研究制定统一信息资源分类指标体系,在梳理内部信息资源,掌握信息总体情况的同时,也要提出分类依据、原则,汇集形成统一信息资源分类指标体系。


    展开全文
  • 目录:一、术语session二、HTTP协议与状态保持三、理解cookie机制四、理解session机制五、理解javax.servlet.http.HttpSession六、HttpSession常见问题七、跨应用程序的session共享八、总结 一、术语session在我的...

    目录:
    一、术语session
    二、HTTP协议与状态保持
    三、理解cookie机制
    四、理解session机制
    五、理解javax.servlet.http.HttpSession
    六、HttpSession常见问题
    七、跨应用程序的session共享

    八、总结

     

    一、术语session
    在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。
    session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。
    然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义, “面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者 “一个POP3 session”③。
    而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session 里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。

    鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥


    二、HTTP协议与状态保持
    HTTP 协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。
    然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、 cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。
    让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
    1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
    2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
    3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。
    由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。


    三、理解cookie机制
    cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。
    正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。
    而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家
    店的会员卡。
    cookie的内容主要包括:名字,值,过期时间,路径和域。
    其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。
    路径与域合在一起就构成了cookie的作用范围。
    如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为
    浏览器会话期的 cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。
    如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。
    存储在硬盘上的cookie 可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按 Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于 Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。
    下面就是一个goolge设置cookie的响应头的例子
    HTTP/1.1 302 Found
    Location: http://www.google.com/intl/zh-CN/
    Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun,
    17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
    Content-Type: text/html

    这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分
    浏览器在再次访问goolge的资源时自动向外发送cookie
    使用Firefox可以很容易的观察现有的cookie的值,使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。
    IE也可以设置在接受cookie前询问,这是一个询问接受cookie的对话框。


    四、理解session机制
    session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个 session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 session id将被在本次响应中返回给客户端保存。
    保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID= yOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
    另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
    <form name="testform" action="/xxx">
    <input type="text">
    </form>
    在被传递给客户端之前将被改写成
    <form name="testform" action="/xxx">
    <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
    <input type="text">
    </form>
    这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。实际上这种技术可以简单的用对action应用URL重写来代替。
    在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个 session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。
    恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。


    五、理解javax.servlet.http.HttpSession
    HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。
    首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域, cookie的生存时间等。
    一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用, Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。
    复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。
    cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。
    cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。
    关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869


    六、HttpSession常见问题
    (在本小节中session的含义为⑤和⑥的混合)
    1、session在何时被创建
    一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <% @page session="false"%>关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的 session对象的来历。由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。
    2、session何时被删除
    综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id
    时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)
    3、如何做到在浏览器关闭时删除session
    严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。
    4、有个HttpSessionListener是怎么回事
    你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。
    5、存放在session中的对象必须是可序列化的吗
    不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。
    6、如何才能正确的应付客户端禁止cookie的可能性
    对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
    7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
    参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。
    8、如何防止用户打开两个浏览器窗口操作导致的session混乱
    这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。
    9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。
    10、为什么session不见了
    排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。


    七、跨应用程序的session共享
    常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。
    然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。
    首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从 Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。
    笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
    <session-info>
    <path>/NASApp</path>
    </session-info>
    需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。
    我们再看一下Weblogic Server是如何处理session的。
    从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下。对于这样一种结构,在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端 cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,应用程序A
    context.setAttribute("appA", session);
    关于session(jsp-servlet 技术)
    应用程序B
    contextA = context.getContext("/appA");
    HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");
    值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。
    那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA 访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。


    八、总结
    session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。
    摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

    展开全文
  • APNs(Apple Push Notification service-苹果推送通知服务) APNs官方文档 APNs是推送的核心。该服务与iOS设备建立起强大的持久连接通讯(和间接WatchOS,TVOS,和MacOS设备)。在早期的时候,iOS通过管理AppSSL认证...
  • 从原理上来看,进程通信的关键技术就是在进程间建立某种共享区,利用进程都可以访问共享区的特点来建立一些通信通道。如下图所示: 其实,以前设计程序时使用的全局变量,就是一种可以在各个函数之间进行通信的...
  • HTTP是一种无状态的协议,不知道链接是谁发起的,所以需要浏览器把登录信息存起来,Session和Cookie就是为解决这个问题而提出来的两个机制。 1.Cookie是服务器在本地机器上存储的小段文本,并随每一个请求发送...
  • 是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享内存段“连接到”他们自己的地址空间里去。所有进程都能访问共享内存中的地址。...
  • 在Android系统中,针对移动设备内存空间有限的特点,提供了一种在进程间共享数据的机制:匿名共享内存,它能够辅助内存管理系统来有效地管理内存,它的实现原理我们在前面已经分析过了。为了方便使用匿名共享内存...
  • Java线程等待唤醒机制(加深理解)

    万次阅读 多人点赞 2019-08-04 16:28:06
    这里就用到了线程等待唤醒机制,下面具体看一下。 等待唤醒机制示例 下面代码是一个简单的线程唤醒机制示例,主要就是在Activity启动的时候初始化并start线程,线程start后会进入等待状态,在onResume方法中执行...
  • RPF机制,PIM-DM工作机制,PIM-SM工作机制 一、组播的RPF机制 路由器在接收到由源S 向组播组G 发送的组播报文后,首先查找组播转发表。 如果存在对应(S,G)表项,且该组播报文实际到达接口与Incoming...
  • 共享经济-共享汽车市场调研报告

    万次阅读 2018-08-17 16:00:23
    共享经济市场调研报告 ——共享汽车市场调研报告 目录   第一章 共享经济概况. 4 第一节 行业介绍. 4 第二节 共享经济发展历程. 5 第三节 两种模式下的生命周期. 6 第四节 行业市场竞争程度. 7 第二章 ...
  • Android跨进程通信:图文详解 Binder机制 原理

    万次阅读 多人点赞 2017-06-22 10:31:24
    虽然 网上有很多介绍 Binder的文章,可是存在一些问题:浅显的讨论Binder机制 或 一味讲解 Binder源码、逻辑不清楚,最终导致的是读者们还是无法形成一个完整的Binder概念 本文采用 清晰的图文讲解方式,按照 大角度...
  • 上一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划简要介绍了Android系统进程间通信机制Binder的总体架构,它由Client、Server、Service Manager和驱动程序Binder四个组件构成。本文着重介绍组件...
  • android相关 第三方库 牛气控件特效组件 开源项目 文档及视频教程 工具插件 其他知识 其他已整合 自2015年4月14日开始定阅“daimajia”所有已发知识点的整理...recyclerview-animators:一个关于RecyclerView items的动
  • IOS后台运行机制 与 动作

    千次阅读 2014-07-15 19:11:04
    当应用返回foreground时,会有一个与之匹配的通知被发送,给应用提供重新建立session的机会。  (9)切向后台时,清除行为警告相关的资源。为了在应用相互切换之间保存应用上下文,当应用切向后台时,系统并不...
  • 所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...
  • ZooKeeper Watch机制

    千次阅读 2019-06-18 11:20:27
    多个分布式进程通过ZooKeeper提供的API来操作共享的ZooKeeper内存数据对象ZNode来达成某种一致的行为或结果,这种模式本质上是基于状态共享的并发模型,与Java的多线程并发模型一致,他们的线程或进程都是”共享...
  • (7)操作系统安全机制

    万次阅读 2017-12-30 10:22:19
    操作系统安全机制:自主访问控制、强制访问控制机制、客体重用机制、标识与鉴别机制、可信路径机制、安全审计
  • 【Linux】进程间通信之共享内存

    千次阅读 2016-07-28 21:04:06
    3、通知事件:一个进程需要向另一个或另一组进程发送消息,通知它们发生了某种事件。 4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够...
  • c++共享内存通信如何实现

    千次阅读 2020-01-13 12:11:23
    现在很多对性能要求高的项目都会支持共享内存的进程间通信(IPC)方式,本文会以百度Apollo自动驾驶项目为例,展示两种c++中实现共享内存通信的方式(对应linux中两种不同的机制)。
  • Chromium支持硬件加速渲染网页,即使用GPU渲染网页。在多进程架构下,Browser、Render和Plugin进程的GPU命令不是在本进程中执行的,而是转发...本文对Chromium硬件加速渲染机制的基础知识进行简要介绍和制定学习计划。
  • 实验六 共享存储区通信

    千次阅读 2009-04-11 19:47:00
    实验六 共享存储区通信实验目的了解和熟悉共享存储机制实验内容编制一长度为1k的共享存储区发送和接收的程序。实验指导一、共享存储区1、共享存储区机制的概念共享存储区(Share Memory)是UNIX系统中通信速度最高...
  • 5月16日,天津市发改委印发《关于加快居民小区公共充电桩建设实施方案》的通知,以需求为导向,结合老旧社区改造推动居民小区公共充电桩建设,缓解百姓充电难问题。2019年,全市各区确保完成100个小区1000台公共充电...
  • LINUX共享内存使用常见陷阱与分析

    千次阅读 2018-04-19 15:52:59
    所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...
  • HTTPS通信机制

    千次阅读 2016-02-20 22:13:12
    概述使用HTTP协议进行通信时,由于传输的是明文所以很容易遭到窃听,就算是加密过的信息也容易在传输中遭受到篡改,因此需要在HTTP协议基础上添加加密处理,认证处理等,有了这些处理机制的HTTP成为HTTPS。...
  • 管道、FIFO以及共享内存

    千次阅读 2013-04-20 10:16:55
    管道是UNIX IPC的最老形式,并且所有UNIX系统都提供此种通信机制,管道有两种限制; 它们是半双工的。数据只能在一个方向上流动。它们只能在具有公共祖先的进程之间使用 1.创建管道 函数pipe用于创建管道。...
  • MFC框架机制详解

    千次阅读 2017-05-25 20:04:47
    MFC框架机制详解
  • Session机制详解

    千次阅读 2012-06-29 00:35:03
    虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时...
  • Linux共享内存使用常见陷阱与分析

    千次阅读 2016-07-11 15:05:20
    所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,449
精华内容 35,779
关键字:

关于建立信息共享机制的通知