上一篇文章中我們講解了在Android App的實際開發中,盡量不在靜態變量、全局變量中保存數據,這是因為App的進程可能是不安全的,在部分手機中其有可能被系統殺死,從而造成靜態全局變量重新初始化。而這時候App當前頁面的Activity還會被保存在內存中,從而造成App并沒有被重啟的假象,但是這只是顯示的頁面沒有被殺死,而進程實際上是被重新啟動了的。這時候在使用已被重新初始化的靜態變量就會發生一些不可預知的錯誤,具體關于不在靜態變量中保存數據的問題,可以參考這里的: [Android產品研發(十)–>不使用靜態變量保存數據](http://blog.csdn.net/qq_23547831/article/details/51655330)
而本文講解的是一種App內頁面跳轉協議,這里的跳轉包括應用內跳轉、H5與Native跳轉,服務器通知客戶端如何跳轉等。
在講解應用內跳轉協議之前我們先講解一下H5與Native相互跳轉的相關知識點。現在越來越多的App采用了Native + H5方式開發,其中Native與H5頁面如何交互?google提供了一個公共的方式:js與native互調,即js可以調用Native方法,Native同樣也可以調用js方法;
**但是這種交互方式存在著不少問題**:
1、Java 調用 js 里面的函數、效率并不是很高、估計要200ms左右吧、做交互性很強的事情、這種速度很難讓人接受、而js去調Java的方法、速度很快、50ms左右、所以盡量用js調用Java方法
2、Java 調用 js 的函數、沒有返回值、調用了就控制不到了
3、Js 調用 Java 的方法、返回值如果是字符串、你會發現這個字符串是 native 的、轉成 locale 的才能正常使用、使用 toLocaleString() 函數就可以了、不過這個函數的速度并不快、轉化的字符串如果很多、將會很耗費時間
4、網頁中盡量不要使用jQuery、執行起來需要5-6秒、最好使用原生的js寫業務腳本、以提升加載速度、改善用戶體驗
5、Android4.2以下的系統存在著webview的js對象注入漏洞…(不清楚的可以google)
基于這種種的原因,我們并未采用這種方式用于Native與webview交互,而是采用scheme + cookie的方式;
這里的scheme是一種頁面內跳轉協議,主要用于支持一下幾種場景:
* 服務器下發跳轉路徑,客戶端根據服務器下發跳轉路徑跳轉相應的頁面;
* H5頁面點擊錨點,根據錨點具體跳轉路徑App端跳轉具體的頁面;
* App端收到服務器端下發的PUSH通知欄消息,根據消息的點擊跳轉路徑跳轉相關頁面
下面我將簡單介紹一下scheme的基本概念以及以上三種場景下scheme的具體應用。
### URL scheme 概述
#### URL scheme 的作用
客戶端應用可以向操作系統注冊一個 URL scheme,該 scheme 用于從瀏覽器或其他應用中啟動本應用。通過指定的 URL 字段,可以讓應用在被調起后直接打開某些特定頁面,比如車輛詳情頁、訂單詳情頁、消息通知頁、促銷廣告頁等等。也可以執行某些指定動作,如訂單支付等。也可以在應用內通過 html 頁來直接調用顯示 app 內的某個頁面。
#### URL scheme 的格式
客戶端自定義的 URL 作為從一個應用調用另一個的基礎,遵循 RFC 1808 (Relative Uniform Resource Locators) 標準。這跟我們常見的網頁內容 URL 格式一樣。
一個普通的 URL 分為幾個部分,**scheme**、**host**、**relativePath**、**query**。
比如:`http://www.baidu.com/s?rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709`,這個URL中,scheme 為 http,host 為 www.baidu.com,relativePath 為 /s,query 為 rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709。
一個應用中使用的 URL 例子(該 URL 會調起車輛詳情頁):uumobile://mobile/carDetail?car_id=123456,其中 scheme 為 uumobile,host 為 mobile,relativePath 為 /carDetail,query 為 car_id=123456。
#### Scheme定義Activity
1)在Androidmanifest.xml中定義scheme
~~~
<!-- scheme協議 -->
<activity
Android:name=".UI.translate.NativeAppActivity"
Android:label="@string/app_name">
<!-- 要想在別的App上能成功調起App,必須添加intent過濾器 -->
<intent-filter>
<!-- 協議部分,隨便設置 -->
<data Android:scheme="uumobile" />
<!-- 下面這幾行也必須得設置 -->
<category Android:name="Android.intent.category.DEFAULT" />
<category Android:name="Android.intent.category.BROWSABLE" />
<action Android:name="Android.intent.action.VIEW" />
</intent-filter>
</activity>
~~~
這樣我們便定義了能夠接受scheme請求的activity實例,當網頁或者是Android代碼發送這種規則scheme的請求的時候就能夠吊起NativeAppActivity了。
2)當然就是實現NativeAppActivity
~~~
/**
* Created by admin
*/
public class NativeAppActivity extends Activity{
public String tag = "NativeAppActivity";
public Activity mContext = null;
public void onCreate(Bundle b)
{
super.onCreate(b);
mContext = this;
Uri uri = getIntent().getData();
if (uri != null)
{
List<String> pathSegments = uri.getPathSegments();
String uriQuery = uri.getQuery();
Intent intent;
if (pathSegments != null && pathSegments.size() > 0) {
// 解析SCHEME
if (someif) {
dosomething();
}
else {
// 若解析不到SCHEME,則關閉NativeAppActivity;
finish();
}
} else {
finish();
}
} else {
finish();
}
}
}
~~~
NativeAppActivity這個類中主要用于實現對scheme的解析,然后做出相應的動作,比如請求scheme跳轉登錄頁面,我們可以這樣定義
~~~
uumobile://appname/gotoLogin
~~~
然后我們解析出scheme如果是這樣的結構就跳轉登錄頁面。。。
這里簡單說一下,我們可以通過Intent對象獲取調用的scheme的host等信息
~~~
this.getIntent().getScheme();//獲得Scheme名稱
this.getIntent().getDataString();//獲得Uri全部路徑
~~~
3)通過服務器下發跳轉路徑跳轉相應頁面
~~~
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("uumobile://yongche/123123123")));
~~~
這里的”uumobile://yongche/123123123”就是服務器下發的跳轉路徑,當我們執行startActivity的時候就會調起NativeAppActivity,然后我們通過在NativeAppActivity解析scheme的內容,跳轉相應的頁面。
4)通過在H5頁面的錨點跳轉相應的頁面
~~~
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//解析scheme
if (url.indexOf(H5Constant.SCHEME) != -1) {
try {
Uri uri = Uri.parse(url);
String[] urlSplit = url.split("\\?");
Map<String, String> queryMap = new HashMap<String, String>();
String h5Url = null;
if (urlSplit.length == 2) {
queryMap = H5Constant.parseUriQuery(urlSplit[1]);
h5Url = queryMap.get(H5Constant.MURL);
}
// 跳轉NativeAppActivity解析
{
// 若設置刷新,則刷新頁面
if (queryMap.containsKey(H5Constant.RELOADPRE) && "1".equals(queryMap.get(H5Constant.RELOADPRE))) {
h5Fragment.isNeedFlushPreH5 = true;
}
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
h5Activity.startActivityForResult(intent, H5Constant.h5RequestCode);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
// 打電話
else if (url.indexOf("tel://") != -1) {
final String number = url.substring("tel://".length());
Config.callPhoneByNumber(h5Activity, number);
return true;
} else if (url.indexOf("tel:") != -1) {
final String number = url.substring("tel:".length());
Config.callPhoneByNumber(h5Activity, number);
return true;
}
// 其他跳轉方式
else {
view.loadUrl(url);
//如果不需要其他對點擊鏈接事件的處理返回true,否則返回false
return false;
}
}
~~~
可以發現我們為Webview設置了WebViewClient,并重寫了WebViewClient的shouldOverrideUrlLoading方法,然后我們解析錨點的url,并根據解析的內容調起NativeAppActivity的scheme Activity,然后在NativeAppActivity中解析scheme的內容并跳轉相應的頁面。
5)根據服務器下發通知欄消息,App跳轉相應的頁面
~~~
public class NotificationActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
L.i("接收到通知點擊事件...");
Intent realIntent = getIntent().getParcelableExtra(NotifeConstant.REAL_INTENT);
// 解析scheme并跳轉
gotoRealScheme(this, realIntent);
}
/**
* notification中跳轉SCHEME,根據有效時間判斷跳轉URL地址
* 跳轉之后更具網絡請求判斷用戶當前狀態
*/
private void gotoRealScheme(Context context, Intent realIntent) {
if (realIntent == null || context == null) {
finish();
return;
}
try {
L.i("開始解析通知中的參數...");
long startShowTime = realIntent.getLongExtra(NotifeConstant.START_SHOW_TIME, 0);
// 有效期時間,單位:s(秒)
long validTime = realIntent.getLongExtra(NotifeConstant.VALID_TIME, 0);
long currentTime = System.currentTimeMillis();
String validActionUrl = realIntent.getStringExtra(NotifeConstant.VALID_ACTION_URL);
String invalidActionUrl = realIntent.getStringExtra(NotifeConstant.INVALID_ACTION_URL);
Intent schemeIntent;
L.i("開始根據URL構建Intent對象...");
if ((currentTime - startShowTime) / 1000L <= validTime) {
schemeIntent = H5Constant.buildSchemeFromUrl(validActionUrl);
} else {
schemeIntent = H5Constant.buildSchemeFromUrl(invalidActionUrl);
}
if (schemeIntent != null) {
// 設置當前頁面為通知欄打開
Config.isNotificationOpen = true;
context.startActivity(schemeIntent);
finish();
//對通知欄點擊事件統計
MobclickAgent.onEvent(context, UMCountConstant.PUSH_NOTIFICATION_CLICK);
} else {
finish();
}
} catch (Exception e) {
// 異常情況下退出當前Activity
finish();
}
}
}
~~~
服務器下發的所有的通知都是先跳轉這里的NotificationActivity,然后在這里執行跳轉其他Activity的邏輯,而這里的H5Constant的buildSchemeFromUrl方法就是構造跳轉頁面Intent對象的,我們可以看一buildSchemeFromUrl方法的具體實現:
~~~
/**
* 從scheme的url中構建出Intent,用于界面跳轉
*
* @param url
* @return
*/
public static Intent buildSchemeFromUrl(String url) {
if (url != null && url.indexOf(H5Constant.SCHEME) != -1) {
Uri uri = Uri.parse(url);
String[] urlSplit = url.split("\\?");
Map<String, String> queryMap = new HashMap<String, String>();
String h5Url = null;
if (urlSplit.length == 2) {
queryMap = H5Constant.parseUriQuery(urlSplit[1]);
h5Url = queryMap.get(H5Constant.MURL);
}
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (!TextUtils.isEmpty(h5Url)) {
intent.putExtra(H5Constant.MURL, h5Url);
}
return intent;
}
return null;
}
~~~
這樣我們就搞構造除了跳轉NativeAppActivity的Intent對象,并將scheme字符串傳遞給了NativeAppActivity,這樣在NativeAppActivity中就可以解析scheme字符串并執行相應的跳轉邏輯了。
**總結**:
Android中的scheme是一種非常好的實現機制,通過定義自己的scheme協議,可以非常方便跳轉app中的各個頁面;
通過scheme協議,服務器可以定制化告訴App跳轉那個頁面,可以通過通知欄消息定制化跳轉頁面,可以通過H5頁面跳轉頁面等。
另外對產品研發技術,技巧,實踐方面感興趣的同學可以參考我的:
[android產品研發(一)-->實用開發規范 ](http://blog.csdn.net/qq_23547831/article/details/51534013)
[android產品研發(二)-->啟動頁優化 ](http://blog.csdn.net/qq_23547831/article/details/51541277)
[android產品研發(三)-->基類Activity ](http://blog.csdn.net/qq_23547831/article/details/51546974)
[android產品研發(四)-->減小Apk大小](http://blog.csdn.net/qq_23547831/article/details/51559066)
[android產品研發(五)-->多渠道打包](http://blog.csdn.net/qq_23547831/article/details/51569261)
[Android產品研發(六)–>Apk混淆](http://blog.csdn.net/qq_23547831/article/details/51581491)
[android產品研發(七)-->Apk熱修復](http://blog.csdn.net/qq_23547831/article/details/51587927)
[Android產品研發(八)–>App數據統計](http://blog.csdn.net/qq_23547831/article/details/51612429)
[Android產品研發(九)–>App網絡傳輸協議](http://blog.csdn.net/qq_23547831/article/details/51655330)
[Android產品研發(十)–>不使用靜態變量保存數據](http://blog.csdn.net/qq_23547831/article/details/51685310)
- 前言
- (一)–>實用開發規范
- (二)-->啟動頁優化
- (三)-->基類Activity
- (四)-->減小Apk大小
- (五)-->多渠道打包
- (六)-->Apk混淆
- (七)-->Apk熱修復
- (八)-->App數據統計
- (九)-->App網絡數據解析
- (十)-->盡量不使用靜態變量保存數據
- (十一)-->應用內跳轉Scheme協議
- (十二)-->App長連接實現
- (十三)-->App輪詢操作
- (十四)-->App升級與更新
- (十五)-->內存對象序列化
- (十六)-->開發者選項
- (十七)-->Hybrid開發
- (十八)-->webview問題集錦
- (十九)-->Android studio中的單元測試
- (二十)-->代碼Review
- (二十一)-->Android中的UI優化
- (二十二)-->Android實用調試技巧
- (二十三)-->Android中保存靜態秘鑰實踐
- (二十四)-->內存泄露場景與檢測
- (二十五)-->MVC/MVVM/MVP簡單理解