本文主要介绍OAuth的用处、OAuth的流程、腾讯微博OAuth认证示例(新浪、人人类似)以及一些认证的异常。
1、OAuth介绍
目前很多主流的用户权限认证都是用OAuth,像google、microsoft、yahoo、人人、新浪微博、腾讯微博。只不过各自使用的OAuth版本可能略有不同。
使用OAuth的一个好处就是在用户向服务器数据请求时,避免了每次都需要传输用户名和密码,通过access token和secret使得用户在正常访问数据的同时保证了用户帐号的安全性。
OAuth比较适合的web应用程序和提供服务器端api或者两者混合的场景,OAuth支持目前大部分的主流语言。
更多关于OAuth见:http://www.oauth.net
2、OAuth流程
OAuth的流程最终的结果是为了得到可以访问数据的access token和ccess secret(可能没有),以后就通过此access token和access secret和服务器进行交互。
大致的流程分为三步(OAuth1.0和2.0可能有点差异):
a 先获得一个未授权的request token,或者叫request code
b 以上步的未授权的token换取授权的request token和request secret(可能没有),这一步之后一般会提示输入用户名、密码
c 使用上步授权后的request token换取access token和access secret(可能没有)
现在就得到了access token和ccess secret(可能没有),使用它们就可以同服务器交互访问数据,而不用每次传递用户名和密码
3、腾讯微博OAuth api介绍
目前腾讯微博使用的是OAuth1.0、新浪微博使用的是OAuth2.0、人人网使用的是OAuth2.0,这里只介绍腾讯微博,关于人人和新浪类似,大家可以自己修改。
因自己写的腾讯微博sdk中默认不带oauth认证过程,不少朋友问到如何进行认证,这里就大致贴代码介绍下,有点长,可看下大概明白意思,自己再根据需要精简。主要分为三个部分:
第一部分:调用认证函数,跳转到认证页面
认证函数如下
private static QqTSdkService qqTSdkService = new QqTSdkServiceImpl();
/**
* OAuth部分参见http://wiki.open.t.qq.com/index.php/API%E6%96%87%E6%A1%A3#category_1
*/
@Override
public Intent auth(Context context, String callBackUrl) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
QqTAppAndToken qqTAppAndToken = new QqTAppAndToken();
qqTAppAndToken.setAppKey(APP_KEY);
qqTAppAndToken.setAppSecret(APP_SECRET);
qqTSdkService.setQqTAppAndToken(qqTAppAndToken);
Map<String, String> requestTokenMap = qqTSdkService.getUnAuthorizedRequestToken(callBackUrl);
if (!MapUtils.isEmpty(requestTokenMap) && requestTokenMap.containsKey(QqTConstant.PARA_OAUTH_TOKEN)) {
Map<String, String> parasMap = new HashMap<String, String>();
parasMap.put(QqTConstant.PARA_OAUTH_TOKEN, requestTokenMap.get(QqTConstant.PARA_OAUTH_TOKEN));
bundle.putString(SnsConstant.OAUTH_URL,
HttpUtils.getUrlWithParas(QqTConstant.GET_AUTHORIZATION_URL, parasMap));
bundle.putString(SnsConstant.CALL_BACK_URL, callBackUrl);
bundle.putString(SnsConstant.REQUEST_TOKEN_SECRET, requestTokenMap.get(SnsConstant.REQUEST_TOKEN_SECRET));
intent.putExtras(bundle);
intent.setClass(context, OAuthWebViewActivity.class);
}
return intent;
}
a. 两个参数第一个为activity中getApplicationContext();得到的context
第二个为认证成功返回的url,对于android的activity格式为"appName://activityClassName",其中appname
为应用名,activityClassName为activity的类名。为了认证后能正确跳转到activity,需要在AndroidManifest.xml中添加相应的activity的intent-filter如下,相当于host配置
<intent-filter>
<data android:scheme="appName" android:host="activityClassName" />
</intent-filter>
b. QqTSdkService、MapUtils、QqTConstant、HttpUtils的引用见腾讯微博java(android) api
c. SnsContant 中的一些常量定义如下
/** 程序中用到的一些字符串常量 **/
public static final String WEBSITE_TYPE = "websiteType";
public static final String OAUTH_URL = "oAuthUrl";
public static final String CALL_BACK_URL = "callBackUrl";
public static final String REQUEST_TOKEN_SECRET = "oauth_token_secret";
public static final String STATUS_ID = "statusId";
public static final String COMMENT_TYPE = "commentType";
public static final String COMMENT_ID = "commentId";
d. OAuthWebViewActivity的就是认证页面,代码见第二部分
activity中调用认证函数
Intent intent = auth(context, "appName://activityClassName");
if (intent == null || intent.getExtras() == null
|| !intent.getExtras().containsKey(SnsConstant.CALL_BACK_URL)) {
// Toast.makeText(this, "进入认证页面失败", Toast.LENGTH_SHORT).show();
return;
} else {
startActivity(intent);
}
第二部分:进入认证页面
OAuthWebViewActivity的代码如下,就是一个webview加载授权页面
package com.trinea.sns.activity;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.trinea.sns.util.CodeRules;
import com.trinea.sns.util.SnsConstant;
/**
* 认证的webView
*
* @author Trinea 2012-3-20 下午08:42:41
*/
public class OAuthWebViewActivity extends Activity {
private WebView authWebView = null;
private Intent intent = null;
private String callBackUrl;
private String requestTokenSecret;
public static OAuthWebViewActivity webInstance = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_PROGRESS);
setContentView(R.layout.web_view);
setTitle("腾讯微博授权认证");
webInstance = this;
authWebView = (WebView)findViewById(R.id.authWebView);
WebSettings webSettings = authWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setSaveFormData(true);
webSettings.setSavePassword(true);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
authWebView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
authWebView.requestFocus();
return false;
}
});
// 根据传递过来的信息,打开相应的授权页面
intent = this.getIntent();
if (!intent.equals(null)) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(SnsConstant.OAUTH_URL)) {
authWebView.loadUrl(bundle.getString(SnsConstant.OAUTH_URL));
if (bundle.getString(SnsConstant.CALL_BACK_URL) != null) {
callBackUrl = bundle.getString(SnsConstant.CALL_BACK_URL);
}
if (bundle.getString(SnsConstant.REQUEST_TOKEN_SECRET) != null) {
requestTokenSecret = bundle.getString(SnsConstant.REQUEST_TOKEN_SECRET);
}
authWebView.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
setTitle("腾讯微博授权页面加载中,请稍候..." + progress + "%");
setProgress(progress * 100);
if (progress == 100) {
setTitle(R.string.app_name);
}
}
});
authWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(/blog_article/url/index.html);
return true;
}
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
if (url != null && url.startsWith(callBackUrl)) {
Class backClass = CodeRules.getActivityClass(CodeRules.getActivityNameFromUrl(/blog_article/callBackUrl/index.html));
if (backClass != null) {
Intent intent = new Intent(OAuthWebViewActivity.this, backClass);
Bundle backBundle = new Bundle();
backBundle.putString(SnsConstant.REQUEST_TOKEN_SECRET, requestTokenSecret);
intent.putExtras(backBundle);
Uri uri = Uri.parse(url);
intent.setData(uri);
startActivity(intent);
}
}
}
});
}
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
/**
* 监听BACK键
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
if (authWebView.canGoBack()) {
authWebView.goBack();
} else {
// OAuthActivity.webInstance.finish();
finish();
}
return true;
}
return super.onKeyDown(keyCode, event);
}
}
a. R.layout.web_view为
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<WebView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/authWebView">
</WebView>
</LinearLayout>
b. CodeRules.getActivityClass(CodeRules.getActivityNameFromUrl(/blog_article/callBackUrl/index.html));作用是从url中获得到activity对应的类,方便在跳转回activity,即根据"appName://activityClassName"得到activityClassName的Class
c. public void onPageStarted(WebView webView, String url, Bitmap favicon)表示监听webView页面开始加载事件
if (url != null && url.startsWith(callBackUrl)) 表示认证已经成功,开始加载callBackUrl("appName://activityClassName"),这个时候我们让它跳转到对应的activity,这个时候的url中已经包含了accessToken和accessSecret
在第一部分startActivity后跳转到认证页面,填入帐号和密码并点击授权便可进入上面c的onPageStarted,这个时候我们已经得到了accessToken和accessSecret
第三部分 认证返回处理
在返回的activity中添加OnNewIntent函数,需要在AndroidManifest.xml中添加相应的activity的属性android:launchMode="singleTask"
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle bundle = intent.getExtras();
if (bundle != null) {
UserInfo userInfo = authBack(intent.getData(), bundle.getString(SnsConstant.REQUEST_TOKEN_SECRET));
if (userInfo != null) {
Toast.makeText(this, "获取用户信息失败,请重新验证", Toast.LENGTH_SHORT).show();
OAuthWebViewActivity.webInstance.finish();
} else {
Toast.makeText(this, "获取用户信息失败,请重新验证", Toast.LENGTH_SHORT).show();
}
}
}
其中authBack函数如下
@Override
public UserInfo authBack(Uri uri, String requestTokenSecret) {
if (uri == null) {
return null;
}
QqTAppAndToken qqTAppAndToken = new QqTAppAndToken();
qqTAppAndToken.setAppKey(SnsConstant.QQT_APP_KEY);
qqTAppAndToken.setAppSecret(SnsConstant.QQT_APP_SECRET);
qqTSdkService.setQqTAppAndToken(qqTAppAndToken);
Map<String, String> requestTokenMap = qqTSdkService.getAuthorizedRequestToken(uri.getQuery());
if (MapUtils.isEmpty(requestTokenMap) || !requestTokenMap.containsKey(QqTConstant.PARA_OAUTH_TOKEN)
|| !requestTokenMap.containsKey(QqTConstant.PARA_OAUTH_VERIFIER)) {
return null;
}
Map<String, String> accessTokenMap = qqTSdkService.getAccessToken(requestTokenMap.get(QqTConstant.PARA_OAUTH_TOKEN),
requestTokenMap.get(QqTConstant.PARA_OAUTH_VERIFIER),
requestTokenSecret);
if (!MapUtils.isEmpty(accessTokenMap) || accessTokenMap.containsKey(QqTConstant.PARA_OAUTH_TOKEN)
|| accessTokenMap.containsKey(QqTConstant.PARA_OAUTH_TOKEN_SECRET)) {
return UserInfoUtils.createUserInfo(websiteType, null, accessTokenMap.get(QqTConstant.PARA_OAUTH_TOKEN),
accessTokenMap.get(QqTConstant.PARA_OAUTH_TOKEN_SECRET));
}
return null;
}
a. UserInfo类代码如下
package com.trinea.sns.entity;
import java.io.Serializable;
/**
* 验证后存储在数据库中的用户信息类
*
* @author Trinea 2012-3-13 上午01:08:30
*/
public class UserInfo implements Serializable {
private static final long serialVersionUID = -2402890084981532871L;
/** 用户id,可能对于某些网站类型为空 **/
private String userId;
/** access token **/
private String accessToken;
/** access secret **/
private String accessSecret;
/** 网站类型 **/
private String websiteType;
/** 用户是否已经被选中 **/
private boolean isSelected;
/**
* 得到用户id,可能对于某些网站类型为空
*
* @return the userId
*/
public String getUserId() {
return userId;
}
/**
* 设置用户id
*
* @param userId
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* 得到accessToken
*
* @return the accessToken
*/
public String getAccessToken() {
return accessToken;
}
/**
* 设置accessToken
*
* @param accessToken
*/
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
/**
* 得到accessSecret
*
* @return the accessSecret
*/
public String getAccessSecret() {
return accessSecret;
}
/**
* 设置accessSecret
*
* @param accessSecret
*/
public void setAccessSecret(String accessSecret) {
this.accessSecret = accessSecret;
}
/**
* 得到网站类型
*
* @return the websiteType
*/
public String getWebsiteType() {
return websiteType;
}
/**
* 设置网站类型
*
* @param websiteType
*/
public void setWebsiteType(String websiteType) {
this.websiteType = websiteType;
}
/**
* 设置用户是否已经被选中
*
* @param isSelected
*/
public void setSelected(boolean isSelected) {
this.isSelected = isSelected;
}
/**
* 得到用户是否已经被选中
*
* @return the isSelected
*/
public boolean isSelected() {
return isSelected;
}
}
b. createUserInfo代码如下
public static UserInfo createUserInfo(String websiteType, String... userInfo) {
if (ArrayUtils.isEmpty(userInfo)) {
return null;
}
UserInfo user = new UserInfo();
user.setUserId((userInfo.length > 0 && userInfo[0] != null) ? userInfo[0] : websiteType);
user.setAccessToken(userInfo.length > 1 ? userInfo[1] : null);
user.setAccessSecret((userInfo.length > 2 && userInfo[2] != null) ? userInfo[2] : websiteType);
user.setWebsiteType(websiteType);
return user;
}
到此大功告成,如果想使用腾讯微博android sdk,请见http://trinea.iteye.com/blog/1299505
4、其他
腾讯微博认证异常
向https://open.t.qq.com/cgi-bin/request_token获取未授权的access token出现如下异常
java.lang.Exception: javax.net.ssl.SSLHandshakeException: org.bouncycastle.jce.exception.ExtCertPathValidatorException: Could not validate certificate signature.
原因应该是以上请求的ssl证书已经不可用,将https改为http即可,如http://open.t.qq.com/cgi-bin/request_token
近日在做一个学习法语的小应用,被MP3AB段复读的功能困扰了很久,最后终于在网上找到一个解决方法,就是使用CountDownTimer让MediaPlayer只播放MP3的某个区段,轻松解决了AB段复读的功能。详细代码如下:
public void play(final String filePath) throws Exception {
try {
if (mMediaPlayer == null) {
// 创建MediaPlayer对象并设置Listener
mMediaPlayer = new MediaPlayer();
} else {
// 复用MediaPlayer对象
mMediaPlayer.reset();
}
mMediaPlayer.setDataSource(filePath);
mMediaPlayer.prepare();
mMediaPlayer.setVolume(10.f, 1.0f);
// 复读第10秒到第20秒这个区间的音频
int startPos = 10000;
int endPos = 20000;
// Try to play three times
repeatAToB(startPos, endPos, 3);
} catch (Exception e) {
e.printStackTrace();
}
}
private void repeatAToB(final int startPos, final int endPos, final int repeatTimes)throws Exception {
mMediaPlayer.seekTo(startPos);
mMediaPlayer.start();
CountDownTimer cntr_aCounter = new CountDownTimer(/* millisInFuture= */endPos - startPos, /* countDownInterval= */1000) {
public void onTick(long millisUntilFinished) {
// DO SOMETHING
}
public void onFinish() {
// Code fire after finish
if((repeatTimes - 1) > 0){
try {
repeatAToB(startPos, endPos, repeatTimes - 1);
} catch (Exception e) {
e.printStackTrace();
}
}else{
mMediaPlayer.stop();
}
}
};
cntr_aCounter.start();
}
如果大家有更好的方案,欢迎拍砖!!
Activity、Service和Broadcast Receiver这些核心组件之间通过消息激活,这个消息就是Intent。
Intent消息可用于当前运行时同应用内部的组件之间或者不同应用的组件之间通信。Intent自身,即一个Intent对象,包含说明一个执行操作的抽象数据结构,传递给执行操作的组件,或者,常见于broadcast的情况,该数据结构用于描述正在执行或者已经发生的事情。
针对组件类型不同,发送Intent有不同的机制:
- 针对Activity,Context.startActivity()方法传递Intent,启动一个新的Activity,或者Activity.startActivityForResult()方法启动新的Activity做完事情后返回到本Activity来;
- 针对Service,Context.startService()方法,用于创建一个Service或者传递给已经运行Service一个指令,于此类似,Context.bindService()建立当前组件和Service之间的连接,可选的,如果该Service未运行,可以创建新的实例;
- 针对Broadcast Receiver,可通过:Context.sendBroadcast()、Context.sendOrderedBroadcast()或者Context.sendStickyBroadcast()方法发送Intent给所有感兴趣的broadcast receiver。
在以上各种情况下,Android系统找到适合响应该Intent的activity、service或者broadcast service集合。不会出现重叠现象,即,broadcast intent只会发送给broadcast receiver,而不会发送给Activity或者Service。
Intent对象Intent对象可绑定一组信息:
- 接收intent的组件需要的信息,比如需要调用系统照相机Activity,要告知它照片存放的路径;
- Android系统需要的信息,比如那个种类的组件可以处理这个Intent,在比如,告知如何启动Activity(比如要求它在哪个task中)。
Intent对象主要包含以下内容:
-
componnet name,组件名称,可处理这个Intent的组件名称。组件名称是可选的,如果填写,Intent对象会发送给指定组件名称的组件,否则,也可以通过其他Intent信息定位到适合的组件。组件名称是个ComponentName类型的对象,该对象又包括:
- 目标组件完整的类名,比如:com.example.project.app.FreneticActivity;
- 在manifest文件中设置的包名,组件的包名和manifest中定义的包名可以不匹配。
- action,命名动作的字符串,用于执行动作,或者,在广播的情况下,表示发生的或者要报告的动作。Intent类定义了一组action常量,见API的Intent.ACTION_*。这些是Intent类预制的一些通用action,还有一些action定义在android其他API中。用户可以定义自己的action字符串常量,用于激活自己的应用组件。这需要把应用的包名作为该字串的前缀,比如:com.example.project.SHOW_COLOR。action名称很像java调用的方法名,下面提到的data和extra类似参数和返回值。
- data,起到表示数据和数据MIME类型的作用。不同的action是和不同的data类型配套的。比如,action是ACTION_EDIT,那么data要包含要编辑的文档URI。如果action是ACTION_CALL,data可能是tel:前缀后面跟电话号码,在比如action是ACTION_VIEW,data是http:开头的URI,则应该是显示或者下载该uri的内容。在匹配intent到能处理该组件的过程中,data(MIME类型)类型是很重要的。比如,一个组件是可以显示图片数据的而不能播放声音文件。很多情况下,data类型可在URI中找到,比如content:开头的URI,表明数据在设备上,而且有content provider控制。但是有些类型只能显式的设置。setData()方法只能设置data的uri,setType()可设置MIME类型,setDataAndType()即可设置URI也可设置MIME类型。
- category,包含处理intent组件种类的额外信息。对一个Intent可以设置任意多个category描述,和cation类似,Intent类预制了一些category常量,Intent.CATEGORY_*。
- extras,可以看作一个Map,通过键值对,可为处理Intent组件提供一些附加的信息。可通过put..()和get..()存取信息。也可以获取Bundle对象,然后通过putExtras()和getExtras()方法存取。
- flug,用于多种情况,在intent增加flug,比如可以指示Android如何启动一个activity,比如是否属于或者不属于当前task,以及,处理完毕后activity的归属。这些flag都定义在Intent类的常量中。
intent的投递,有两种方式:
- 显式的设定目标组件的component名称。不过有时开发者不知道其他应用的component名称。显式方式常用于自己应用内部的消息传递,比如应用中一个activity启动一个相关的service或者启动一个姊妹activity;
- 隐式intent,component名称为空的情况。这种方式往往用于激活其他应用中的组件。
android投递一个显式的intent,只需找到对应名称的组件即可。
隐式的intent需要用到不同的策略。android需要找到处理这个intent的最合适组件(集合)。要通过intent filter,比较intent对象和组件关联结构。filter根据组件的能力决定他们能处理哪些intent。android系统打开合适的组件处理相应的隐式intent。如果组件不包含任何intent filter,那只能接收显式的intent。带filter的组件既可接收隐式intent也可接收显式的。
Intent有三个方面可用于intent filter:
- action
- data,包括URI部分和数据类型部分
- category
extra和flag在这方面不起作用。
Intent filter为了能支持隐式intent,activity、service和broadcast receiver会包含1到多个intent filter。每个intent filter描述组件的可接收一组intent的能力。在intent filter中,说明了可接受的类型,以及不想要的intent。隐式的intent要想投递到一个组件,只需通过组件的一个filter即可。
组件把filter分成多个,是为了针对具体不同的任务。在sample中的Note pad示例中,NoteEditor activity有两个filter,一个用于启动并打开指定的note,另一个是为了打开新的空的note。
一个intent filter是一个IntentFilter类的实例。但是,android系统必须在组件未启动的情况下就知道它的能力,因此intent filter一般不会在java代码中设置,而是在应用的manifest文件中作为<intent-filter>元素的方式声明。一个例外是,为broadcast receiver注册动态的filter,可以调用Context.registerReceiver()方法,通过直接实例化IntentFilter对象创建。
filter有三个平等的部分:action、data和category。隐式intent将测试这三个部分。一个intent要想投递到一个组件,那么这三个测试都要通过才行。当然如果组件有多个intent filter,可能一个intent没有通过,但是通过了另外的一个,这样也可以把intent投递到组件。
action测试在intent filter中可以包含多个action,比如:
<intent-filter . . . >
<action android:name="com.example.project.SHOW_CURRENT" />
<action android:name="com.example.project.SHOW_RECENT" />
<action android:name="com.example.project.SHOW_PENDING" />
. . .
</intent-filter>
要想通过测试,intent中的action名称要匹配其中之一。
如果intent filter中不包含action列表,而intent指定action,那么intent没有匹配的action,不通过;intent未指定action,而intent filter指定,会自动通过测试。
category测试在intent filter中可包含category列表:
<intent-filter . . . >
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
. . .
</intent-filter>
intent想通过测试,必须匹配一个intent filter中的category。
原理上讲,intent如果没有category设置,那么总是可以通过测试。这基本上是正确的,但是有一个例外。Android在为所有隐式intent执行startActivity()方法的时候,会认为它们至少包含了一个android.intent.category.DEFAULT。因此,如果activity想收到隐式intent,必须加入这个category。
date测试data元素在intent filter元素中,可以重复多次(action和category不允许重复的),也可以根本没有。比如:
<intent-filter . . . >
<data android:mimeType="video/mpeg" android:scheme="http" . . . />
<data android:mimeType="audio/mpeg" android:scheme="http" . . . />
. . .
</intent-filter>
在data元素中指定uri和数据类型(MIME类型)。uri是被分开表示的:
scheme://host:port/path
其中host和port是关联的,如果host没有设置,port也会忽略。
所有这些属性都是可选的,但是不是独立的。比如,如果要设置path,那么也必须设置schema、host和port。
在比较intent中的uri和intent filter中指定的uri时,只会比较intent filter中提及的URL部分。比如,intent filter中只提及了schema,那么所有url包含这个schema的都匹配。在filter的path部分可以使用通配符做到灵活的匹配。
mimeType属性,比uri方式更常用。intent和intent filter都可以使用mime通配符的方式,比如,text/*。
如果既有mimeType,又有uri的情况,比较规则如下:
- 如果intent和intent filter都没有设置任何uri和mimetype,通过;
- intent包含uri但是没有data type的情况,intent filter的uri部分与之匹配,而且也没有data type部分,可以通过,比如mailto:和tel:
- intent对象包含数据类型但是没有uri部分,那么仅当intent filter也只有数据类型,而没有uri部分的时候能通过;
- intent对象包括uri和数据类型(或者数据类型在uri中),分两部分测试,intent对象的数据类型要匹配intent filter,intent对象的uri,或者匹配intent filter中的uri,或者intent filter中没有uri部分(仅当intent对象的uri是content:或者file:的时候)
转自:http://marshal.easymorse.com/archives/2972