Android Intent 教程
原文:Android: Intents Tutorial
作者:Darryl Bayliss
译者:kmyhy
人不会漫无目的地瞎逛,他们所做的大部分事情——比方看电视、购物、编写下一个杀手级 app —— 都带有特定的目的或者意图,即 intent。
Android 也是相同的。在一个 app 干某件事情之前。它须要知道这件事情的目的或 intent。才干正确地完毕整件事情。
这说明人和 Android 并无不同。
在本文,你将利用 Intent 去创建一个模因软件(一种用于恶搞的图片制作软件,在国外非常流行,能够利用这款软件来简单的PS之后来黑别人或自娱自乐)。通过这个 demo。你能够学到:
- Intent 是什么,以及它在 Android 中广泛用途。
- 怎样用 Intent 创建和检索其它 app 中的内容并使用到你的 app 中。
- 怎样接收和响应一个别的 app 发来的 Intent。
假设你是一个新手,极度建议你阅读Android Tutorial for Beginners教程,以了解最主要的工具和概念。
准备好要模因的照片。
本教程将你的 Android 开发技能提升到 9000 +以上!
開始
从这里下载開始项目。
在開始项目中,你会找到 XML 布局和相关的 activity、一些模式化的代码、用于拉伸位图的助手类、以及后面会用到的一些资源如 Drawable 和 String 等。
假设你已经打开了 Android Studio,点击 File\Import Project 然后选择你所下载的開始项目的最上层文件夹。 否则,请打开 Android Studio 并从欢迎屏中选择 Open an existing Android Studio project,并选择開始项目的最上层文件夹。
花点时间浏览下開始项目。TakePictureActivity 包括了一个 ImageView,点击它会打开设备摄像头。当你点击 LETS MEMEIFY!,你会将 ImageView 中位图的文件路径传递给 EnterTextActivity,这个 activity 中真正的乐子開始了,你能够在这里输入一段恶搞的文本并将你的照片转换成下一个病毒式的模因!
创建第一个 Intent
执行 app。你会看到:
如今还没有什么功能。假设你依据提示去做并点击 ImageView。什么也不会发生!
你会加入一些代码让事情变得有趣些。
打开 TakePictureActivity.java 在类中加入常量定义:
private static final int TAKE_PHOTO_REQUEST_CODE = 1;
这个常量用于在返回时唯一标识你的 intent——后面你就知道了。
注意:本文假设你会解决类未引入警告,不须要专门说明须要引入某个类。
简单提示一下。假设你忘记引入某个类,你能够将鼠标放在提示“类未引入警告”的类上,按 alt+回车键来导入它。
在 onClick() 方法下加入例如以下代码,请自行加入必要的 import 语句:
private void takePictureWithCamera() {
// 创建一个 intent 用于从摄像头拍照
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File photoFile = createImageFile();
selectedPhotoPath = Uri.parse(photoFile.getAbsolutePath());
captureIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
startActivityForResult(captureIntent, TAKE_PHOTO_REQUEST_CODE);
}
这种方法的代码有点多,因此分成几步来加入代码。
第一行声明了一个 Intent 对象。说起来简单,但那到底是啥子意思呢?
一个 Intent 是一个任务或功能的抽象,它会被 app 在未来某个时刻执行。
简单说。它告诉你的 app 须要做的事情。最主要的 intent 由一下几个部分组成:
- Actions: 也就是 intent 须要做的事情。比方拨打某个电话号码,打开一个 URL。编辑某些数据。一个 action 是一个简单字符串常量描写叙述应该完毕的事情。
- Data: 即 intent 需用使用的资源。
在 Android 中它会用一个 URI(唯一资源标识符) 或者 Uri 对象来表示。
数据类型须要依据 action 而变。
比如,在一个打电话的 intent 中。你不能将电话号码搞成一张图片吧?
将 action 和 data 组合在一起,Android 就能够知道这个 intent 是干什么的以及用什么来干的问题。
就是这样简单!
回到 takePictureWithCamera() 方法,创建 intent 时我们使用 ACTION_IMAGE_CAPTURE 来作为 intent 的 action。你可能猜到这个 intent 是用来拍照的了,这恰巧是一个模因软件必须要用到的东西。
后面两行创建了一个暂时文件以便保存照片。開始项目中已经有创建暂时文件的代码,你能够看一眼这些代码。
理解它是怎么实现的。
了解 Extra
第4行代码将一个 extra 加入到新建的 intent 中。
你说的 extra 是啥子东东?
Extra 是一种传递给 intent 的包括额外信息的键值存储对象,以便让 intent 用于完毕特定的动作。
比方说。假设事先准备一些东西的话。人才干更好地完毕某个任务,Android 也是相同的。一个好的 intent 总是须要准备好必要的附属物(extra)。
一个 intent 的 extra 类型是已知的,而且依据 action 而定。这和提供给 action 的 data 的类型是一样的道理。
一个极好的样例是创建 action 为 ACTION_WEB_SEARCH 的 intent。
这样的 action 接收一个键为 QUERY 的 extra,用于表示你想搜索的查询字串。这个 key 一般是一个字符串常量,表示你不应该改变它。用这样的 action 和 extra 打开一个 intent 会显示一个 Google 的搜索页,并列出你的搜索结果。
再来看一下 captureIntent.putExtra() 这句,EXTRA_OUTPUT 表示你会将摄像头获取的照片保存到文件——这样。Uri 就用来指向早先创建的空文件。
调用 intent
如今一个功能正常的 intent 已经准备就绪。附上一个经典的 intent 的心智模型:
仅仅剩一件事情,就是在 takePictureWithCamera() 方法最后一行让 intent 去完毕它的使命。也就是这句:
startActivityForResult(captureIntent, TAKE_PHOTO_REQUEST_CODE);
这句让 Android 打开一个 activity 并执行 captureIntent 指定的 action:拍照并保存到文件。
一旦 activity 完毕了 action,你须要获得捕获的照片。
TAKE_PHOTO_REQUEST_CODE 是我们预先定义的常量。它会被用于在 intent 返回时标识这个 intent 。
在 onClick() 的 switch case 语句的 R.id.picture_imageview 分支中,在 break 语句之前加入:
takePictureWithCamera();
当你点击 image view 时。会调用 takePictureWithCamera 方法。
来检查一下你的工作成果!
执行 app。点击 ImageView 打开摄像头:
这时你能够拍张照,但你无法用它来做什么。我们将在下一节来解决这个。
注意:假设你在模拟器中执行。你须要编辑 AVD 中的相机设置。打开 Tools\Android\AVD Manager 菜单,点击你想设置的虚机右边的绿色铅笔图标。然后点击窗体左下角的 Show Advanced Settings。
在 Camera 一节,确保全部 enabled camera 下拉框中的选项都设置为 Emulated。
隐式 intent
假设你在真机上执行 app,而这个设备上安装了很多相机类 app。则你会发现有时候会出现这样的情况:
会询问你须要用哪个 app 来处理这类 intent。
当你创建一个 intent 时,你能够显示地指定或者不指定由哪种 app 来完毕 intent 的 action。ACTION_IMAGE_CAPTURE 是一个极好的隐式 intent 的样例。
隐式 intent 告诉 Android 由用户进行选择。假设用户已经有一个 app。他们就是喜欢用这个 app 来完毕某种任务,那么利用这个 app 的功能来为你服务又有什么错呢?至少。这避免了在你的 app 中反复发明轮子。
一个隐式 intent 告诉 Android 它须要某个 app 为它执行 action。Android 系统会询问每一个已安装的 app。谁能够处理这类 action,然后由这个 app 进行处理。
假设不止一个 app 能处理这类 intent。则提示由用户进行选择:
假设仅仅有一个 app 能够处理。intent 自己主动用它来执行 action。假设没有不论什么 app 能够处理。Android 什么也不会返回,给你一个空。并导致 app 崩溃!
要避免这个问题,我们能够检查返回结果。以确保在打开 intent 之前,至少有一个 app 能处理该 action。
或者在 AndroidManifest.xml 中声明要安装这个 app 必须在设备上有一个摄像头。
这个 demo 中使用了第二个方法。
你用一个隐式 intent 来进行拍照,但你没有在 app 中获取照片。而没有照片。你的模因 app 就无法继续下去。
在 TakePictureActivity 的 takePictureWithCamera() 后加入方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TAKE_PHOTO_REQUEST_CODE && resultCode == RESULT_OK) {
setImageViewWithImage();
}
}
在 takePictureWithCamera() 方法中用 startActivityForResult() 方法打开的activity 被关闭并返回 app 时会执行这种方法。
上面的 if 语句推断返回的 requestCode 是否和你传入的 TAKE_PHOTO_REQUEST_CODE 匹配。以确保返回的 intent 是我们所希望的 intent。同一时候还要推断 resultCode 是等于一个 Android 常量 RESULT_OK。这个常量表示操作成功的意思。
假设一切 OK,说明我们的照片已经准备就绪,因此调用 setImageViewWithImage()。
这种方法如今定义。
首先,在 TakePictureActivity 顶部。加入一个 Boolean 变量:
private Boolean pictureTaken = false;
这个变量记录是否拍过照,假设你连续拍了几张照片的话,这个变量就实用了。非常快我们就会用到它。
然后。在 onActivityResult()后面加入:
private void setImageViewWithImage() {
Bitmap pictureBitmap = BitmapResizer.ShrinkBitmap(selectedPhotoPath.toString(),
takePictureImageView.getWidth(),
takePictureImageView.getHeight());
takePictureImageView.setImageBitmap(pictureBitmap);
lookingGoodTextView.setVisibility(View.VISIBLE);
pictureTaken = true;
}
BitmapResizer 是開始项目中附带的一个助手类,确保你从相机中获得的照片压缩到适合于你的设备屏幕的尺寸。尽管设备也会拉伸照片,但压缩照片更能节省内存。
执行 app,选择你喜欢的相机 app——假设询问你的时候——然后拍照。
这次,照片会被压缩并显示在 ImageView 中:
你还会在下方 TextView 中,看到一句话,赞美了你的拍摄技巧!
有教养是一件非常好的事情!
:]
显式 Intents
接下来进入这个模因 app 的第二阶段,但首先得让你的照片传给还有一个 activity,由于你操心这里的屏幕空间有点紧张。
仍然是 TakePictureActivity, 在其它常量后面加入:
private static final String IMAGE_URI_KEY = "IMAGE_URI";
private static final String BITMAP_WIDTH = "BITMAP_WIDTH";
private static final String BITMAP_HEIGHT = "BITMAP_HEIGHT";
这些键会用于你要传给下一个 activity 的 extra 中。
然后。在 TakePictureActivity 加入下列方法, 请自行导入相关类:
private void moveToNextScreen() {
if (pictureTaken) {
Intent nextScreenIntent = new Intent(this, EnterTextActivity.class);
nextScreenIntent.putExtra(IMAGE_URI_KEY, selectedPhotoPath);
nextScreenIntent.putExtra(BITMAP_WIDTH, takePictureImageView.getWidth());
nextScreenIntent.putExtra(BITMAP_HEIGHT, takePictureImageView.getHeight());
startActivity(nextScreenIntent);
} else {
Toast.makeText(this, R.string.select_a_picture, Toast.LENGTH_SHORT).show();
}
}
这里推断了 pictureTaken 是否为 true。这表明你的 ImageView 已经从相机获得了一张照片。
假设没有,你的 activity 会显示一个 Toast 信息。让你去拍张照片。假设为 true,则创建一个 intent,使用先前定义的常量 key 设置它的 extra。
接着,在 onClick() 方法中的 R.id.enter_text_button 分支的 break 之前调用这种方法:
moveToNextScreen();
执行 app,点击 LETS MEMEIFY! 。假设你不拍照的话,你会看到一个 Toast 显示:
假设已经拍过照,moveToNextScreen() 方法会走到创建 intent 并进入输入文本的 activity 这一步。同一时候会附加 extra 到这个 intent,比方照片的 Uri 和宽高。这将在下一个 activity 中用到。
你已经创建了你的第一个显式 intent。
和隐式 intent 比較,显式 intent 要稳妥得多,由于它描写叙述了一个组件,这个组件会用来创建和开启 Intent。
它可能是来自于你的 app 的还有一个 activity。也可能是 app 中的某个服务。比方在后台下载一个文件。
这个 intent 在构造时会指定一个上下文(比如,这里的 this)以及 intent 想打开的 class(EnterTextActivity.class)。由于你已经显式地说明 intent 怎样从 A 找到 B,Android 进行简单的调用就能够了。
用户无法控制 intent 怎样完毕:
执行 app。
拍照,但这次点击 LETS MEMEIFY!,你的显式 intent 会将 action 交给下个 activity :
在開始项目中已经在 Manifest 中声明过这个 activity 了。你不必做这个步骤。
模因的第二阶段
看起来这个 intent 真棒。但你的 extra 传递到哪了?它们有没有在最后一块内存处拐错弯了?我们该把它们找出来让它们干活了!
在 EnterTextActivity 顶部加入下列常量:
private static final String IMAGE_URI_KEY = "IMAGE_URI";
private static final String BITMAP_WIDTH = "BITMAP_WIDTH";
private static final String BITMAP_HEIGHT = "BITMAP_HEIGHT";
我们简单地将前一个 activity 中的常量拷贝到这里。
然后。在 onCreate() 方法中的最后一行后面加入:
pictureUri = getIntent().getParcelableExtra(IMAGE_URI_KEY);
int bitmapWidth = getIntent().getIntExtra(BITMAP_WIDTH, 100);
int bitmapHeight = getIntent().getIntExtra(BITMAP_HEIGHT, 100);
Bitmap selectedImageBitmap = BitmapResizer.ShrinkBitmap(pictureUri.toString(), bitmapWidth, bitmapHeight);
selectedPicture.setImageBitmap(selectedImageBitmap);
在创建 activity 时,将上一个 activity 传入的 Uri 赋给 pictureUri。要获得当前 intent 请使用 getIntent() 方法。
获得 intent 后,就能够訪问 extra 中存储的值了。
由于变量和对象以不同的形式存储,要从 intent 中訪问它们须要用不同的方法。
比如要訪问 Uri 对象。须要使用 getParcelableExtra()。其它 extra 方法用于訪问其它类型的变量比方字符串和原始数据类型。
原始的 getExtra() 方法也能够让你指定一个默认值。
假设要訪问的值没有提供,或者 key 缺失,则使用默认值。
获取到所需的 extra 之后。就能够用 Uri 和 BITMAP_WIDTH、BITMAP_HEIGHT 指定的大小创建位图。最后设置 ImageView 的 image 以显示照片
除了 ImageView。屏幕上还有 2 个 EditText view,用户能够输入他们的恶搞文字。
開始项目已经为你完毕了这些工作,将恶搞的文字 PS 到照片上。
你须要做的仅仅是改动 onClick()。在 switch case 语句的 R.id.write_text_to_image_button 分支加入:
createMeme();
当当当当! 执行 App。
拍照。在第二个 activity 输入你的恶搞文字,点击 LETS MEMEIFY!:
你编写了你自己的模因 app! 但别高兴得太早——你还要为 app 进行一些“抛光工程”。
怎么保存?
假设能将你的恶搞图片保存成图片并分享给全世界就好了。它自己是不会进行病毒式传播的。:]
幸运的是開始项目已经完毕了大部分工作——你仅仅须要将导线连接起来。
在 saveImageToGallery() 方法中加入以下代码,就在 try 块后面,第二个 Toast.makeText() 之前:
// 创建一个 intent,请求扫描新建的文件。将照片 uri 传递给 intent。然后广播 intent。
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(imageFile));
sendBroadcast(mediaScanIntent);
这个 intent 使用了一个 ACTION_MEDIA_SCANNER_SCAN_FILE 的 action 去请求 Android 的媒体库,将 image 的 url 加入到媒体库。这样,app 就能够使用 image 的 Uri 訪问媒体库了。
ACTION_MEDIA_SCANNER_SCAN_FILE action 须要 intent 提供额外数据,数据须要是一个 Uri。这个 Uri 我们用已保存的位图文件的 File 对象来构造。
最后,通过 Android 系统来广播 intent,以便全部有兴趣的人——这里即 media scanner——去执行。由于 media scanner 没实用户界面,你无法启动一个 activity。所以我们仅仅能用广播 intent 的方式替代。
如今,在 onClick() 方法的 R.id.save_image_button 分支的 break 之前加入:
saveImageToGallery(viewBitmap);
当用户点击 SAVE IMAGE,上述代码会进行一些错误处理,假设一切正常,则打开 intent。
执行 app,拍照。输入恶搞文字,点击 LETS MEMEIFY!。当图片合成后。点击 SAVE IMAGE。
关闭 app,打开 Photos 程序。假设用模拟器測试,则打开 Gallery 程序。
你会看到一张带有恶搞文字的新照片:
你的恶搞照片能够不受 app 的限制并能够发送到社交媒体或用你想用的不论什么方式分享出去。
你的模因 app 做好了!
Intent 过滤
如今,你已经充分了解针对不同的任务使用不同的 intent 了。可是,这个关于诚实的 intent 故事里还有还有一个情节:当发送一个隐式 intent 时,你的 app 怎么知道哪个 intent 须要处理。
在 app/manifests 文件夹下。打开 AndroidManifest.xml 在第一个 activity 标签你会看到:
关键是 intent-filter 节点。一个 Intent Filter 同意你的 app 的组件能够响应隐式 intent。
当 Android 试图实现一个其它 app 发来的隐式 intent 时,这就好像一面旗帜。
一个 app 能够拥有多个 intent filter。它们像旗帜一样挥舞着,希望 Android 要找的人就是它。
这就像是 intent 和 app 们在进行一场网上约会!:]
为了证明自己就是和某个 intent 匹配的 app。intent 过滤器须要提供三样东西:
- Intent 的 Action: 这个 app 能够完毕的 action。比如相机 app 会为你的 app 执行 ACTION_IMAGE_CAPTURE 操作。
- Intent 的 Data: 这个 intent 能够接受的数据类型。取值范围可能是某个文件路径、端口、MIME 类型比方 image 和 video。你能够一个或多个属性,能够严格也能够宽泛地控制 app 从 intent 中获得的数据类型。
- Intent 的 Category: 能够接受的 intent 的类别。这是一种用于指定隐式 intent 中哪个 action 能被处理的附加方式。
让 Memeify(演示样例 app) 通过一个隐式 intent 将图片和其它 app 共享是一个不错的主意——这个过程会简单到令你惊奇。
在 Manifest 文件的第一个 intent 过滤器之后加入代码:
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="@string/image_mime_type" />
</intent-filter>
这个新的过滤器指明你的 app 会处理 action 为 SEND 的隐式 intent。同一时候将 intent 类别指定为 DEFAULT,表明在这样的情况下我们不须要对类别进行不论什么特殊的设置。同一时候仅仅须要提供 MIME 类型为 image 的数据。
打开 TakePictureActivity.java 在类中加入例如以下方法:
private void checkReceivedIntent() {
Intent imageRecievedIntent = getIntent();
String intentAction = imageRecievedIntent.getAction();
String intentType = imageRecievedIntent.getType();
if (Intent.ACTION_SEND.equals(intentAction) && intentType != null) {
if (intentType.startsWith(MIME_TYPE_IMAGE)) {
Uri contentUri = imageRecievedIntent.getParcelableExtra(Intent.EXTRA_STREAM);
selectedPhotoPath = getRealPathFromURI(contentUri);
setImageViewWithImage();
}
}
}
我们在这里获取了开启这个 activity 的 Intent 并找出它的 action 和 类型。将它们和 intent filter 中的定义进行比較,看它的 data 是不是 MIME_TYPE_IMAGE 类型。
假设是。继续获取图片的 Uri,通过開始项目中提供的助手方法获取位图,并让 ImageView 显示位图。
然后在 onCreate() 方法后加入:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
checkReceivedIntent();
}
当你的 app 的窗体将焦点切换到你的 activity 上时,对 intent 进行检查。这使得 activity 一显示到屏幕上就显示位图。
执行 app。马上回到 Home 屏,然后再回到 Photos 程序,或者 Gallery 程序——假设你正在使用模拟器的话。选择一张照片,点击分享按钮,从弹出菜单中选择 Memeify:
Memeify 正准备接受你的照片。点击 Memeify 看看会发生什么—— Memeify 会打开,并在 ImageView 中显示你选中的照片。
你的 app 如今毫不客气地接受了这个 intent!
结束
你能够从这里下载完毕的项目。
Intent 是构建 Android 的砖石之中的一个。没有它,Android 引以为傲的开放和连通根本无法做到。学会怎样利用好 intent,你会获得一个强大的盟友。
假设你想学习很多其它关于 intent 和 intent 过滤器的内容,请參考Google 的 Intents 文档。
有不论什么问题及建议,请在以下留言。