標籤:
轉載請標明出處:一片楓葉的專欄
上一篇文章中我們講解了在Android App的實際開發中,盡量不在靜態變數、全域變數中儲存資料,這是因為App的進程可能是不安全的,在部分手機中其有可能被系統殺死,從而造成靜態全域變數重新初始化。而這時候App當前頁面的Activity還會被儲存在記憶體中,從而造成App並沒有被重啟的假象,但是這隻是顯示的頁面沒有被殺死,而進程實際上是被重新啟動了的。這時候在使用已被重新初始化的靜態變數就會發生一些不可預知的錯誤,具體關於不在靜態變數中儲存資料的問題,可以參考這裡的: android產品研發(十)–>不使用靜態變數儲存資料
而本文講解的是一種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產品研發(一)–>實用開發規範
android產品研發(二)–>啟動頁最佳化
android產品研發(三)–>基類Activity
android產品研發(四)–>減小Apk大小
android產品研發(五)–>多渠道打包
android產品研發(六)–>Apk混淆
android產品研發(七)–>Apk熱修複
android產品研發(八)–>App資料統計
android產品研發(九)–>App網路傳輸協議
android產品研發(十)–>不使用靜態變數儲存資料
本文以同步至github中:https://github.com/yipianfengye/androidProject,歡迎star和follow
android產品研發(十一)-->應用內跳轉scheme協議