Mms模块ConversationList流程分析

一 代码位置结构及相关类

..\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:一个联系人数据信息


二 交互过程:

界面显示数据获取过程:

Mms模块ConversationList流程分析_第1张图片

类交互过程:

Mms模块ConversationList流程分析_第2张图片

二 界面数据查询更新流程图

Mms模块ConversationList流程分析_第3张图片

三 代码实现过程分析

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实现了两个线程的消息交互。

Mms模块ConversationList流程分析_第4张图片

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具体作用这里不作详细分析)

Mms模块ConversationList流程分析_第5张图片


看一下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

       到此查询过程如下:

Mms模块ConversationList流程分析_第6张图片

下面几个部分就围绕这个流程图进行详细介绍;

         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方法

ContactsCacheContact类的内部类;  

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界面到数据加载的主要过程便是这样。

 

这里仅仅只是一个框架性的对各个模块进行分析和介绍;

具体各个模块还需要进一步的详细分析。

其中可能存在有些地方并不正确,欢迎指正;

或将待后续深入了解之后进行更正更新。


你可能感兴趣的:(Mms模块ConversationList流程分析)