`
kevinlynx
  • 浏览: 82221 次
  • 性别: Icon_minigender_1
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

系统自带短信程序源码部分分析

阅读更多
这里并不打算对整个短信源码进行分析,完全是看了某部分代码后的自我总结。我从GIT上clone了Conversation(即短信程序)的所有源码,结果编译不过。不过这对分析它的源码并不造成太大的阻碍。

这里主要对短信主界面的数据和UI的交互角度进行分析,因为我自己写的短信程序在加入获取联系人头像功能后,程序启动时花费的查询时间太长。虽然我也觉得系统默认的短信程序,甚至HandcentSMS,启动时间都不是很快。(大概是我的机器性能太差)

一、代码结构

Conversation中整体结构主要包括com.android.mms.data和com.android.mms.ui,如名字所示,大概就是数据处理部分和UI部分。数据部分主要是获取/缓存联系人信息、获取/缓存会话信息等。

ConversationList类是程序的主activity,派生于ListActivity,就是一个大的列表。此外:
ConversationListAdapter是这个ListView的adapter,派生于CursorAdapter;
ConversationListItem是一个自定义的ViewGroup,派生于RelativeLayout,用于表示会话列表的每一个item;
Conversation表示一个会话数据;Contact表示一个联系人;ContactList维护一个联系人列表;
RecipientIdCache用于开线程读取一个特殊的表,该表映射会话数据到联系人信息,也就是通过Recipient就可以获取联系人信息。

二、UI结构

这里的UI主要就是ConversationList/ConversationListAdapter/ConversationListItem三者之间的交互。

在layout中,conversation_list_item.xml作为这个ListView(ConversationList)的item定义,直接使用了ConversationListItem这个view:

<com.android.mms.ui.ConversationListItem xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:background="@drawable/conversation_item_background_unread"
    android:paddingRight="10dip" >


这个自定义item最重要的工作,就是将会话数据绑定到UI控件上,例如QuickContactBadge。在ListView的使用中,要绑定数据,还有个方法就是自写adapter,在构造adapter时就传入所有数据。但是如你所见,这种方法需要先读取出所有的数据。

而这个系统自带的短信程序,则没有一次读入。这个自定义item还有个功能就是,作为一条联系人信息的更新监听器。读取联系人信息是非常慢的,因为会涉及到几个表的查询。在构造这个item时,程序在另一个线程中异步读取联系人信息,而item只有一个联系人的简要信息(电话号码)。当联系人读出来后,再通知它的监听器,也就是这个item,然后更新UI显示。

ConversationListAdapter中只实现了bindView和newView这两个函数,此外,它作为listView的AbsListView.RecyclerListener,还实现了onMovedToScrapHeap函数。

关于RecyclerListener,这里有篇文章从源码级角度分析了下,大概意思就是ListView在处理item时,有个缓存机制。

三、数据与UI的映射

这部分才是重要的分析部分,也是我需要学习的部分。
ConversationList的onStart中,开启了一个异步查询,查询所有的会话:

    @Override
    protected void onStart() {
        super.onStart();

         .......
        startAsyncQuery();


startyAsyncQuery调用了Conversation.startQueryForAll函数,该函数说白了还是调用AsyncQueryHandler.startQuery函数:

    public static void startQueryForAll(AsyncQueryHandler handler, int token) {
        handler.cancelOperation(token);
        handler.startQuery(token, null, sAllThreadsUri,
                ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
    }


关于如何获取会话列表,其实就是个SQL的连表查询,可以参见这里:获取短信会话列表

当查询完后,android回调到自己实现的AsyncQueryHandler.onQueryComplete,该函数主要就是告诉adapter,we have done!:

        @Override
        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
            switch (token) {
            case THREAD_LIST_QUERY_TOKEN:
                mListAdapter.changeCursor(cursor);


一旦adapter获得了一个cursor后,就会主动去取得listview的各项数据。以上便是获取会话列表的大致流程。

接下来看看联系人获取的流程:
adapter获得数据后,会调用bindView来绑定数据到UI的item:

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        if (!(view instanceof ConversationListItem)) {
            Log.e(TAG, "Unexpected bound view: " + view);
            return;
        }

        ConversationListItem headerView = (ConversationListItem) view;
        Conversation conv = Conversation.from(context, cursor);

        ConversationListItemData ch = new ConversationListItemData(context, conv);
        headerView.bind(context, ch);
    }


Conversation.from函数会先检查Conversation缓存中是否有该cursor对应的数据,没有的话则会从cursor中取:

    public static Conversation from(Context context, Cursor cursor) {
        // First look in the cache for the Conversation and return that one. That way, all the
        // people that are looking at the cached copy will get updated when fillFromCursor() is
        // called with this cursor.
        long threadId = cursor.getLong(ID);
        if (threadId > 0) {
            Conversation conv = Cache.get(threadId);
            if (conv != null) {
                fillFromCursor(context, conv, cursor, false);   // update the existing conv in-place
                return conv;
            }
        }
        Conversation conv = new Conversation(context, cursor, false);
        try {
            Cache.put(conv);
        } catch (IllegalStateException e) {
            LogTag.error("Tried to add duplicate Conversation to Cache");
        }
        return conv;
    }



然后主要是fillFromCursor函数(如果是创建新的Conversation,其构造函数中也是调用了该函数),该函数就是简单地从cursor中getXXXX获取各个数据,并且,最重要的,获取联系人信息:

    private static void fillFromCursor(Context context, Conversation conv,
                                       Cursor c, boolean allowQuery) {

...
        ContactList recipients = ContactList.getByIds(recipientIds, allowQuery);


注意这里allowQuery参数为false。

ContactList.getByIds函数根据Conversation中recipientIds获取出对应的address,然后根据address从联系人URI中进一步获取联系人信息。

    public static ContactList getByIds(String spaceSepIds, boolean canBlock) {
        ContactList list = new ContactList();
        for (RecipientIdCache.Entry entry : RecipientIdCache.getAddresses(spaceSepIds)) {
            if (entry != null && !TextUtils.isEmpty(entry.number)) {
                Contact contact = Contact.get(entry.number, canBlock);
                contact.setRecipientId(entry.id);
                list.add(contact);
            }
        }
        return list;
    }


最终的contact被生成于Contact.get(entry.number, canBlock)中。该函数在canBlock为false的情况下,会push一个异步执行体(Runnable)到一个线程中。然后将contact返回。最终返回到adapter那一层的函数。

这个异步查询线程,会真正地去查询联系人信息。在此之前,外界获取出来的联系人不过是一个很简单的信息:只有电话号码。

adapter的bindView中,紧接着:
        ConversationListItemData ch = new ConversationListItemData(context, conv);
        headerView.bind(context, ch);
    }


bind函数中很重要的操作,就是建立该会话对应的联系人对象的监听:

        ContactList contacts = ch.getContacts();

        if (DEBUG) Log.v(TAG, "bind: contacts.addListeners " + this);
        Contact.addListener(this);


以上过程,即展示了会话数据是如何映射到ListView的item,及联系人信息是如何与会话和listview item建立联系(即异步查询,然后同步)。


值得一提的是:查询联系人信息都是在bindView时发生,当一个listview被显示出来后,未显示的item是不会被触发bind的,也就是说:在listview显示时,不会触发查询整个会话对应的所有联系人,只有显示出来的item才会涉及到查询联系人。



基于以上,我写了一个测试程序。结果似乎很好,就像系统自带的短信程序一样。启动速度依然不算快。问题的所在,似乎并不在于查询时间花费的很长。listview倒是早就显示出来了(看见了程序标题),但是items却要花些时间才能显示。

而且,我的测试程序更为恶心的是,listview在上下滚动时,会显得有点卡。经过一番折腾,发现是bindView里花的时间过长,后来加了Conversation的缓存,甚至去掉了日志,依然有点卡。不知道有否高手帮我解决下。

测试例子见附件。


1.10.2011 update

之前例子程序中ListView上下滚动时,会显得有点卡。本来都放弃不管了,结果今天打开eclipse时莫名其妙就想起这事:也许应该用ListActivity!结果一改,还真不卡了。注:系统的短信程序用的就是ListActivity。



3
0
分享到:
评论
6 楼 George_ghc 2013-11-25  
恩。不错。学习一下
5 楼 yshsunshine 2012-12-02  
你们都能运行吗  ,我这里运行过后什么都没有啊
4 楼 xiaoweixiong 2012-02-27  
楼主QQ多少啊,有问题请教
3 楼 mybrew 2011-11-17  
写得不错。
2 楼 kjsoloho 2011-09-28  
我只关心,发信息成功的时候是什么广播?
1 楼 dh1314al 2011-07-28  
不晓得,博主的这个问题解决没。关于博主提到的两个问题,我谈一下我的建议吧。第一点关于读取联系人头像,这个可以异步去系统数据库中取,第二点在bindview中取读取数据库的话这个肯定是会有点卡的,这个cursoradapter中的cursor可以先获取到吗,这样就是启动的时候有点慢,之后就不会出现卡的情况了。看的不是很仔细有错的地方,谅解。

相关推荐

    android系统自带短信程序源码部分分析.docx

    android系统自带短信程序源码部分分析

    vc++ 应用源码包_3

    多线程高速文件搜索程序源码 VC++视频聊天系统源代码 实例简单,有用户登录、传输文件、视频、画质调节、禁音检测、回音消除、自动增益、噪音抑制、视频控制等、 VC++搜索指定文件夹中的文件 VC++文件分割、合并...

    vc++ 应用源码包_1

    多线程高速文件搜索程序源码 VC++视频聊天系统源代码 实例简单,有用户登录、传输文件、视频、画质调节、禁音检测、回音消除、自动增益、噪音抑制、视频控制等、 VC++搜索指定文件夹中的文件 VC++文件分割、合并...

    收银系统

     本系统与百威软件老版本有高度的兼容性,系统自带智能升级程序,免去了手工升级的繁琐。 ▲开放式的打印设计和OOP技术 百威9000V6商业管理系统的打印样式设计功能采用当今流行的报表设计器进行设计。用户不仅可以...

    vc++ 应用源码包_2

    多线程高速文件搜索程序源码 VC++视频聊天系统源代码 实例简单,有用户登录、传输文件、视频、画质调节、禁音检测、回音消除、自动增益、噪音抑制、视频控制等、 VC++搜索指定文件夹中的文件 VC++文件分割、合并...

    vc++ 开发实例源码包

    精灵系统,一套MFC渲染引擎,含2D/3D等渲染,效果看源码,IFEngine是整个引擎接口,IFSystem是硬件查询系统,IFApplication是应用程序对象基类。 FlashPlayer播放器4.0的VC++源代码 如题。 FreeBird2011最初版(模仿...

    vc++ 应用源码包_5

    多线程高速文件搜索程序源码 VC++视频聊天系统源代码 实例简单,有用户登录、传输文件、视频、画质调节、禁音检测、回音消除、自动增益、噪音抑制、视频控制等、 VC++搜索指定文件夹中的文件 VC++文件分割、合并...

    vc++ 应用源码包_6

    多线程高速文件搜索程序源码 VC++视频聊天系统源代码 实例简单,有用户登录、传输文件、视频、画质调节、禁音检测、回音消除、自动增益、噪音抑制、视频控制等、 VC++搜索指定文件夹中的文件 VC++文件分割、合并...

    JAVA上百实例源码以及开源项目源代码

    Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码,文件操作,压缩包查看  Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码...

    JAVA上百实例源码以及开源项目

    Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码,文件操作,压缩包查看  Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码...

    java开源包8

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    android手机音乐播放器实训报告final.doc

    课程负责人签名: 年 月 日 目录 第一章 引言 6 1.1 目的及范围 6 第二章 需求说明 7 2.1 系统参与者 7 2.2 系统用例 7 2.3领域模型分析 8 第三章 架构设计说明 11 3.1 逻辑视图 11 3.2 进程视图 11 3.3 开发视图 ...

    java开源包1

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包11

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包2

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包3

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包6

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包5

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包10

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

Global site tag (gtag.js) - Google Analytics