一 代码位置结构及相关类
..\packages\apps\Mms\src\com\android\mms\ui:存放界面显示相关的类
..\packages\apps\Mms\src\com\android\mms\data:存放界面显示需要的数据相关的类
主要的类:
ConversationList:信息对话界面——>ListActivity
ConversationListAdapter:适配器 ——>CursorAdapter
ConversationListItem:对话界面的ListItem View——>RelativeLayout
ConversationListItemData:对话界面列表显示需要的各项数据
Conversation:显示所需所有对话信息的所有数据
ContactList:每个Thread信息所对应的联系人
Contact:一个联系人数据信息
二 交互过程:
界面显示数据获取过程:
类交互过程:
二 界面数据查询更新流程图
三 代码实现过程分析
1 ConversationList中启动查询
onStart(){
……
startAsyncQuery();
}
实际上是:
startAsyncQuery() {
……
// mQueryHandler——>ThreadListQueryHandler ConversationList的内部类
//最终继承于AsyncQueryHandler
Conversation.startQueryForAll(mQueryHandler,THREAD_LIST_QUERY_TOKEN);
}
2 Conversation中调用异步查询线程
Conversation.startQueryForAll——>
public static void startQueryForAll(AsyncQueryHandler handler, int token) {
……
final AsyncQueryHandler queryHandler = handler;
queryHandler.postDelayed(new Runnable() {
//匿名内部类
public void run() {
queryHandler.startQuery(
queryToken, null, sAllThreadsUri,
ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
}
}, 10);
Conversations.DEFAULT_SORT_ORDER);
}
(AsyncQueryHandler使用条用者线程和工作线程组成)异步查询
此查询的是:所有Thread信息;
3 AsyncQueryHandler中查询过程
//各参数的含义
public void startQuery(int token, Object cookie, Uri uri,
String[] projection, String selection, String[] selectionArgs,
String orderBy) {
//启动一个工作者线程
mWorkerThreadHandler.sendMessage(msg);
}
——》工作线程查询完毕之后,返回到调用者线程;
——》执行AsyncQueryHandler的onQueryComplete函数;
——》 回到自行实现的继承于AsyncQueryHandler的类中 重写的onQueryComplete函数中;
——》执行到ThreadListQueryHandler的onQueryComplete函数中;
——》通知到ConversationList,至此查询Thread信息的过程结束;
数据据查询就是要使ContentProvider与数据库进行交互
在AsyncQueryHandler的内部类工作者线程WorkerHandler的函数handleMessage中完成;
AsyncQueryhandler:
A helper class to help make handling asynchronous ContentResolver queries easier.
AsyncQueryhandler中有两个handlerMessage,
一个是基于外部线程looper的,
一个是基于内部WorkerHandler实现的HandlerThread新线程的looper。
外部调用startQuery会通过mWorkerThreadHandler.sendMessage(msg)将查询发送给
WorkerHandler中处理,即在新线程中查询,
当WorkerHandler处理完后,把结果发送给AsyncQueryhandler的handlerMessage来调用对应的onXXXComplete函数。
这里就是把查询结果返回给原来线程来处理,这就通过两个handlerMessage实现了两个线程的消息交互。
AsyncQueryHandler实现步查询原理过程在此不作详细分析;
涉及到线程、Handle,Message等
ContentProvider如何与数据库进行交互在此不作详细分析;
4 ThreadListQueryHandler
属于ConversationList类的内部类:继承于AsyncQueryHandler;
重写抽象函数,接收查询结果的反馈;
简单看一下这个函数:
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case THREAD_LIST_QUERY_TOKEN:
……
// mListAdapter属于ConversationListAdapter
mListAdapter.changeCursor(cursor); //更新UI数据
}
}
——》至此工作将转移到ConversationListAdapter中进行;
——》生成所需要ViewIten和绑定UI显示所需要的数据;
ConversationListAdapter继承于CursorAdapter;
关于CursorAdapter功能及实现原理作用在此不作详细分析;
5 ConversationListAdapter
继承于:CursorAdapter;
简单看一下:ListView于Adapter以及Cursor的关系:
Adapter的作用就是ListView界面与数据之间的桥梁,
当列表里的每一项显示到页面时,都会调用Adapter的getView方法返回一个View
(对于CursorAdapter具体作用这里不作详细分析)
看一下CursorAdapter中的getView函数:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
……
View v;
//这里的作用很关键 决定要不要新创建一个ViewItem
if (convertView == null) {
//创建一个ViewItem
v = newView(mContext, mCursor, parent);
} else {
//涉及到Recycler机制 保证不会无限去创建Item,重复利用
v = convertView;
}
//将数据分配给所要显示的ViewItem
bindView(v, mContext, mCursor);
return v;
}
两个抽象函数abstract
newView:返回一个View,自定义ViewItem,需要重写;
bindView:绑定数据,需要重写;
下面看看 ConversationListAdapter对这两个函数的实现:
newView:
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// LayoutInflater获取res\layout\下的布局文件xml,并且实例化;
//这里就是ListViewItem
return mFactory.inflate(R.layout.conversation_list_item, parent, false);
}
(具体LayoutInflater的作用在此不详细分析)
看看conversation_list_item的布局:
<com.android.mms.ui.ConversationListItem xmlns:android="http:.....">
//联系人快捷标识 显示一张联系人图片 点击弹出相关功能:tell,msg
<android.widget.QuickContactBadge android:id="@+id/avatar"/>
//ViewItem可以容纳的控件
<ImageView android:id="@+id/presence"/>
<TextView android:id="@+id/from"/>
<TextView android:id="@+id/date"/>
<ImageView android:id="@+id/error"/>
<ImageView android:id="@+id/attachment"/>
<TextView android:id="@+id/subject"/>
</com.android.mms.ui.ConversationListItem>
bindView:
@Override
public void bindView(View view, Context context, Cursor cursor) {
//转化为信息列表的ListViewItem
ConversationListItem headerView = (ConversationListItem) view;
//使用cursor构建对话信息 关联信息数据和联系人数据
Conversation conv = Conversation.from(context, cursor);
//构建单个对话信息数据
ConversationListItemData ch = new ConversationListItemData(context, conv);
//绑定数据
headerView.bind(context, ch);
}
根据cursor所获取到的数据个数 循环构建;
三 联系人数据的查询
前面使用AsyncQueryHandler所获取到的cursor仅仅是查询了,所有对话信息数据;但是其中的联系人仅仅只是保存了其ID:recipientIds;
还需要根据此recipientIds获取其联系人的信息;这个就是在包装信息数据给ListItem使用的时候获取的;
Conversation conv = Conversation.from(context, cursor);获取联系人相关信息;
下面看下这个过程:
先从Conversation中的Cache中查找是否当前cursor所对应的Thread信息已存在于缓存中,
若存在则将其更新并返回;否则新创建,并将其加入到缓存Cache中;
public static Conversation from(Context context, Cursor cursor) {
long threadId = cursor.getLong(ID);
if (threadId > 0) {
//从Conversation缓存中查找cursor所对应的Thread信息
Conversation conv = Cache.get(threadId);
if (conv != null) {
//已存在缓存中 update the existing conv in-place
fillFromCursor(context, conv, cursor, false);
return conv;
}
}
//不存在于缓存中,新创建
Conversation conv = new Conversation(context, cursor, false);
Cache.put(conv);
return conv;
}
实际上不管是更新还是创建 都会走函数fillFromCursor();
那么下面看看这个函数都干了些什么事情;
private static void fillFromCursor(Context context, Conversation conv,
Cursor c, boolean allowQuery) {
synchronized (conv) {
//填写conv实例基本的对话信息 如ThreadId,date,count,attach,type等;
}
//获取cursor中联系人Ids;
String recipientIds = c.getString(RECIPIENT_IDS);
//通过recipientIds 获取对应的联系人数据:address,name……
ContactList recipients = ContactList.getByIds(recipientIds, allowQuery);
synchronized (conv) {
//关联联系人数据
conv.mRecipients = recipients;
//计算未读信息条数
}
}
(注意这里的synchronized的用法,这个是线程相关,这里不详细分析)
——》ContactList.getByIds
这里就转到ContactList类里面操作去了()这里还是mms的data包下里面的类;
1 ContactList
到此查询过程如下:
下面几个部分就围绕这个流程图进行详细介绍;
ContactList
此类从ArrayList继承下来:public class ContactList extends ArrayList<Contact>{}
public static ContactList getByIds(String spaceSepIds, boolean canBlock) {
ContactList list = new ContactList();
//foreach语句
for (RecipientIdCache.Entry entry : RecipientIdCache
.getAddresses(spaceSepIds)) { //根据Id获取号码 访问数据库
if (entry != null && !TextUtils.isEmpty(entry.number)) {
//根据号码获取联系人数据
Contact contact = Contact.get(entry.number, canBlock);
contact.setRecipientId(entry.id);
list.add(contact);
}
}
//返回联系人列表 给Conversation;
return list;
}
通过这里Contact contact = Contact.get(entry.number, canBlock); 传入号码
——》转到Contact里面执行;
下面看看这个类的get方法
2 Contact 异步或者阻塞方式获取联系人数据
number:联系人的号码
canBlock:将决定是以阻塞的方式还是异步的方式获取联系人数据
public static Contact get(String number, boolean canBlock) {
//调用的是ContactsCache类实例的get方法
return sContactCache.get(number, canBlock);
}
下面看一下ContactsCache里面的get方法
ContactsCache是Contact类的内部类;
public Contact get(String number, boolean canBlock) {
//返回一个contact不管数据库中是否存在 先从内部缓存中查找匹配号码的
//若不存在则直接将其返回,若不存在则返回新创建一个
Contact contact = get(number); //内部查找
Runnable r = null;
synchronized (contact) {
while (canBlock && contact.mQueryPending) { //是否阻塞方式
contact.wait(); }
final Contact c = contact; //匿名内部类实现线程
r = new Runnable() {
public void run() {
//仍然要在线程中更新,填充联系人数据 不管是否从缓存中取得
updateContact(c); }
}; }
if (canBlock) {
r.run(); //阻塞方式
} else {
pushTask(r); //异步方式 }
return contact;
}
通过此方法异步或者阻塞方式获取到的联系人数据 通过此get方法得到的联系人数据可能仅仅只是包含号码,而没有其他数据信息;
下面看一下updateContact方法
private void updateContact(final Contact c) {
Contact entry = getContactInfo(c.mNumber);
//从数据库中获取Contact数据
Contact entry = getContactInfo(c.mNumber);
//设置Contact实例c的数据信息:name id……
//notify to update who?
//who? Here It is ConversationListItem;
UpdateListener l;//从Contact的UpdateListener队列中获取一个Listener对象
//更新当前监听者所使用的Contact数据
l.onUpdate(c)
}
3 Contact中updateListener的添加
这里存在一个updateListener对象就是 ConversationListItem实例:什么时候传进去的呢?
看到 bind方法被调用时 也就是 上面所讲bindView时;
public final void bind(){
……
Contact.addListener(this); //添加UpdateListener
}
4 联系人处理方式
Contact处理联系人数据有两种方式:异步和阻塞;具体这里不作详细分析;
那么这里有个点让我不明白!
1) 加载一个Thread ListItem对应的联系人数据 可能有多个 是在一个ContactList getByIds方法中for循环执行;
2) 异步方式单独处理每一个号码对应的联系人数据
3) 将pushTask(r);加入到TaskStack中之后,放弃对CPU的控制权;
4) TaskStack中线程mWorkerThread;异步执行存在很多的不确定性,怎么控制;
5) 一次异步方式执行updateContact会去通知ItemList更新数据,为什么不是选择一个Thread信息所有的号码处理完毕 之后再去更新ItemList;
是这么个道理: 加入TaskStack中时;执行:
public void push(Runnable r) {
synchronized (mThingsToLoad) {
mThingsToLoad.add(r);
mThingsToLoad.notify(); //放弃资源控制
}
} 这里就等待getByIds将所有的号码处理加入到TaskStack中来处理;
但是仍然是每一次线程run方法都会去更新;也就是更新一个联系人就要更新UI一次。这样岂不浪费时间和资源;
这个mWorkerThread线程是在启动MmsApp时候就启动了
MmsApp.java中
@Override
public void onCreate(){
……
//创建ContactsCache对象,ContactsCache创建TaskStack对象
//TaskStack是ContactsCache内部类 其构造函数启动线程
Contact.init(this);
}
这个Mms这个联系人管理是比较的复杂!这里所认识的可能并一定正确;待后续完善。
五 数据更新到界面更新
回到ConversationListAdapter的函数bindView函数中来
@Override
public void bindView(View view, Context context, Cursor cursor) {
ConversationListItem headerView = (ConversationListItem) view;
Conversation conv = Conversation.from(context, cursor);
ConversationListItemData ch = new ConversationListItemData(context, conv);
headerView.bind(context, ch);
}
需要看一下headerView.bind所执行的bind函数:
//更新ListViewItem中控件的相关内容
void bind(Context context, final ConversationListItemData ch) {
……
// Date
mDateView.setText(ch.getDate());
// From.
mFromView.setText(formatMessage(ch));
// Register for updates in changes of any of the contacts in this conversation.
ContactList contacts = ch.getContacts();
Contact.addListener(this); // 有onUpdate函数
// Subject
mSubjectView.setText(ch.getSubject());
//avatar
updateAvatarView();
}
整个ConversationList界面到数据加载的主要过程便是这样。
这里仅仅只是一个框架性的对各个模块进行分析和介绍;
具体各个模块还需要进一步的详细分析。
其中可能存在有些地方并不正确,欢迎指正;
或将待后续深入了解之后进行更正更新。