關於Intent Uri頁面跳轉


 

android browser支持支持Intent Scheme URL語法的可以在wrap頁面加載或點擊時,通過特定的intent uri鏈接可以打開對應app頁面,例如

 <a href="intent://whatsapp/#Intent;scheme=myapp;package=com.xiaoyu.myapp;S.help_url=http://Fzxing.org;end">what can I do</a>  

通過android系統解析查找符合規則的Activity后啟動對應activity,如果未找到匹配Activity則browser進行處理。

配置

 <a href="intent://whatsapp/#Intent;scheme=myapp;package=com.xiaoyu.myapp;S.help_url=http://Fzxing.org;end">what can I do</a>  
	//這樣如果沒有對應應用,該鏈接就會跳轉到S.help_url指定的url上
	<intent-filter>  
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />  
        <category android:name="android.intent.category.BROWSABLE" /><!-- 定義瀏覽器類型,有URL需要處理時會過濾 -->  
        <data android:scheme="myapp" android:host="whatsapp" android:path="/" /><!-- 打開以whatsapp協議的URL,這個自己定義 -->  
    </intent-filter>  

  

URI的解析與生成:

在Intent 類內部有個parseUri(String uri, int flags)方法用來解析Uri生成Intent返回,下面主要分析該方法:

在分析該解析方法時候,首先我們需要理解intent uri規則,在intent類里規則串如下,

android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;<br />i.some_int=100;S.some_str=hello;end

 根據此規則,看出主要分為三個部分:

Header: android-app://android app為判斷是否是android 應用

Action: com.example.MY_ACTION //組件的action內容

Package: com.example.app //包名啟動組件使用

Extra: some_int=(int)100,some_str=(String)hello //傳遞的數據,i代表int,S代表String ,c代表char等基本類型

除了這幾部分內容也包含intent內的其它內容,如categry,type,component,selector等

在簡單分析理解Uri規則串后,再來分析parseUri就很好理解了,下面看一下主要代碼

 

 public static Intent parseUri(String uri, int flags) throws URISyntaxException {
        int i = 0;
        try {
            final boolean androidApp = uri.startsWith("android-app:");//判斷開頭是否為android,

            // Validate intent scheme if requested.
            if ((flags&(URI_INTENT_SCHEME|URI_ANDROID_APP_SCHEME)) != 0) {
                if (!uri.startsWith("intent:") && !androidApp) {//是否為intent Uri
                    Intent intent = new Intent(ACTION_VIEW);
                    try {
                        intent.setData(Uri.parse(uri));
                    } catch (IllegalArgumentException e) {
                        throw new URISyntaxException(uri, e.getMessage());
                    }
                    return intent;
                }
            }

            i = uri.lastIndexOf("#");//查找標記位,開始解析
            // simple case
            if (i == -1) {
                if (!androidApp) {
                    return new Intent(ACTION_VIEW, Uri.parse(uri));
                }

            // old format Intent URI
            } else if (!uri.startsWith("#Intent;", i)) {
                if (!androidApp) {
                    return getIntentOld(uri, flags);
                } else {
                    i = -1;
                }
            }

            // new format
            Intent intent = new Intent(ACTION_VIEW);
            Intent baseIntent = intent;
            boolean explicitAction = false;
            boolean inSelector = false;

            // fetch data part, if present
            String scheme = null;
            String data;
            if (i >= 0) {
                data = uri.substring(0, i);
                i += 8; // length of "#Intent;"
            } else {
                data = uri;
            }

            // loop over contents of Intent, all name=value;
            while (i >= 0 && !uri.startsWith("end", i)) {//按類別分離循環處理(解析action,categry,type,extra data)
                int eq = uri.indexOf('=', i);
                if (eq < 0) eq = i-1;
                int semi = uri.indexOf(';', i);
                String value = eq < semi ? Uri.decode(uri.substring(eq + 1, semi)) : "";

                // action
                if (uri.startsWith("action=", i)) {
                    intent.setAction(value);
                    if (!inSelector) {
                        explicitAction = true;
                    }
                }

                // categories
                else if (uri.startsWith("category=", i)) {
                    intent.addCategory(value);
                }

                // type
                else if (uri.startsWith("type=", i)) {
                    intent.mType = value;
                }

                // launch flags
                else if (uri.startsWith("launchFlags=", i)) {
                    intent.mFlags = Integer.decode(value).intValue();
                    if ((flags& URI_ALLOW_UNSAFE) == 0) {
                        intent.mFlags &= ~IMMUTABLE_FLAGS;
                    }
                }

                // package
                else if (uri.startsWith("package=", i)) {
                    intent.mPackage = value;
                }

                // component
                else if (uri.startsWith("component=", i)) {
                    intent.mComponent = ComponentName.unflattenFromString(value);
                }

                // scheme
                else if (uri.startsWith("scheme=", i)) {
                    if (inSelector) {
                        intent.mData = Uri.parse(value + ":");
                    } else {
                        scheme = value;
                    }
                }

                // source bounds
                else if (uri.startsWith("sourceBounds=", i)) {
                    intent.mSourceBounds = Rect.unflattenFromString(value);
                }

                // selector
                else if (semi == (i+3) && uri.startsWith("SEL", i)) {
                    intent = new Intent();
                    inSelector = true;
                }

                // extra data parse
                else {
                    String key = Uri.decode(uri.substring(i + 2, eq));
                    // create Bundle if it doesn't already exist
                    if (intent.mExtras == null) intent.mExtras = new Bundle();
                    Bundle b = intent.mExtras;
                    // add EXTRA,這里巧妙的對基本數據類型進行處理,(字母.var)值得學習借鑒
                    if      (uri.startsWith("S.", i)) b.putString(key, value);
                    else if (uri.startsWith("B.", i)) b.putBoolean(key, Boolean.parseBoolean(value));
                    else if (uri.startsWith("b.", i)) b.putByte(key, Byte.parseByte(value));
                    else if (uri.startsWith("c.", i)) b.putChar(key, value.charAt(0));
                    else if (uri.startsWith("d.", i)) b.putDouble(key, Double.parseDouble(value));
                    else if (uri.startsWith("f.", i)) b.putFloat(key, Float.parseFloat(value));
                    else if (uri.startsWith("i.", i)) b.putInt(key, Integer.parseInt(value));
                    else if (uri.startsWith("l.", i)) b.putLong(key, Long.parseLong(value));
                    else if (uri.startsWith("s.", i)) b.putShort(key, Short.parseShort(value));
                    else throw new URISyntaxException(uri, "unknown EXTRA type", i);
                }

                // move to the next item
                i = semi + 1;
            }

            if (inSelector) {
                // The Intent had a selector; fix it up.
                if (baseIntent.mPackage == null) {
                    baseIntent.setSelector(intent);
                }
                intent = baseIntent;
            }

            ......

            return intent;//解析生產內容,對應到生產的intent,返回處理

        } catch (IndexOutOfBoundsException e) {
            throw new URISyntaxException(uri, "illegal Intent URI format", i);
        }
    }

 

經過上面簡要分析,我們了解Uri intent生成過程,這種設計非常巧妙可以達到很好的解耦處理,相比於顯示與隱士啟動方式,這種方式更加靈活。

例如,我們可以由Server端動態下發,本地通過解析后在進行跳轉頁面,即可達到動態控制頁面功能,其實不僅可以應用到頁面跳轉中,如果進一步擴展,可以應用到更多功能中,

比如我們可以擴展自定義規則,如header改為:http,https,代表頁面跳轉到webActivity,header改為:tipe時為toast提示,改為dialog時為彈框顯示等。

我們還可以更為細致的控制,如可加入版本號指定對應的版本的app執行這規則,其余版本默認行為,適用於修復部分bug。由此可見我們可以通過修改parseUri方法即可以擴展到更多功能中,下面看一下我的修改,

static final String TAG="PageJump";
   
    static final String SCHEME_INTENT = "page";
    static final String SCHEME_ANDROIDAPP = "android-app:";
    static final String SCHEME_HTTP = "http";
    static final String SCHEME_HTTPS = "https";
    static final String SCHEME_TIPS_DIALOG = "tips_dialog";
    static final String SCHEME_TIPS_TOAST = "tips_toast";

//動態解析實現對頁面行為控制
    public static void jumpPageUri(Context context, String strUri) throws URISyntaxException{
        //{(2,5][8,12)}android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;
            if (TextUtils.isEmpty(strUri)) {
                throw new NullPointerException("parser uri content is empty");
            }

            String data = strUri.trim();
            //uri是否在版本內
            final int curVer = Utils.getVerCode(context);
            if(isInRangeVersion(data,curVer)){
                return;
            }
            //remove command version part
            if(data.startsWith("{")) {
                int verEnd = data.indexOf('}', 1);
                data = data.substring(verEnd+1);
            }
            String uriData = data;

            if(uriData.startsWith(SCHEME_ANDROIDAPP)){
                Intent intent = Intent.parseUri(uriData, Intent.URI_INTENT_SCHEME);
                String appPackage = context.getPackageName();
                ComponentName componentName = intent.getComponent();

                //out intent
                if (componentName == null || !appPackage.contains(componentName.getPackageName())){
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(intent);
                }
            }
            else if (uriData.startsWith(SCHEME_INTENT)) {
                Intent sendIntent = UriProcessor.parseIntentUri(data, Intent.URI_INTENT_SCHEME);

                // Verify that the intent will resolve to an activity
//                if (null == sendIntent.resolveActivity(context.getPackageManager())) {
//                    throw new URISyntaxException(data, "not found match page");
//                }
                sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(sendIntent);
            }
            else if (uriData.startsWith(SCHEME_HTTP) || uriData.startsWith(SCHEME_HTTPS)) {
            //  WebViewActivity.launch(context, uri);
            }
            else if(uriData.startsWith(SCHEME_TIPS_DIALOG)){
            //  DialogUtil.showNormal("test");
            }
            else if(uriData.startsWith(SCHEME_TIPS_TOAST)){
            //  ToastUtils.showShortMessage("");
            }

    }

規則串前面增加了應用版本范圍,{(2,5][8,12)},這里我使用開閉區間的方式來指定及范圍,這種方式更精簡使用,版本解析處理

 /**
     * current command version whether contain current app version
     * @param data
     * @param curVer
     * @return
     */
    public static boolean isInRangeVersion(String data,final int curVer){
        if(data.startsWith("{")){
            int verEnd = data.indexOf('}', 1);

            if(verEnd>0) {
                String verStr = data.substring(0, verEnd+1);

                boolean in_range=true;
                int pos=1;
                try {
                    while (pos >= 0 && !verStr.startsWith("}")) {
                        in_range=true;
                        char ch = verStr.charAt(pos);
                        if (ch == '[' || ch == '(') {
                            boolean[] border=new boolean[2];
                            int semi = verStr.indexOf(',', pos);
                            int startVer = Integer.valueOf(verStr.substring(pos + 1, semi));
                            border[0]= (ch=='[');
                            int toVer = 0, flagVer = 0;
                            if ((flagVer = verStr.indexOf(']', semi)) >= 0 || (flagVer = verStr.indexOf(')', semi)) >= 0) {
                                toVer = Integer.valueOf(verStr.substring(semi + 1, flagVer));
                                border[1]= (verStr.charAt(flagVer)==']');
                            }
                            // judge current version code not inside range
                            // jude min version code < <=
                            if((border[0] && curVer<startVer) ||(!border[0] && curVer<=startVer)){
                                in_range=false;
                            }
                            // judge max version code > >=
                            if((border[1] && curVer>toVer) ||(!border[1] && curVer>=toVer)){
                                in_range=false;
                            }
                            pos = flagVer + 1;
                            if (pos + 1 >= verStr.length())
                                break;
                        }
                    }
                    return in_range;
                }catch (NumberFormatException ex){
                    Log.e(TAG,"parse regular expression version error!");
                }

            }
            return true;
        }
        return true;
    }

 

測試使用:

//    String jumpUri1="{(2,5][8,12)}android-app://com.example.app/#Intent;action=com.example.MY_ACTION;i.some_int=100;S.some_str=hello;end";
    String jumpUri2="{(0,3][6,12)}android-app://com.example.app/#Intent;action=com.example.MY_ACTION;i.some_int=100;end";
    String jumpUri3="{(0,6]}android-app://com.example.app/#Intent;action=com.example.MY_ACTION;i.some_int=100;end";
    String jumpUriPage="{(2,6]}android-app://com.example.myapp/#Intent;action=com.example.myapp.SecondActivity;package=com.example.myapp;category=android.intent.category.DEFAULT;S.some=systemFrom;end";
    String jumpUriPage2="{[1,8]}page#Intent;action=com.example.myaction;package=com.example.myapp;category=android.intent.category.DEFAULT;S.some=innerFrom;end";
	
	try {
        //    PageJump.jumpPageUri(getApplicationContext(),jumpUri1);
            PageJump.jumpPageUri(getApplicationContext(),jumpUri2);
            PageJump.jumpPageUri(getApplicationContext(),jumpUri3);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

 

分析intent的代碼設計后,真是覺得源碼設計的十分巧妙,值得仔細認真琢磨。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM