Android其中最重要的特性之一,就是一个应用可以基于“action”来切换到另一个应用。比如,你的应用想要查找地方,在地图上显示。但是不一定要创建一个activity来显示地图,可以使用Intent发起一个请求来查看地址,然后Android系统会启动一个可以显示地图的应用。
之前,会使用到显式的Intent来让一个activity跳转到另一个activity。但是,当想要跳转到一个独立的应用时,比如查看地图,这时候就一定要使用隐式Intent。
创建一个隐式Intent
隐式Intent不用像显式Intent那样传入一个目标activity的类名,只需要传入操作的响应,也就是action。这个action表明了你想要做什么。Intents通常会包含与action相关的数据,比如想要查看的地址,或者想要发送的邮件信息等。这取决于你想要做什么,数据可能是Uri类型,也可能是其他类型,或者根本就不需要数据。
如果数据是Uri类型的,下面一个简单的Intent()构造函数例子,可以用来定义action和数据。
比如说,怎么样创建一个Intent初始化一个电话拨号,用Uri类型来作为电话号码的类型。
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
查看地图:
Uri location = Uri.parse("geo:38.899533,-77.036476");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
打开网页:
Uri website = Uri.parse("http://xxxxx.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, website);
发送邮件:
Uri emailUri = Uri.parse("mailto:[email protected]");
intent emailIntent = new Intent(Intent.ACTION_SENDTO, emailUri);
想要传递其他不同类型的数据,可以调用不同的putExtra()方法,把数据添加进去隐式的Intent中。
默认的情况下,系统基于所包含的 Uri 数据确定Intent需要的相应 MIME 类型。如果Intent中不包含Uri,应该使用 setType()来指定与Intent相关的数据。设置MIME类型指定那一种类型的activity可以接收这个Intent。
发送一个邮件附件:
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"[email protected]"});
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"));
这个Intent没有Uri,所以声明MIME类型为 “text/plain”
注意:定义Intent时要尽可能具体,这是非常重要的。比如,如果你使用ACTION_VIEW inten想要展示一张图片,那你就应该指定MIME类型为 image/*. 这就防止了你的app被Intent触发之后意外地查看到其它的数据类型(比如地图应用)
检验是否有适合的应用来接收Intent
尽管Android系统保证每一个确切的Intent都会对应apps中发起的一个请求响应(比如电话、邮件或者地图应用等),但是在调用一个Intent之前还是应该包含验证这一步骤的。如果调用了一个Intent,但是设备中并没有一个可以处理这个Intent请求响应的应用,这时候app就会崩溃了。
检验过程并不复杂,创建一个PackageManager的对象,调用queryIntentActivities() 来获取activity的List容器,这个可以用来处理Intent。如果这个List不为空的话,那就说明至少有一个应用可以接这个Intent,调用Intent是安全的。
PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
使用Intent来启动一个活动
一旦已经创建了Intent,并且设置好了额外的信息,就可以调用startActivity()来发送给系统了。如果系统发现有多个应用都可以出来响应这个Intent,那么就会显示一个对话框出来,让你选择其中一个应用。如果你之前已经选择了默认的应用,那就会直接打开。
效果图,因为已经选择默认的浏览应用了,就直接打开
结合上面的检验步骤,代码如下:
Uri website = Uri.parse("http://www.baidu.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, website);
PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
if(isIntentSafe){
startActivity(webIntent);
}
用户可以勾选选择框来默认启动的应用,可能每一次用户都会选择用一个应用来打开网页或者拍照。但是呢,如果类似于分享这种,用户可能就会要选择几个不同的应用来分享了。以应该有一个选择对话框可以让用户选择。毕竟不能让用户只能勾选一个默认应用吧。
所以,需要使用 createChooser()来创建一个intent,然后再把它传进startActivity()中。这样,通过传入createChooser()方法返回的Intent,就会显示一个选择窗口,可以选择不同的应用。标题就是传入的title。
Intent intent = new Intent(Intent.ACTION_SEND);
...
// 最好在字符串资源中定义UI文本,这个文本显示的是"Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// 创建一个Intent来显示选择窗口,传入intetn和标题
Intent chooser = Intent.createChooser(intent, title);
//验证
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
接收返回结果
启动另一个应用并不是只有startActivity()这个方法,也可以使用startActivityForResult()来代替startActivity(),这样启动另一应用的同时,还可以获得返回的结果。比如,启动照相机应用,然后会接收拍到的照片作为结果。当然,在activity中,需要一个方法来接收这个返回结果。activity在 onActivityResult()方法中接收。
注意:当调用 startActivityForResult(),可以使用显式Intent,也可以使用隐式Intent。当启动自己的Activity来接收结果时,应使用显式Intent确保可收到预期的结果。
接下来看看一个启动照相机应用的例子。
先添加权限,请求系统硬件的权限,还有保存照片写入文件的权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera2.CaptureRequest"/>
简单的布局,一个Button启动,一个ImageView显示图片
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.choicepictest.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/take_photo"
android:text="Take Photo"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/picture"
android:layout_gravity="center_vertical"/>
</LinearLayout>
然后到MainActivity。首先,当然先初始化组件,在Oncreate()函数中调用startActivityForResult()方法启动照相机,这个方法需要传入两个参数,一个是Intent对象,一个是请求码,用来标识是哪一个发起的请求。
先定义两个请求码
public static final int TAKE_PHOTO = 1;
public static final int SHOW_PICTURE = 2;
onCreate方法中,先new一个File用来保存图片,使用隐式Intent发起请求android.media.action.IMAGE_CAPTURE
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
takePhoto = (Button) findViewById(R.id.take_photo);
picture = (ImageView) findViewById(R.id.picture);
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File outImage = new File(Environment.getExternalStorageDirectory(),"my_image.jpg");
try {
if(outImage.exists()){
outImage.delete();
}
outImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
imageUri = Uri.fromFile(outImage);
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
boolean IsIntentSafe = activities.size() > 0;
if(IsIntentSafe){
startActivityForResult(intent, TAKE_PHOTO);
}
}
接下来是另一方法onActivityResult,就是用来接收结果、处理结果的方法。
有三个参数
第一个是请求码,就是上面 startActivityForResult方法中传入的请求码。
第二个是结果码,RESULT_OK表示操作成功, RESULT_CANCELED表示用户退出或者因为某种原因。
第三个参数就是传送数据的Intent。
下面第一个处理是裁剪照片,使用startActivityForResult启动裁剪,Intent传入的是com.android.camera.action.CROP,请求码SHOW_PICTURE。操作成功后,第二个处理是显示照片
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case TAKE_PHOTO :
if(resultCode==RESULT_OK){
Intent intent = new Intent("com.android.camera.action.CROP");
if(data !=null){
intent.setDataAndType(data.getData(),"image/*");
} else {
intent.setDataAndType(imageUri,"image/*");
}
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,SHOW_PICTURE);
}
break;
case SHOW_PICTURE:
if(resultCode==RESULT_OK){
try {
Bitmap bitmap = BitmapFactory.decodeStream(
getContentResolver().openInputStream(imageUri)
);
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
添加Intent过滤器
前面讲的都是:从你的应用开始另一个应用的Activity。但如果你的应用可以执行对另一个应用有用的操作,应准备好响应来自其他应用的操作请求。 例如,如果构建一款可与用户的好友分享消息或照片的社交应用,最关注的是支持 ACTION_SEND,以便用户可以从另一应用发起 “共享”操作并且启动你的应用执行该操作。
要允许其他应用开始你的Activity,您需要 在相应元素的Manifest文件中添加一个 元素。
当应用安装在手机上时,系统会识别你的intent-filter并添加信息至所有已安装应用支持的Intent内部目录。当其他应用通过隐式Intent调用 startActivity() 或 startActivityForResult() 时,系统会找到可以响应该Intent的Activity。
为了确定哪一种Intent你的应用可以出来,每一个intent filter的添加应该尽可能地指定响应的类型和接收的数据类型。如果Activity具有满足以下 Intent 对象条件的intent filter,系统可能向Activity发送给定的 Intent:
Action
一个响应操作的字符串名称。例如ACTION_SEND 或者 ACTION_VIEW 。在intent filter中使用< action >来指定。指定的值一定是响应的完整字符串名称。
Data
跟intent相关的数据的描述。
在intent filter中使用< data >来指定。标签中,可以使用一个或多个属性,但是只能指定MIME类型,URI的前缀,URI的模式或者这些方法的组合和其他一些可接受的数据类型。如果你不需要声明关于这个数据的Uri(比如当你的activity处理其他的“extra”类型,而不是Uri),应该只指定这个android:mimeType属性来声明处理数据的类型。比如text/plain或者 image/jpeg。
1、android:scheme
用于指定数据的协议部分,如 http 部分。
2、 android:host
用于指定数据的主机名部分,如 www.baidu.com部分。
3、android:port
用于指定数据的端口部分,一般紧随在主机名之后。
4、 android:path
用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
5、android:mimeType
用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
Category
< category > 标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的 Intent中还可能带有的 category。只有< action >和< category >中的内容同时能够匹配上 Intent 中指定的 action 和 category 时,这个活动才能响应该 Intent。但是category大部分很少用。所有的隐式intent默认定义为CATEGORY_DEFAULT。
每个Intent中只能指定一个action,但却能指定多个category。为了能够接收到其他应用发送过来的隐式intent,必须要在intent filter中包含 CATEGORY_DEFAULT类别 。方法 startActivity() 和 startActivityForResult() 就像声明 了CATEGORY_DEFAULT 类别那样处理所有的Intent。
比如,这里定义了一个activity,可以出来 ACTION_SEND intent,当数据类型是text或者image。
<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>
在activity中处理接收的隐式Intent
为了决定在你的Activity执行哪种操作,先读取用于开始Activity的 Intent。
当你的Activity开始时,调用 getIntent()来获取一个启动你的Activity的 Intent。可以在Activity生命周期的任何时间执行此操作,但通常应该在 onCreate() 或 onStart()中执行。
例如:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取启动你的activity的Intent
Intent intent = getIntent();
Uri data = intent.getData();
// 找出这个intent的类型
if (intent.getType().indexOf("image/") != -1) {
// 使用image数据处理
} else if (intent.getType().equals("text/plain")) {
// 使用text来处理
}
}
返回结果
如果想要向调用你的应用的Activity返回一个结果,只需调用 setResult() 来指定结果码和结果的Intent。当操作完成且返回原来的Activity时,调用 finish() 关闭(和destroy)你的Activity。
例如:
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
setResult(Activity.RESULT_OK, result);
finish();
必须要指定一个结果码作为结果。通常是RESULT_OK 或者 RESULT_CANCELED,如果需要的话,还可以提供一个装有数据的Intent。结果码默认设置为RESULT_CANCELED。所以,如果用户在完成操作之前和返回你设置的结果之前就点击返回键,那么原来的activity就会接收到RESULT_CANCELED作为结果。
如果只是简单地返回一个整数表示其中一个选择的结果,可以设置结果码为任何大于0的值。如果想用结果码传递一个整数又不需要包含一个Intent,可以调用setResult()紧紧传入结果码。
例如:
setResult(RESULT_COLOR_RED);
finish();
这里不需要检查activity是被startActivity() 还是startActivityForResult()启动的。如果启动你的activity的inten可能期待返回一个结果,那么就简单地调用setResult()。如果原来的activity已经调用了startActivityForResult(),那么系统会传送你提供给setResult()的结果。否则,结果会被忽略掉。
到这里,结合之前照相机的例子想一想,也明白了大概的流程了。