1. 程式人生 > >Keep Awake,Keep Studying,Keep Moving,Keep...

Keep Awake,Keep Studying,Keep Moving,Keep...

        事情的起因是這樣的,因為需要在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("<html><body>");
            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("<a href=\"%s\">%s</a>", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("</body></html>");
            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資訊我們可以看到:

<a href="http://www.baidu.com">http://www.baidu.com</a>
<a href="mailto:[email protected]">[email protected]</a>
而電話號碼卻沒有任何提示,做過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("<html><body>");
            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("<a href=\"%s\">%s</a>", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("</body></html>");
            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]+[\\- \\.]*)?"                    // +<digits><sdd>*
                + "(\\([0-9]+\\)[\\- \\.]*)?"               // (<digits>)<sdd>*
                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>

這不正是
我們苦苦尋找的用於匹配電話號碼的正則表示式麼?!那麼我們接下來就只需要獲取電話號碼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("<html><body>");
            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("<a href=\"%s\">%s</a>", link, url);
                        m.appendReplacement(sb, href);
                    }
                    else {
                        m.appendReplacement(sb, "$0");
                    }
                }
                m.appendTail(sb);
            }
            sb.append("</body></html>");
            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的測試",替換:"你好,這是簡單<a href="tel:123>"123</a>的測試"。

           5.總結

        經過了各種糾結,總算完成了需要的功能了。在此也小小的總結以下,對於SDK裡面提供的工具自己還是應該多熟悉以下。其次應該使用StarUML將程式碼執行流程圖畫出來,這樣一來直觀而來也便於交流。最後,對於這種總結自己應該多做一些,一方面可以督促自己儘量去搞懂,另一方面也可以檢查自己的知識漏洞。

相關推薦

Keep Awake,Keep Studying,Keep Moving,Keep...

        事情的起因是這樣的,因為需要在Email的顯示介面中為電話號碼新增高亮顯示,從而提高使用者體驗。我想用過google自帶郵箱的朋友都知道,當我們的收到郵件的內容中含有網址,郵箱地址時,會在顯示介面中以超連結的方式顯示,此時當我們點選該網址或者郵箱地址時,便會

Keep Moving~

用例圖主要用來描述“使用者、需求、系統功能單元”之間的關係。它展示了一個外部使用者能夠觀察到的系統功能模型圖。  【用途】:幫助開發團隊以一種視覺化的方式理解系統的功能需求。   用例圖所包含的元素如下:   1. 參與者(Actor)   表示與您的應用程式或系

keep studying

題目描述 Every positive number can be presented by the exponential form.For example, 137 = 2^7 + 2^3 + 2^0。 Let’s present a^b

BNUOJ 52511 Keep In Line

class blog ring break 需要 main code bre 是否 隊列,$map$。 每次出隊進行出隊操作的是時候,先把隊列中需要出隊的人全部出隊,然後比較對頭和當前出隊的人是否相同。 #include<bits/stdc++.h>

HTTP/1.0+ "keep-alive" 連接

通過 保持 就會 無法 首部 報文 response line -a 一、keep-alive 連接 (1) 我們在使用串行連接的時候,比如加載四張圖片,當加載第一張圖片時,會建立連接,加載完後會關閉連接,加載第二張圖片時同樣會先建立連接再關閉連接,以此類推,這樣就會消耗

Uva 1153 Keep the Customer Satisfied (貪心+優先隊列)

最長 題意 code log algo cmp cst name node 題意:已知有n個工作,已知每個工作需要的工作時間qi和截至時間di,工作只能串行完成,問最多能完成多少個工作 思路:首先我們按照截至時間從小到大排序,讓它們依次進入優先隊列中,當發生執行完成時間大於

HTTP協議頭部與Keep-Alive模式詳解

兩個 conn exp uid iteye 想象 ket -c ack 1、什麽是Keep-Alive模式? 我們知道HTTP協議采用“請求-應答”模式,當使用普通模式,即非KeepAlive模式時,每個請求/應答客戶和服務器都要新建一個連接,完成

005推斷兩個字符串是否是變位詞 (keep it up)

right sans color amp 兩個 我們 nag 排序 isa 寫一個函數推斷兩個字符串是否是變位詞。變位詞(anagrams)指的是組成兩個單詞的字符同樣,但位置不同的單詞。比方說, abbcd和abcdb就是一對變位詞 這也是簡單的題。 我們能夠排序然

TCP keep-alive - 判斷TCP鏈路的連接情況

pan 內連接 soc 當前 簡單的 span lose keep 數據傳輸 TCP 是面向連接的 , 在實際應用中通常都需要檢測對端是否還處於連接中。如果已斷開連接,主要分為以下幾種情況: 1. 連接的對端正常關閉,即使用 closesocket 關閉

HTTTP及TCP的超時以及KEEP-ALIVE機制小結

詳解 int 客戶 博客 abc key html htttp cee 一、HTTP的超時和Keep Alive HTTP Keepalive 機制是http 1.1中增加的一個功能。 在HTTP 1.0中,客戶端每發起一個http 請求,等收到接收方的應答之後就斷開T

vue2.0 keep-alive最佳實踐

pat out blank 名稱 red 減少 基本用法 keep ref   轉自:https://segmentfault.com/a/1190000008123035 1.基本用法 vue2.0提供了一個keep-alive組件用來緩存組件,避免多次加載相應的組件,減

HDU 5744 Keep On Movin (思維題,水題)

amp tle 輸出 ngs pro ber end use view Problem Description Professor Zhang has kinds of characters and the quantity of the i-th character is

HTTP Keep-Alive的作用

服務器 但是 web keepalive 客戶端 解決 httpd 應用 時間 HTTP Keep-Alive的作用 作用:Keep-Alive:使客戶端到服務器端的連接持續有效,當出現對服務器的後繼請求時,Keep-Alive功能避免了建立或者重新建立連接。Web服務器,

<keep-alive>控制Vue Router路由

view homepage 信息 app keepal div path router class 只給部分組件加上<keep-alive>啊,在app.vue裏這樣 <!-- 這裏是需要keepalive的 --> <keep-alive&

Android -- 自定義view實現keep歡迎頁倒計時效果

super onfinish -m use new getc awt ttr alt 1,最近打開keep的app的時候,發現它的歡迎頁面的倒計時效果還不錯,所以打算自己來寫寫,然後就有了這篇文章。 2,還是老規矩,先看一下我們今天實現的效果   相較於我們常見的倒計時

組件的 keep-alive 簡介

兩個 對象 hydra gist dom pat 會同 代碼 處理 本篇文章,我們來講一下keep-alive的實現。 Vue中,有三個內置的抽象組件,分別是keep-alive、transition和transition-group, 它們都有一個共同的特點,就是自身不會

http keep-alive簡解

結束 編碼 完全 靜態頁面 1.0 是否 客戶端 問題 connect http協議中,客戶端發送請求,服務端再接收到請求後返回所需要的數據後即關閉連接,這樣客戶端讀取數據局時會返回EOF(-1),表明數據已接受完全 備註:EOF end of file 什麽是keep-a

keep-alive 路由跳轉後不刷新頁面

-a this 之前 一次 ram 並且 觸發 卸載 但是 使用keep-alive記住了狀態 通過路由跳轉並且攜帶了參數,之前this.$route.params.list卸載mounted中,第一次進入能夠更新,但是後面再次進入就不會更新了。 借用別人的話 當引入kee

聊聊keep-alive組件的使用及其實現原理

white pes input clas cto lods num mco tumx 寫在前面 因為對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,並做了總結與輸出。 文章的原地址:https://git

HTTP ------ connection 為 close 和 keep-alive 的區別

圖片 tcp連接 三次握手 字段 tcp 其它 時代 http 網頁 keep-alive和close這個要從TCP握手講起HTTP請求是基於TCP連接的,TCP的請求會包含(三次握手,中間請求,四次揮手)在HTTP/1.0時代,一個HTTP請求就要三次握手和四次揮手,當一