IntentUri页面跳转
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进行处理。
配置
1 2 3 4 5 6 7 8 | <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类里规则串如下,
1 | 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就很好理解了,下面看一下主要代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | 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方法即可以扩展到更多功能中,下面看一下我的修改,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 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)},这里我使用开闭区间的方式来指定及范围,这种方式更精简使用,版本解析处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | /** * 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 ; } |
测试使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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的代码设计后,真是觉得源码设计的十分巧妙,值得仔细认真琢磨。
参考:关于Intent Uri页面跳转 - HappyCode002 - 博客园
https://zhuanlan.zhihu.com/p/449084824
https://www.51cto.com/article/554845.html