Android 2.3.5 设置邮件中电话号码高亮显示

        事情的起因是这样的,因为需要在Email的显示界面中为电话号码添加高亮显示,从而提高用户体验。我想用过google自带邮箱的朋友都知道,当我们的收到邮件的内容中含有网址,邮箱地址时,会在显示界面中以超链接的方式显示,此时当我们点击该网址或者邮箱地址时,便会弹出浏览器或者撰写邮件。目前的情况是,当我们的邮件内容中含有可识别的电话号码时,我们可以点击并跳转到拨号界面,但却不能高亮显示,因此我们的目标就是将电话号码修改为高亮显示,如下图1:


图1:修改后的效果

        那么我们还是列出我们的目的吧,以免忘记(我就容易忘记:D)。

        目标:将接收邮件内容中的电话号码高亮显示。

        因为自己也是第一次接触邮件这块,因此可能走了很多弯路或者说的并不清除,请别见笑,如果后文中有错误还恳请各位朋友指正。毕竟,没有人一生下来就是老鸟!!!

        好了废话不多说,开始吧!

        1.找到问题的根源

        需要修改的Email读取邮件的界面,当我看到该界面的时候第一反应,这货应该是一个TextView,用于撰写Email的界面应该是一个EditText。毕竟TextView用于显示这些内容足够了啊,进而联想到xml布局文件中的android:autoLink属性,只要为该TextView控件加上该属性则在TextView中的网址,邮件地址,电话号码等自动会变为超链接(不信的可以自己试试)。有了这个思路那么我们首先应该找到显示邮件内容的布局文件(这里提一下,1.如果像我等小白的话可能就会老老实实的去找packages/apps/Email/res/layout,然后再去看哪个名字像是显示内容的,比如:Mmessage_content.xml这种就很像,我一开始就是这么找的T_T。2.如果以前看过源码的朋友估计会通过查看该APP的应用程序入口,然后根据如果跳转到邮件显示Activity最后再搜索setContentView方法就可以知道其布局文件。3.其实其实,SDK/tools文件夹下真的有好多有用的东西——hierarchyviewer就是其中之一,我们只要运行该工具同时连接上我们的设备,也可以是模拟器,该工具会自动列出当前界面的布局状态,如图2所示)。

图2 hierarchyviewer显示

        我用的是Ubuntu10.04 64bit 在windows下都差不多的,hierarchyviewer存放在SDK下的tools目录中,通过hierarchyviewer我可轻松的分析该界面的布局状态。鼠标左键选中以上布局文件时,右边界面中会有提示。因此在这里我们可以很轻松的将我们前面的假设推翻,显示界面根本就不是什么TextView而是WebView(小白就是小白啊T_T)。怎么会是WebView呢,稍稍做过web开发的人都知道的吧,我们要在界面中显示图片,超链接,邮件等等,这些东西如果要放到TextView中去做???很明显应该选择WebView嘛,同时我们从服务其获得的信息就是html字符串(后文会提到)。因此前面的问题就变成了在WebView中对电话号码进行高亮显示。

        2.查看源代码

        Linux之父Linus曾说过,要了解程序原理的最好方法就是——Read the fucking source code.好吧,通过查看程序入口,然后跳转到显示界面这些步骤省略,最终我们找到/packages/apps/Email/src/com/android/email/activity/MessageView.java,该类主要完成消息内容的显示。那么如何开始查看呢?因为我们主要关心的WebView的设置,因此我们可以直接搜索WebView来查看,结果全文中只有一个WebView:

private WebView mMessageContentView;

因此我们可以继续搜索mMessageContentView这个对象,我们可以在onCreate方法中找到:

        
        mMessageContentView.setClickable(true);
        mMessageContentView.setLongClickable(false);    // Conflicts with ScrollView, unfortunately
        mMessageContentView.setVerticalScrollBarEnabled(false);
        mMessageContentView.getSettings().setBlockNetworkLoads(true);
        mMessageContentView.getSettings().setSupportZoom(false);
        mMessageContentView.setWebViewClient(new CustomWebViewClient());


        这里对该WebView对象进行了一些设置。

        (1).setClickable(true)是设置WebView可点击;

        (2).setLongClickable(false)是该WebView不支持长点击事件,这是因为会和ScrollView冲突,因此将其设置位false,可以在ScrollView中处理长点击事件;

        (3).setVerticalScrollBarEnabled(false)接着设置垂直方向滚动禁止,也是和ScrollView冲突;

        (4).setBlcokNetworkLoads(true)设置是否阻止网络加载,如果邮件中含有图片则会打开该属性;

        (5).setSupportZoom(false)这是设置当前WebView不支持缩放;

        (6).setWebViewClient使WebView接收到各种通知和请求;

接下来我们继续查找mMessageContentView可以发现mMessageContentView.loadDataWithBaseURL(...)方法,通过命名我们可以知道该方法用于加载URL同时,其中的参数很是奇怪,有text,mHtmlTextWebView等,这些东西是什么呢,果断加入Log.i("TAG","text:"+text);将这些信息通过log.i输出来,加入这些代码之后单独编译Email并将修改之后的Email.apk push到/system/app路径下实践,根据打印出来的log我们可以初步将目光锁定到:

    
   /**
     * Reload the body from the provider cursor.  This must only be called from the UI thread.
     *
     * @param bodyText text part
     * @param bodyHtml html part
     *
     * TODO deal with html vs text and many other issues
     */
    private void reloadUiFromBody(String bodyText, String bodyHtml) {
        String text = null;
        mHtmlTextRaw = null;
        boolean hasImages = false;

        if (bodyHtml == null) {
            text = bodyText;
            /*
             * Convert the plain text to HTML
             */
            StringBuffer sb = new StringBuffer("");
            if (text != null) {
                // Escape any inadvertent HTML in the text message
                text = EmailHtmlUtil.escapeCharacterToDisplay(text);
                // Find any embedded URL's and linkify
                Matcher m = Patterns.WEB_URL.matcher(text);
                while (m.find()) {
                    int start = m.start();
                    /*
                     * WEB_URL_PATTERN may match domain part of email address. To detect
                     * this false match, the character just before the matched string
                     * should not be '@'.
                     */
                    if (start == 0 || text.charAt(start - 1) != '@') {
                        String url = m.group();
                        Matcher proto = WEB_URL_PROTOCOL.matcher(url);
                        String link;
                        if (proto.find()) {
                            // This is work around to force URL protocol part be lower case,
                            // because WebView could follow only lower case protocol link.
                            link = proto.group().toLowerCase() + url.substring(proto.end());
                        } else {
                            // Patterns.WEB_URL matches URL without protocol part,
                            // so added default protocol to link.
                            link = "http://" + url;
                        }
                        String href = String.format("%s", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("");
            text = sb.toString();
        } else {
            text = bodyHtml;
            mHtmlTextRaw = bodyHtml;
            hasImages = IMG_TAG_START_REGEX.matcher(text).find();
        }
        mShowPicturesSection.setVisibility(hasImages ? View.VISIBLE : View.GONE);
        if (mMessageContentView != null) {
            mMessageContentView.loadDataWithBaseURL("email://", text, "text/html", "utf-8", null);
        }

        // Ask for attachments after body
        mLoadAttachmentsTask = new LoadAttachmentsTask();
        mLoadAttachmentsTask.execute(mMessage.mId);
    }

根据打印出来的log信息(这里就不贴log了,大家可以自己添加log打印出来看看),我们可以知道邮件内容有两种类型:bodyText和bodyHtml。前者不包括Html标签,那么这是哪两种情况呢?

        (1).bodyText是对方通过Android邮箱给你发送邮件时,我们接收到的邮件信息是bodyText,不带Html标签;

        (2).bodyHtml是通过网络邮箱发送时,我们接收到的信息是bodyHtml,带有Html标签;

         根据打印出来的log信息我们可以看到:

http://www.baidu.com
[email protected]

而电话号码却没有任何提示,做过web开发的人肯定一看就明白了,不过我这里作为小菜的我还是要罗嗦几句,href在Html中表示超链接如果要了解详情的请自己去百度哈。很明显嘛,原始的bodyText数据不带这些标签的,经过处理(正则匹配)之后加上标签,然后通 loadDataWithBaseURL(...)方法来完成加载显示的动作。那么回想以下我们的目标是什么?不就是为电话号码加入高亮显示么!因此我们只需要通过正则表达式匹配出邮件内容中的电话号码,同时给它加上类似于邮件和网址的标签就可以了吧!!!

        3.深入分析并实现功能

        有以上的分析,我们便可以开始动作实践了。但是问题又来了,要实现电话号码高亮的功能我们需要两个条件,第一,用于匹配电话号码的正则表达式,用于匹配邮件内容中的电话号码;第二,因为网址和邮件的高亮都有自己特殊的标志(邮件对应的是mailto),那么电话号码会不会也有对应的标志呢?在进一步分析前,我们先回过头想一想。虽然在邮件中我们的电话号码没有高亮,但是我们依然可以点击该号码跳转到拨号界面啊。也就是说该程序是识别到了电话号码的,只是没有高亮显示而已。继续分析,前面我们提到了,邮件显示网址和邮箱地址这些都是经过了正则匹配的(前面代码中),也就是匹配之后才知道是否为网址和邮箱地址。那么既然电话号码可以拨打肯定也是经过了正则匹配的。分析到这里,我们所需要的的两个条件之一已经基本解决,剩下的就是去找到已经定义好的电话号码匹配正则表达式。那么第二个问题呢?看似没有思路啊(0.0)!没关系,我们先来看看以下代码吧:

mMessageContentView.loadDataWithBaseURL("email://", text, "text/html", "utf-8", null);

        这里也获取不到什么有用的信息,但是这个方法是最终实现加载内容并显示的方法,因此它里面肯定有很多重要的东西,那么我们果断跳转跟踪它的实现吧。直接跳转到了frameworks/base/core/java/android/webkit/WebView.java中(小白解惑跳转方法:对准loadDataWithBaseURL方法名同时按住左Ctrl+鼠标左键)。代码如下:

    public void loadDataWithBaseURL(String baseUrl, String data,
            String mimeType, String encoding, String historyUrl) {

        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
            loadData(data, mimeType, encoding);
            return;
        }
        switchOutDrawHistory();
        WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
        arg.mBaseUrl = baseUrl;
        arg.mData = data;
        arg.mMimeType = mimeType;
        arg.mEncoding = encoding;
        arg.mHistoryUrl = historyUrl;
        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
        clearHelpers();
    }

在该方法中将传入的参数封装到了BaseUrlData对象arg中,然后通过sendMessage()方法将arg对象发送出去。

    
void sendMessage(int what, Object obj) {
        mEventHub.sendMessage(Message.obtain(null, what, obj));
    }

继续跟踪

        private synchronized void sendMessage(Message msg) {
            if (mBlockMessages) {
                return;
            }
            if (mMessages != null) {
                mMessages.add(msg);
            } else {
                mHandler.sendMessage(msg);
            }
        }

哈哈终于看到熟悉的Handler了,既然是通过mHandler发送消息,那么肯定会有接收消息的handleMessage()方法啊
,直接搜索mHandler的handleMessage()方法发现

            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    if (DebugFlags.WEB_VIEW_CORE) {
                        Log.v(LOGTAG, (msg.what < REQUEST_LABEL
                                || msg.what
                                > VALID_NODE_BOUNDS ? Integer.toString(msg.what)
                                : HandlerDebugString[msg.what
                                        - REQUEST_LABEL])
                                + " arg1=" + msg.arg1 + " arg2=" + msg.arg2
                                + " obj=" + msg.obj);
                    }
                    switch (msg.what) {
                        case WEBKIT_DRAW:
                            webkitDraw();
                            break;
                        ...

                        case LOAD_DATA:
                            BaseUrlData loadParams = (BaseUrlData) msg.obj;
                            String baseUrl = loadParams.mBaseUrl;
                            if (baseUrl != null) {
                                int i = baseUrl.indexOf(':');
                                if (i > 0) {
                                    /*
                                     * In 1.0, {@link
                                     * WebView#loadDataWithBaseURL} can access
                                     * local asset files as long as the data is
                                     * valid. In the new WebKit, the restriction
                                     * is tightened. To be compatible with 1.0,
                                     * we automatically add the scheme of the
                                     * baseUrl for local access as long as it is
                                     * not http(s)/ftp(s)/about/javascript
                                     */
                                    String scheme = baseUrl.substring(0, i);
                                    if (!scheme.startsWith("http") &&
                                            !scheme.startsWith("ftp") &&
                                            !scheme.startsWith("about") &&
                                            !scheme.startsWith("javascript")) {
                                        nativeRegisterURLSchemeAsLocal(scheme);
                                    }
                                }
                            }
                            mBrowserFrame.loadData(baseUrl,
                                    loadParams.mData,
                                    loadParams.mMimeType,
                                    loadParams.mEncoding,
                                    loadParams.mHistoryUrl);
                            break;

                        ...

                        case START_DNS_PREFETCH:
                            mBrowserFrame.startDnsPrefetch();
                            break;
                    }
                }
            };

        这里我只截取了我们需要的部分LOAD_DATA,分析该Case似乎也不能知道什么,但有两个关键点:第一,这里的scheme似乎可以用来匹配数据类型;第二,loadParams.mData似乎就是我们前面传入的data参数,该参数是加上了html标签的邮件内容;针对以上两点我们可以在这里打印log信息,然后单独编译framework,替换到设备中原framework。这样,再次运行的时候便会打印出log信息。这里我就分别打印出scheme和loadParams.mData两个字符串。经过实践以后验证了我的两个猜想,scheme中存放的是email://,就是我们前面传入的标志。loadParams.mData中存放的就是加入了html标签的邮件内容字符串

       到这里似乎没有眉目了啊。我们是想找到href中关于电话号码的关键词,这样可以使电话号码高亮显示,但跟踪到这里似乎并不能得到有用的信息。但我们可以知道,WebView的最终显示是通过将数据传递到JNI再到底层去实现的,有兴趣的朋友可以自己再去跟踪下。这里的nativeRegisterURLSchemeAsLocal位于external/webkit/WebKit/android/jni/WebViewCore.cpp中,对应的native方法是RegisterRULSchemeAsLocal()。继续跟踪可以跳转到/external/webkit/WebCore/page/SecurityOrigin.cpp中的SecurityOrigin::registerURLSchemeAsLocal()方法。后续没有再继续跟踪了,毕竟本人小菜一个,后面再进一步学习吧。

       3.峰回路转柳暗花明

        在前面饶了一大圈,头也晕了,我们还是整理整理思路吧。1.我们想找到设置电话号码高亮的标签(类似邮件的href=mailto)。2.找到用于匹配电话号码的正则表达式。经过了前面的查找,我们似乎还是无法找到电话号码的标签位。那我们换一下,这次先找匹配电话号码的正则表达式。首先,我们在代码:

    private void reloadUiFromBody(String bodyText, String bodyHtml) {
        String text = null;
        mHtmlTextRaw = null;
        boolean hasImages = false;

        if (bodyHtml == null) {
            text = bodyText;
            /*
             * Convert the plain text to HTML
             */
            StringBuffer sb = new StringBuffer("");
            if (text != null) {
                // Escape any inadvertent HTML in the text message
                text = EmailHtmlUtil.escapeCharacterToDisplay(text);
                // Find any embedded URL's and linkify
                Matcher m = Patterns.WEB_URL.matcher(text);
                while (m.find()) {
                    int start = m.start();
                    /*
                     * WEB_URL_PATTERN may match domain part of email address. To detect
                     * this false match, the character just before the matched string
                     * should not be '@'.
                     */
                    if (start == 0 || text.charAt(start - 1) != '@') {
                        String url = m.group();
                        Matcher proto = WEB_URL_PROTOCOL.matcher(url);
                        String link;
                        if (proto.find()) {
                            // This is work around to force URL protocol part be lower case,
                            // because WebView could follow only lower case protocol link.
                            link = proto.group().toLowerCase() + url.substring(proto.end());
                        } else {
                            // Patterns.WEB_URL matches URL without protocol part,
                            // so added default protocol to link.
                            link = "http://" + url;
                        }
                        String href = String.format("%s", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("");
            text = sb.toString();
        } else {
            text = bodyHtml;
            mHtmlTextRaw = bodyHtml;
            hasImages = IMG_TAG_START_REGEX.matcher(text).find();
        }
        mShowPicturesSection.setVisibility(hasImages ? View.VISIBLE : View.GONE);
        if (mMessageContentView != null) {
            mMessageContentView.loadDataWithBaseURL("email://", text, "text/html", "utf-8", null);
        }

        // Ask for attachments after body
        mLoadAttachmentsTask = new LoadAttachmentsTask();
        mLoadAttachmentsTask.execute(mMessage.mId);
    }

中发现有Matcher m = Patterns.WEB_URL.matcher(text);这句代码就是在对text作正则匹配,从字面上意思来看就是匹配网址的。我们直接跳转到Patterns中可以看到,该类中存放着多种用于正则匹配的Patterns对象。一不小心,:-)发现了该Patterns对象:PHONE

    public static final Pattern PHONE
        = Pattern.compile(                                  // sdd = space, dot, or dash
                "(\\+[0-9]+[\\- \\.]*)?"                    // +*
                + "(\\([0-9]+\\)[\\- \\.]*)?"               // ()*
                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // +

这不正是
我们苦苦寻找的用于匹配电话号码的正则表达式么?!那么我们接下来就只需要获取电话号码href后的标志就可以了。现在我们可以这样来思考,因为电话号码以及网址和邮箱地址都是可以点击的,因此我们去寻找该点击事件的触发点。看看该触发事件是根据什么来判断的(猜想是通过独特的Flag完成:D)

mMessageContentView.setWebViewClient(new CustomWebViewClient());

还记得onCreate()方法中的WebView初始化么,setWebViewClient方法就是让WebView接收各种请求信息,当然要包括了超链接这种啊。因此,找到其中CustomWebViewClient类实现,代码如下:

    private class CustomWebViewClient extends WebViewClient {
        /**
         * This is intended to mirror the operation of the original
         * (see android.webkit.CallbackProxy) with one addition of intent flags
         * "FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET".  This improves behavior when sublaunching
         * other apps via embedded URI's.
         *
         * We also use this hook to catch "mailto:" links and handle them locally.
         */
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            // hijack mailto: uri's and handle locally
            if (url != null && url.toLowerCase().startsWith("mailto:")) {
                return MessageCompose.actionCompose(MessageView.this, url, mAccountId);
            }

            // Handle most uri's via intent launch
            boolean result = false;
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            intent.addCategory(Intent.CATEGORY_BROWSABLE);
            intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
            try {
                startActivity(intent);
                result = true;
            } catch (ActivityNotFoundException ex) {
                // If no application can handle the URL, assume that the
                // caller can handle it.
            }
            return result;
        }
    }

该类中override了shouldOverrideUrlLoading方法,在该方法中我们可以清楚的看到url来匹配mailto:标志,因此我们可以大胆的猜想,根据这里匹配来确定到底点击了哪种链接,从而通过intent跳转到不同的Activity。那么我为何不将url打印出来呢?哈哈,赶快加入log,编译Email.apk,安装调试。果然不出我们的所料,打印出来的log信息正是:tel:13800174444。看到木有,看到木有,电话号码的标签就是tel啊

       4.最终实现

    private void reloadUiFromBody(String bodyText, String bodyHtml) {
        String text = null;
        mHtmlTextRaw = null;
        boolean hasImages = false;

        if (bodyHtml == null) {
            text = bodyText;
            /*
             * Convert the plain text to HTML
             */
            StringBuffer sb = new StringBuffer("");
            if (text != null) {
                // Escape any inadvertent HTML in the text message
                text = EmailHtmlUtil.escapeCharacterToDisplay(text);
                // Find any embedded URL's and linkify
                Matcher m = Patterns.WEB_URL.matcher(text);
                while (m.find()) {
                    int start = m.start();
                    /*
                     * WEB_URL_PATTERN may match domain part of email address. To detect
                     * this false match, the character just before the matched string
                     * should not be '@'.
                     */
                    if (start == 0 || text.charAt(start - 1) != '@') {
                        String url = m.group();
                        Matcher proto = WEB_URL_PROTOCOL.matcher(url);
                        String link;
                        if (proto.find()) {
                            // This is work around to force URL protocol part be lower case,
                            // because WebView could follow only lower case protocol link.
                            link = proto.group().toLowerCase() + url.substring(proto.end());
                        } else {
                            // Patterns.WEB_URL matches URL without protocol part,
                            // so added default protocol to link.
                            link = "http://" + url;
                        }
                        String href = String.format("%s", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("");
            text = sb.toString();
            text = highLight(ALL, text);
        } else {
            text = bodyHtml;
            text = highLight(PHONENUM, text);
            mHtmlTextRaw = bodyHtml;
            hasImages = IMG_TAG_START_REGEX.matcher(text).find();
        }
        mShowPicturesSection.setVisibility(hasImages ? View.VISIBLE : View.GONE);
        if (mMessageContentView != null) {
            mMessageContentView.loadDataWithBaseURL("email://", text, "text/html", "utf-8", null);
        }

        // Ask for attachments after body
        mLoadAttachmentsTask = new LoadAttachmentsTask();
        mLoadAttachmentsTask.execute(mMessage.mId);
    }

       经过了这么复杂的过程,我们所需要的东西都已经拿到了,那么赶快动手修改代码吧。前面我们就已经分析过了,要加入高亮显示,只需要对接收到的text文件进行修改就可以了。修改后的代码如下
    
    private void reloadUiFromBody(String bodyText, String bodyHtml) {
        String text = null;
        mHtmlTextRaw = null;
        boolean hasImages = false;

        if (bodyHtml == null) {
            text = bodyText;
            /*
             * Convert the plain text to HTML
             */
            StringBuffer sb = new StringBuffer("");
            if (text != null) {
                // Escape any inadvertent HTML in the text message
                text = EmailHtmlUtil.escapeCharacterToDisplay(text);
                // Find any embedded URL's and linkify
                Matcher m = Patterns.WEB_URL.matcher(text);
                while (m.find()) {
                    int start = m.start();
                    /*
                     * WEB_URL_PATTERN may match domain part of email address. To detect
                     * this false match, the character just before the matched string
                     * should not be '@'.
                     */
                    if (start == 0 || text.charAt(start - 1) != '@') {
                        String url = m.group();
                        Matcher proto = WEB_URL_PROTOCOL.matcher(url);
                        String link;
                        if (proto.find()) {
                            // This is work around to force URL protocol part be lower case,
                            // because WebView could follow only lower case protocol link.
                            link = proto.group().toLowerCase() + url.substring(proto.end());
                        } else {
                            // Patterns.WEB_URL matches URL without protocol part,
                            // so added default protocol to link.
                            link = "http://" + url;
                        }
                        String href = String.format("%s", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("");
            text = sb.toString();
            text = highLight(ALL,text);//全部高亮,highLight的参数有三种PHONENUM,EMAILADDRESS,WEBURL
        } else {
            text = bodyHtml;
            text = highLight(ALL,text);//为什么这里会是ALL?!后文会有讲解
            mHtmlTextRaw = text;//这里也要注意修改哦 不然后面点击显示图片后文中的电话号码高亮会消失的哦:-)
            hasImages = IMG_TAG_START_REGEX.matcher(text).find();
        }
        mShowPicturesSection.setVisibility(hasImages ? View.VISIBLE : View.GONE);
        if (mMessageContentView != null) {
            mMessageContentView.loadDataWithBaseURL("email://", text, "text/html", "utf-8", null);
        }

        // Ask for attachments after body
        mLoadAttachmentsTask = new LoadAttachmentsTask();
        mLoadAttachmentsTask.execute(mMessage.mId);
    }

这里我加入了自己的修改方法highLight(int TYPE,String text),这里的TYPE表示需要高亮的对象,包括了PHONENUM,EMAILADDRESS,WEBURL,ALL四种,其中ALL表示包含前面所有;text表示需要高亮的字符串;因为bodyText和bodyHtml获取到的数据不同,前面有提到,bodyText是纯文本String;bodyHtml是具有html标签的文本,同时BodyHtml是网络邮箱返回的数据,其中有的已经是对EmailAddress和web url处理过的,但有的还是没有,比如像126,163等邮箱,他们返回的bodyHtml中如果含有url或者email addrss,它也不会将其置为高亮,因此这里通过ALL来全部处理。最后贴出自己的highLight方法


    
   /**
     * high light the telephone number and web url and email address.
     * @author http://blog.csdn.net/yihongyuelan
     * @param TYPE contains PHONENUM,EMAILADDRESS,WEBURL,ALL
     * @return String which has been replaced 
     */
	public String highLight(int TYPE,String text){
		replacedString = text;//这里的replacedString是全局变量
		String replaceElement = "";//表示需要处理的元素电话号码用"tel:"表示,同样,邮件是"mailto:" web url则是""(空)
		switch (TYPE) {
		case PHONENUM://只对电话号码进行高亮处理
			pattern = Patterns.PHONE ;//用于正则匹配phone的patterns
			matcher = pattern.matcher(replacedString);
			replaceElement = PHONE_HIGHLIGHT_ELEMENT;
			while(FLAG){
				replacedString = replaceKeyString(matcher,replaceElement);//主要的替换方法
				matcher = getMatcher(replacedString);
			}
			FLAG = true;
			break;
		case EMAILADDRESS://只对邮件地址号码进行高亮处理	
			pattern = Patterns.EMAIL_ADDRESS ;
			matcher = pattern.matcher(replacedString);
			replaceElement = EMAILADDRESS_HIGHLIGHT_ELEMENT ;
			while(FLAG){
				replacedString = replaceKeyString(matcher,replaceElement);
				matcher = getMatcher(replacedString);
			}
			FLAG = true;
			break;
		case WEBURL://只对web url进行高亮处理
			pattern = Patterns.WEB_URL;
			matcher = pattern.matcher(replacedString);
			replaceElement = WEBURL_HIGHLIGHT_ELEMENT;
			while(FLAG){
				replacedString = replaceKeyString(matcher,replaceElement);
				matcher = getMatcher(replacedString);
			}
			FLAG = true;
			break;
		case ALL://只对以上三者进行高亮处理
			pattern = Patterns.PHONE;
			replaceElement = PHONE_HIGHLIGHT_ELEMENT;
			matcher = pattern.matcher(replacedString);
			while(FLAG){
				replacedString = replaceKeyString(matcher,replaceElement);
				matcher = getMatcher(replacedString);
			}
			FLAG = true;
			
			pattern = Patterns.EMAIL_ADDRESS;
			replaceElement = EMAILADDRESS_HIGHLIGHT_ELEMENT;
			matcher = pattern.matcher(replacedString);
			while(FLAG){
				replacedString = replaceKeyString(matcher,replaceElement);
				matcher = getMatcher(replacedString);
			}
			FLAG = true;
			
			pattern = Patterns.WEB_URL;
			replaceElement = WEBURL_HIGHLIGHT_ELEMENT;
			matcher = pattern.matcher(replacedString);
			while(FLAG){
				replacedString = replaceKeyString(matcher,replaceElement);
				matcher = getMatcher(replacedString);
			}
			FLAG = true;
		default:
			break;
		}
		return replacedString;
	}
    
    /**
     * replace the key String.
     * @author http://blog.csdn.net/yihongyuelan
     * @param matcher Matcher
     * @param mReplaceElement replace element "" or "tel:" or "mailto:"
     * @return replacedString text has been replaced
     */
	public String replaceKeyString(Matcher matcher, String mReplaceElement) {
		String replaceElement = mReplaceElement;
		String keyString = "";
		String result = replacedString;
		while (matcher.find()) {
			keyString = matcher.group();

			if (WEBURL_HIGHLIGHT_ELEMENT.equals(replaceElement)) {//在匹配web url时处理
				int endUrl = matcher.start();
				int startUrl = endUrl - 7;
				if (startUrl > 0) {
					String hrefStr = result.substring(startUrl, endUrl);
					if (hrefStr.contains("href")) {
						continue;//如果包含href说明已经处理过了,不再处理
					}
				}
				
				int endSrc = matcher.start();
				int startSrc = endSrc - 6;
				if (startSrc > 0) {
					String srcStr = result.substring(startSrc, endSrc);
					if (srcStr.contains("src")) {
						continue;//如果包含src说明是资源引用,不属于需要高亮的web url
					}
				}
				
				int endXMLNS = matcher.start();
				int startXMLNS = endXMLNS - 14;
				if (startXMLNS > 0) {
					String xmlnsStr = result.substring(startXMLNS, endXMLNS);
					if (xmlnsStr.contains("xmlns")) {
						continue;//命名空间需要高亮
					}
				}
				
				int endStr = matcher.start() - 2;
				int startStr = endStr - keyString.length();
				if (startStr > 0) {
					String webStr = result.substring(
							endStr - keyString.length(), endStr);
					if (keyString.contains(webStr)) {
						continue;//已经处理过的不再需要处理
					}
				}

                if(matcher.start()-1 >=0 && result.charAt(matcher.start()-1) == '@'){
                	continue;//不出里邮件格式如[email protected] 
                }
                
                if(keyString.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
                	continue;不出里数字格式 ru 10.128.10.10
                }
                
                String url = keyString;
                String link;
				int start = matcher.start();
				int end = start + 4;
				String flagStr = result.substring(start, end);
                if(!flagStr.contains("http") && !flagStr.contains("Http")
                		&& !flagStr.contains("HTTP") && !flagStr.contains("ftp")
						&& !flagStr.contains("Ftp")  && !flagStr.contains("FTP")
						&& !flagStr.contains("rtsp") && !flagStr.contains("Rtsp")
						&& !flagStr.contains("RTSP")){
                	link = "http://" + url;
                    String href = String.format("%s", link, url);
                    result = result.substring(0, matcher.start())+href+result.substring(matcher.end());
                    return result;//如果不包含http等的url 如www.baidu.com
                }
            
			}
			if (PHONE_HIGHLIGHT_ELEMENT.equals(replaceElement)) {//在匹配电话号码时处理

				if (4 == keyString.length()) {//低于4个数字的不处理
					continue;
				}

				if (matcher.start() - 1 >= 0) {//数字前面含有@ - 等特殊字符的不处理
					char keyChar = result.charAt(matcher.start() - 1);
					if (keyChar == 45 || keyChar == 47 || keyChar == 61 || keyChar == 63
									  || (35 <= keyChar && keyChar <= 38)) {
						continue;
					}
				}

				int end = matcher.start();
				int start = end - 8;
				if (start < 0) {
					start = end - 7;
				}
				if(start>=0){//不处理数字前面以http之类开头的 如http://10.128.12.211
					String urlStr = result.substring(start, end);
					if (urlStr.contains("http") || urlStr.contains("Http")
							|| urlStr.contains("HTTP") || urlStr.contains("ftp")
							|| urlStr.contains("Ftp") || urlStr.contains("FTP")
							|| urlStr.contains("rtsp") || urlStr.contains("Rtsp")
							|| urlStr.contains("RTSP")) {
						continue;
					}
				}

				if (matcher.end() != result.length()) {//数字后面有字母包括或者@的不处理
					char keyChar = result.charAt(matcher.end());
					if ((64 <= keyChar && keyChar <= 122)
							|| keyChar == 95) {
						continue;
					}
				}
				if (keyString.matches("\\d{1,4}\\-\\d{1,2}\\-\\d{1,2}(\\s\\d{1,2})?")) {//日期不处理
					continue;
				}
                if(keyString.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){//如 10.120.121.22不处理
                	continue;
                }
				
				int endPhone = matcher.start();
				int startPhone = endPhone - 4;
				String emailStr = result.substring(startPhone, endPhone);
				if (emailStr.contains("tel")) {//包含tel标志的 不处理
					continue;
				}
				
				int endStr = matcher.start() - 2;
				int startStr = endStr - keyString.length();
				if (startStr > 0) {
					String webStr = result.substring(endStr - keyString.length(), endStr);
					if (keyString.contains(webStr)) {
						continue;//已经处理过的不再处理
					}
				}
			}
			if (EMAILADDRESS_HIGHLIGHT_ELEMENT.equals(replaceElement)) {//匹配邮件地址时
				int endEmail = matcher.start();
				int startEmail = endEmail - 7;
				String emailStr = result.substring(startEmail, endEmail);
				if (emailStr.contains("mailto")) {//已经有mailto标志的 不再匹配
					continue;
				}
				int endStr = matcher.start() - 2;
				int startStr = endStr - keyString.length();
				if (startStr > 0) {
					String webStr = result.substring(endStr - keyString.length(), endStr);
					if (keyString.contains(webStr)) {
						continue;//已经处理国的不再处理
					}
				}
			}
			String replacement = "" + keyString + "";//开始替换
			result = result.substring(0, matcher.start())+replacement+result.substring(matcher.end());
			return result;
		}
			FLAG = false;
		return result;
	}
	
    /**
     * construct the matcher.
     * @author http://blog.csdn.net/yihongyuelan
     * @param String to construct matcher
     * @return matcher
     */
	public Matcher getMatcher(String str) {
		Matcher matcher = pattern.matcher(str);
		return matcher;
	}

针对以上代码做下简单的分析,其实代码的功能就是将找到需要替换的字符串然后在替换。比如,原:"你好,这是简单123的测试",替换:"你好,这是简单的测试"。

           5.总结

        经过了各种纠结,总算完成了需要的功能了。在此也小小的总结以下,对于SDK里面提供的工具自己还是应该多熟悉以下。其次应该使用StarUML将代码执行流程图画出来,这样一来直观而来也便于交流。最后,对于这种总结自己应该多做一些,一方面可以督促自己尽量去搞懂,另一方面也可以检查自己的知识漏洞。

你可能感兴趣的:(Android)