• 关于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的代码设计后,真是觉得源码设计的十分巧妙,值得仔细认真琢磨。

  • 相关阅读:
    HMMPfam的安装使用手记(转载)
    Linux下MySQL忘记密码的解决方法
    systemctl命令
    Linux配置ssh免密登录
    Linux下scp报Permission denied错误的解决方法
    Ubuntu修改时区和时间
    Ubuntu安装JDK
    Ubuntu用apt安装MySQL
    IntelliJ IDEA集成工具Database连接MySQL8.0报错的解决方法
    tar命令
  • 原文地址:https://www.cnblogs.com/happyxiaoyu02/p/7366807.html
Copyright © 2020-2023  润新知