Intent是Android中存放一个操作的抽象描述的数据结构,可用于在不同的组件以及不同的app间进行传递,是消息的载体,有显式和隐式之分(若两者都有则执行显式的),下图是隐式Intent的传递过程.
1. 启动其他App的Activity
1.1 新建一个隐式的Intent对象
隐式Intent不需要声明目标组件的class name,但是要声明要执行的action,这个action就是你想要执行的操作,比如view,edit,send等等. 如果需要给这些action绑定数据,比如要传地址信息,邮件内容等,就需要用到Uri类型的数据,绑定到intent的对象中,如下示例:
// 拨打电话
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
// 查看地图
// Map point based on address
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
// Or map point based on latitude/longitude
// Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
// 访问网页
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
当然Intent还有通过putExtra相关方法来传入数据,不过这个要接受和发送的双方都约定好才能使用,而Android系统默认的方式则是通过Uri并设置合适的MIME类型,这样系统才能找到合理的处理这个Intent的activities,如下示例:
// 发送一封带附件的email
Intent emailIntent = new Intent(Intent.ACTION_SEND);
// The intent does not have a URI, so declare the "text/plain" MIME type
emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"jon@example.com"}); // recipients
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject");
emailIntent.putExtra(Intent.EXTRA_TEXT, "Email message text");
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"));
// You can also attach multiple items by passing an ArrayList of Uris
// 建立一个日历event,注意这个API 14及以上才支持
Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
Calendar beginTime = Calendar.getInstance().set(2012, 0, 19, 7, 30);
Calendar endTime = Calendar.getInstance().set(2012, 0, 19, 10, 30);
calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
calendarIntent.putExtra(Events.TITLE, "Ninja class");
calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo");
- 注意: 准确的设置Intent的参数的意义在于系统能够准确的找到执行的组件,比如你想要查看图片,于是你使用了ACTION_VIEW的Intent,如果你再调用setType()设置类型为"image/*",那么其他的带ACTION_VIEW的Activity比如地图类的就不会被启动.
1.2. 验证是否有app能够接收你的Intent
虽然Android系统保证某些类型的Intent都至少有一个内置的app能够接收,但是由于Android系统的分散和自由,可能有的软件被删除等等原因,你想用Intent启动其他app的时候,一定要验证一下是否有app接收,如果你直接启动而没有app接收则app会crash(ActivityNotFoundException);
a. 可以使用PackageManager的queryIntentActivities()来验证,该方法需要一个Intent的参数,返回所有能够接收该intent的activity的信息,包在一个List里面,可以直接判断List的size是否大于0即可:
PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
b. 也可以通过这个Intent的resolveActivity()方法来判断
// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
1.3. 显示App候选框
调用startActivity()的时候:
- 如果只有一个activity能执行,则系统会直接启动那个activity;
- 如果有多个activity能执行,系统会显示一个dialog让用户来选择使用哪个activity来执行,并且带有一个"将此选择设置为默认"的选项,也就是如果你选择了"将此选择设置为默认"的选项后再点击相应的activity,那么这个intent以后都将直接由这个activity来执行,这种适用于打开网页这种需要保持使用某个app的activity来执行的情况.
但是还有一种情况,就是我就是要每次都要选择不同的activity来处理,比如分享,显然我不可能只分享到某个app,那要如何操作呢?这就需要用到APP Chooser,会每次都显示,让用户下选择.使用时只需调用Intent的createChooser()方法即可,示例代码如下:
Intent intent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);
// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
2. 从其他Activity中返回结果
启动其他App的Activity是双向的,你可以传给别的Activity数据,你也可以接收它的返回结果,想要接收到结果则需要用startActivityForResult()来启动Intent,而不是startActivity(),然后在onActivityResult()的回调中接收.
- 注意: 显式和隐式的Intent用startActivityForResult()都能启动,但是如果是在自己app内的交互,你应该要使用显式的Intent来确保能接收到期望的结果.
示例代码如下:
// 启动intent
static final int PICK_CONTACT_REQUEST = 1; // The request code
...
private void pickContact() {
Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
pickContactIntent.setType(Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
}
// 接收和处理返回结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == PICK_CONTACT_REQUEST) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
// The user picked a contact.
// The Intent's data Uri identifies which contact was selected.
// Do something with the contact here (bigger example below)
}
}
}
为了成功处理返回结果,你必须要知道返回的Intent的格式是什么,比如通讯录会返回带URI的结果,相机App会将Bitmap对象保存在"data"数据中返回,下面看下具体的通讯录返回的联系人数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request it is that we're responding to
if (requestCode == PICK_CONTACT_REQUEST) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
// Get the URI that points to the selected contact
Uri contactUri = data.getData();
// We only need the NUMBER column, because there will be only one row in the result
String[] projection = {Phone.NUMBER};
// Perform the query on the contact to get the NUMBER column
// We don't need a selection or sort order (there's only one result for the given URI)
// CAUTION: The query() method should be called from a separate thread to avoid blocking
// your app's UI thread. (For simplicity of the sample, this code doesn't do that.)
// Consider using CursorLoader to perform the query.
Cursor cursor = getContentResolver()
.query(contactUri, projection, null, null, null);
cursor.moveToFirst();
// Retrieve the phone number from the NUMBER column
int column = cursor.getColumnIndex(Phone.NUMBER);
String number = cursor.getString(column);
// Do something with the phone number...
}
}
}
注意不同版本的通讯录的权限请求,详细参考Security and Permissions
3. 允许其他的App来启动你的Activity
上面两点讲的是如何启动其他App一起如何处理返回结果,现在要讲的时如何让其他App来启动你的Activity,要实现这个功能,你的Activity需要做一些准备,比如你想支持分享操作,需要设置ACTION_SEND,具体实现在Manifest中添加<intent-filter>元素并将相应action加入.
- Tips: 系统对于Intent的操作是这样的:当你app安装的时候,系统会识别你在intent filter中的配置并将相应信息添加到系统内部的一个catalog,当一个app启动一个隐式的intent时,系统就会把intent中的信息拿出来去catalog中查找.
3.1 添加Intent Filter
为了让你Activity来处理合适的intent,你需要尽可能精细的设置好action和data的值. 系统的标准如下:
- Action: 字符串,也就是intent filter中的<action>元素,指定activity要执行的action,可以设置一个或多个,当然如果不设置就无法被其他App启动了.
- Data: 与intent绑定的数据的描述,也就是<data>元素,可以设置一个或多个,但是推荐的只设置 android:mimeType这个属性即可,比如"text/plain"或"image/jpeg".
- Category: 给Intent提供另外一种分类的方式,也就是<category>元素,可以设置一个或多个,默认都是"CATEGORY_DEFAULT"
示例代码如下:
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
如果有任何两对action和data互斥,则需要添加一个独立的intent filter将它们分开,比如你的Activity要处理text和image,Action为ACTION_SEND和ACTION_SENDTO,就必须定义两个intent filter来将它们分开,因为ACTION_SENDTO一定要用Uri数据来适配接收者地址(如果混在一起写,未携带地址的Intent也能通过ACTION_SENDTO,进而启动你的Activity,这样就不能很好的处理了),如下示例:
<activity android:name="ShareActivity">
<!-- filter for sending text; accepts SENDTO action with sms URI schemes -->
<intent-filter>
<action android:name="android.intent.action.SENDTO"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="sms" />
<data android:scheme="smsto" />
</intent-filter>
<!-- filter for sending text or images; accepts SEND action and text or image data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
- 注意: 为了接收隐式的intent,你的<intent-filter>中必须要设置CATEGORY_DEFAULT,不然匹配不了.
具体可以参考Receiving Simple Data from Other Apps.
3.2 在你的Activity中处理接收到的Intent
用getIntent()方法拿到Intent对象,你应该在onCreate()或onStart()中来处理,如:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get the intent that started this activity
Intent intent = getIntent();
Uri data = intent.getData();
// Figure out what to do based on the intent type
if (intent.getType().indexOf("image/") != -1) {
// Handle intents with image data ...
} else if (intent.getType().equals("text/plain")) {
// Handle intents with text ...
}
}
3.3 返回结果
要给启动你的Activity的Activity放回结果很简单,只需调用setResult()方法然后结束时调用finish()方法即可.如下:
// Create intent to deliver some kind of result data
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri"));
setResult(Activity.RESULT_OK, result);
finish();
返回结果中需要设置返回码,选RESULT_OK或RESULT_CANCELED(也可以自己给个任意int数值).
- 注意:默认的返回码是RESULT_CANCELED,如果你没设置返回码就返回,默认就是该值.
如果不想返回数据,或者只想返回一个int类型的数据,那么可以直接设置返回码:
setResult(RESULT_COLOR_RED);
finish();
- 注意: 没必要去检查你的Activity是被startActivity()启动还是startActivityForResult()启动,如果启动你的intent可能需要返回,你就调用setResult()来设置相应的返回结果,对方接收不了就会被系统ignore掉.-----------------------------------------------------说了这么多你是不是想特别特别特别特别特别特别特别特别想知道如何检查你的Activity是被哪个启动的???反正我是特别想就对了..................... 可以用getCallingActivity()的返回值为空与否来判断.
总结
这篇讲的Intent都是比较基础的,也比较全面,对于Android其他部分的理解还是比较重要的.