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請求就要三次握手和四次揮手,當一