Android中Intent习惯用法

Android中的Intent是一个非常重要的类,如果对Intent不是特别了解,可以参见《详解Android中Intent的使用方法》。如果对Intent Filter不是特别了解,可以参见《详解Android中Intent对象与Intent Filter过滤匹配过程》。

本文着重讲一下Android中一些常见的Intent的习惯用法,比如如何通过Intent发送短信、发送邮件、启动摄像机拍照录视频、设置闹铃、打开WIFI设置界面等等。

限于篇幅,本文分为上下两篇,这是上篇。

发送短信

发送短信的时候,我们要使用的action是Intent.ACTION_SENDTO,并且要指定其URI是smsto:协议,这样能保证是短信应用接收并处理我们的intent对象,而不是其他应用接收,从而准确实现发送短信的目的。如果我们的action不是Intent.ACTION_SENDTO,而是Intent.ACTION_SEND,且没有指定smsto:协议的URI的话,那么Android在接收到intent对象之后不会直接启动短信应用,而是弹出了App Chooser,让我们选择要启动哪个应用,比如电子邮件、QQ等等,所以为了确保直接启动短信应用,我们应该使用Intent.ACTION_SENDTO并且指定smsto:协议的URI。

示例代码如下:

//使用ACTION_SENDTO而不是ACTION_SEND
Intent intent = new Intent(Intent.ACTION_SENDTO);
//指定URI使用smsto:协议,协议后面是接收短信的对象
Uri uri = Uri.parse("smsto:10086");
intent.setData(uri);
//设置消息体
intent.putExtra("sms_body", "手头有点紧,借点钱吧~~");

ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null){
 startActivity(intent);
}

在构造发送短信的URI时,前面是smsto:协议,后面跟的是接收短信的对方的手机号。如果在构建URI时,只写了smsto:,而没有写后面的手机的号的话,那么该intent也可以成功启动短信应用,不过这种情形下,在启动了短信应用之后,还需要我们自己再手动输入接收信息的手机号。我们通过key为sms_body的extra设置短信的内容。

需要注意的是,在执行了startActivity(intent)之后,虽然短信应用启动了,但是短信没有直接发出去,需要我们再点击一下发送消息才可以。

发送邮件

发送邮件的时候,我们要使用的action也是Intent.ACTION_SENDTO,并且要指定其URI是mailto:协议,这样能保证是邮件应用接收并处理我们的intent对象,而不是其他应用接收,从而准确实现发送邮件的目的。如果我们的action不是Intent.ACTION_SENDTO,而是Intent.ACTION_SEND,且没有指定mailto:协议的URI的话,那么Android在接收到intent对象之后不会直接邮件应用,而是弹出了App Chooser,让我们选择要启动哪个应用,比如短信、QQ等等,所以为了确保直接启动邮件应用,我们应该使用Intent.ACTION_SENDTO并且指定mailto:协议的URI。

示例代码如下:

//使用ACTION_SENDTO而不是ACTION_SEND
Intent intent = new Intent(Intent.ACTION_SENDTO);
//指定URI使用mailto:协议,确保只有邮件应用能接收到此intent对象
Uri uri = Uri.parse("mailto:");
intent.setData(uri);
String[] addresses = {"zhangsan@126.com", "lisi@126.com"};
String[] cc = {"boss@126.com"};
String[] bcc = {"girlfriend@126.com"};
String subject = "加班";
String content = "国庆正常上班~~";
//设置邮件的接收方
intent.putExtra(Intent.EXTRA_EMAIL, addresses);
//设置邮件的抄送方
intent.putExtra(Intent.EXTRA_CC, cc);
//设置邮件的密送方
intent.putExtra(Intent.EXTRA_BCC, bcc);
//设置邮件标题
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
//设置邮件内容
intent.putExtra(Intent.EXTRA_TEXT, content);
//设置邮件附件
//intent.putExtra(Intent.EXTRA_STREAM, Uri.parse(...));
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null){
 startActivity(intent);
}

启动邮件应用后的截图如下所示:

Android中Intent习惯用法

我们分别通过key为Intent.EXTRA_EMAIL、Intent.EXTRA_CC和Intent.EXTRA_BCC的extra依次设置邮件的接收方、抄送方、密送方,其值均为String数组。我们通过key为Intent.EXTRA_SUBJECT的extra设置邮件标题,通过key为Intent.EXTRA_TEXT的extra设置邮件内容。如果想发送附件,那么可以将附件封装成Uri的形式,然后通过key为Intent.EXTRA_STREAM的extra设置邮件附件。

需要注意的是,在执行了startActivity(intent)之后,虽然邮件应用启动打开了,但是邮件没有直接发出去,需要我们再点击一下右上角的发送按钮才能将邮件发出去。

打电话

要想通过Intent打电话,我们有两个可以使用的action:Intent.ACTION_DIAL和Intent.ACTION_CALL,二者有一定的区别。

如果使用Intent.ACTION_DIAL作为intent对象的action,那么当执行startActivity(intent)之后,会启动打电话应用,并且会自动输入指定的手机号,但是不会自动拨打,需要我们手动按下拨打按钮才能真正给对方打电话。

如果使用Intent.ACTION_CALL作为intent对象的action,那么当执行startActivity(intent)之后,会启动打电话应用,并且直接拨打我们指定的手机号,无需我们再手动按下拨打按钮。但是需要注意的是,该action需要权限android.permission.CALL_PHONE,如果在应用的AndroidManifest.xml文件中没有添加该权限,那么当指定到startActivity(intent)这句代码的时候,就会抛出异常,应用崩溃退出。

以下是示例代码:

//Intent.ACTION_DIAL只拨号,不打电话
//Intent intent = new Intent(Intent.ACTION_DIAL);
//Intent.ACTION_CALL直接拨打指定电话,需要android.permission.CALL_PHONE权限
Intent intent = new Intent(Intent.ACTION_CALL);
Uri uri = Uri.parse("tel:10086");
intent.setData(uri);
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null){
 startActivity(intent);
}

在该示例代码中,我们使用了Intent.ACTION_CALL作为intent对象的action,并且在AndroidManifest.xml中添加了如下权限:

复制代码 代码如下:
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>

我们使用tel:协议的URI,在协议后面的是要拨打的号码,将该Uri作为intent对象的data。

拍照

要想通过Intent启动摄像机进行拍照,我们需要设置intent对象的action值为MediaStore.ACTION_IMAGE_CAPTURE的action。然后我们用key为MediaStore.EXTRA_OUTPUT的extra设置图片的输出路径,最后调用startActivityForResult()方法以启动摄像机应用,并重写我们的onActivityResult()以便在该方法中得知拍照完成。

示例代码如下:

//表示用于拍照的requestCode
 private final int REQUEST_CODE_IMAGE_CAPTURE = 1;
 //我们存储照片的输出路径,以便后续使用
 private Uri imageOutputUri = null;

 //拍照
 private void captureImage(){
 PackageManager pm = getPackageManager();

 //先判断本机是否在硬件上有摄像能力
 if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)){
 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 ComponentName componentName = intent.resolveActivity(pm);
 //判断手机上有无摄像机应用
 if(componentName != null){
 //创建图片文件,以便于通过Uri.fromFile()生成对应的Uri
 File imageFile = createImageFile();
 if(imageFile != null){
  //根据imageFile生成对应的Uri
  imageOutputUri = Uri.fromFile(imageFile);
  //利用该Uri作为拍照完成后照片的存储路径,注意,一旦设置了存储路径,我们就不能获取缩略图了
  intent.putExtra(MediaStore.EXTRA_OUTPUT, imageOutputUri);
  //调用startActivityForResult()方法,以便在onActivityResult()方法中进行相应处理
  startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE);
 }else{
  Toast.makeText(this, "无法创建图像文件!", Toast.LENGTH_LONG).show();
 }
 }else{
 Toast.makeText(this, "未在本机找到Camera应用,无法拍照!", Toast.LENGTH_LONG).show();
 }
 }else{
 Toast.makeText(this, "本机没有摄像头,无法拍照!", Toast.LENGTH_LONG).show();
 }
 }

 //创建图片文件,以便于通过Uri.fromFile()生成对应的Uri
 private File createImageFile(){
 File image = null;

 //用时间戳拼接文件名称,防止文件重名
 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
 String imageFileName = "JPEG_" + timeStamp + "_";
 File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);

 try{
 image = File.createTempFile(
  imageFileName, //前缀
  ".jpg", //后缀
  storageDir //文件夹
 );
 }catch (IOException e){
 image = null;
 e.printStackTrace();
 Log.e("DemoLog", e.getMessage());
 }

 return image;
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
 //首先判断是否正确完成
 if(resultCode == RESULT_OK){
 switch (requestCode){
 case REQUEST_CODE_IMAGE_CAPTURE:
  //此处,我们可以通过imageOutputUri获取到我们想要的图片
  String imagePath = imageOutputUri.toString();
  Log.i("DemoLog", "照片路径是: " + imagePath);
  Toast.makeText(this, "照片路径是: " + imagePath, Toast.LENGTH_LONG).show();

  //以下代码尝试获取缩略图
  //如果设置MediaStore.EXTRA_OUTPUT作为extra的时候,那么此处的intent为null,需要判断
  if(intent != null){
  Bitmap thumbnail = intent.getParcelableExtra("data");
  //有的手机并不会给拍照的图片生成缩略图,所以此处也要判断
  if(thumbnail != null){
  Log.i("DemoLog", "得到缩略图");
  }
  }
 default:
  break;
 }
 }
 }

我们分析一下上面的代码片段:

不是所有的Android设备都能拍照的,所以首先我们调用了PackageManager的hasSystemFeature(PackageManager.FEATURE_CAMERA)方法,判断当前设备在硬件层级是否具有拍照的能力。

然后我们创建了一个action为MediaStore.ACTION_IMAGE_CAPTURE的intent对象。

然后我们通过调用intent.resolveActivity(pm)方法判断当前设备有无摄像机应用以便我们启动。如果没有摄像机应用但是我们却把intent对象传递给startActivity()或startActivityForResult()的话,就会抛出异常,应用崩溃退出。

我们自己写了一个createImageFile方法,通过该方法我们在自己的应用所对应的外设存储卡上创建了一个图片文件。需要注意的是,此步骤需要WRITE_EXTERNAL_STORAGE权限,在AndroidManifest.xml中注册如下:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
 android:maxSdkVersion="18"></uses-permission>

我们利用上面生成的图片文件生成了对应的Uri,将其存储在Activity中类型为Uri的字段imageOutputUri中,之后我们执行了intent.putExtra(MediaStore.EXTRA_OUTPUT, imageOutputUri),利用该Uri作为拍照完成后照片的存储路径。
此处需要特别注意的是,一旦设置了存储路径,我们就不能在onActivityResult()中获取缩略图了。

最后我们需要调用方法startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE)以启动摄像机应用进行拍照,其中REQUEST_CODE_IMAGE_CAPTURE是我们自定义指定的用于拍照的requestCode。

我们覆写了onActivityResult方法,拍照完成后触发该方法的执行。首先我们要判断resultCode是否与RESULT_OK相等,只有相等才表明拍照成功,然后我们判断如果requestCode是否等于REQUEST_CODE_IMAGE_CAPTURE,若相等表明是拍照返回的结果。那么此时,我们就可以通过我们之前存储的imageOutputUri获取刚刚拍完的照片了,其URI字符串如:
file:///storage/sdcard0/Android/data/com.ispring.commonintents/files/Pictures/JPEG_20150919_112704_533002075.jpg
需要注意的是,如果我们在第5步之中设置MediaStore.EXTRA_OUTPUT作为照片输出路径的话,那么在onActivityResult中无法获取从摄像机应用换回的Intent,即为null,这样也就无法获取缩略图。反之,如果在第5步没有设置MediaStore.EXTRA_OUTPUT作为照片输出路径的话,intent不为空,可以尝试执行Bitmap thumbnail = intent.getParcelableExtra("data")获取缩略图,如果thumbnail不为空,表示能成功获取缩略图。但是有的手机并不会给拍照的图片生成缩略图,所以此处的thumbnail也有可能是null,所以在使用之前要先判断。

摄像

通过Intent启动摄像机进行摄像的步骤与上面刚提到的通过Intent启动摄像机进行拍照的步骤非常相似,稍有区别。要启动Camera进行摄像,我们需要给intent设置值为MediaStore.ACTION_VIDEO_CAPTURE的action,然后我们用key为MediaStore.EXTRA_OUTPUT的extra设置图片的输出路径,最后调用startActivityForResult()方法以启动摄像机应用,并重写我们的onActivityResult()以便在该方法中得知摄像完成。

以下是示例代码:

//表示用于录视频的requestCode
 private final int REQUEST_CODE_VIDEO_CAPTURE = 2;

 //我们存储视频的输出路径,以便后续使用
 private Uri videoOutputUri = null;

 //摄像
 private void captureVideo(){
 PackageManager pm = getPackageManager();

 //先判断本机是否在硬件上有摄像能力
 if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)){
 //将intent的action设置为MediaStore.ACTION_VIDEO_CAPTURE
 Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
 ComponentName componentName = intent.resolveActivity(pm);
 //判断手机上有无摄像机应用
 if(componentName != null){
 //创建视频文件,以便于通过Uri.fromFile()生成对应的Uri
 File videoFile = createVideoFile();
 if(videoFile != null){
  //根据videoFile生成对应的Uri
  videoOutputUri = Uri.fromFile(videoFile);
  //利用该Uri作为摄像完成后视频的存储路径
  intent.putExtra(MediaStore.EXTRA_OUTPUT, videoOutputUri);
  //调用startActivityForResult()方法,以便在onActivityResult()方法中进行相应处理
  startActivityForResult(intent, REQUEST_CODE_VIDEO_CAPTURE);
 }else{
  Toast.makeText(this, "无法创建视频文件!", Toast.LENGTH_LONG).show();
 }
 }else{
 Toast.makeText(this, "未在本机找到Camera应用,无法摄像!", Toast.LENGTH_LONG).show();
 }
 }else{
 Toast.makeText(this, "本机没有摄像头,无法摄像!", Toast.LENGTH_LONG).show();
 }
 }

 //创建视频文件,以便于通过Uri.fromFile()生成对应的Uri
 private File createVideoFile(){
 File videoFile = null;

 //用时间戳拼接文件名称,防止文件重名
 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
 String imageFileName = "MP4" + timeStamp + "_";
 File storageDir = getExternalFilesDir(Environment.DIRECTORY_MOVIES);

 try{
 videoFile = File.createTempFile(
  imageFileName, //前缀
  ".mp4", //后缀
  storageDir //文件夹
 );
 }catch (IOException e){
 videoFile = null;
 e.printStackTrace();
 Log.e("DemoLog", e.getMessage());
 }

 return videoFile;
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
 //首先判断是否正确完成
 if(resultCode == RESULT_OK){
 switch (requestCode){
 case REQUEST_CODE_VIDEO_CAPTURE:
  //如果设置MediaStore.EXTRA_OUTPUT作为extra的时候,
  //在有的手机上,此处的intent为不为null,但是在有的手机上却为null,
  //所以不建议从intent.getData()中获取视频路径
  //我们应该自己记录videoOutputUri来得知视频路径,下面注释的代码不建议使用
  /*if(intent != null){
  Uri videoUri = intent.getData();
  if(videoUri != null){
  //路径格式如content://media/external/video/media/130025
  Log.i("DemoLog", "视频路径是: " + videoUri.toString());
  }
  }*/

  String videoPath = videoOutputUri.toString();
  //1.如果没有设置MediaStore.EXTRA_OUTPUT作为视频文件存储路径,那么路径格式如下所示:
  // 路径格式如content://media/external/video/media/130025
  //2.如果设置了MediaStore.EXTRA_OUTPUT作为视频文件存储路径,那么路径格式如下所示:
  // 路径格式如file:///storage/sdcard0/Android/data/com.ispring.commonintents/files/Movies/MP420150919_184132_533002075.mp4
  Log.i("DemoLog", "视频路径是: " + videoPath);
  Toast.makeText(this, "视频路径是: " + videoPath, Toast.LENGTH_LONG).show();
  break;
 default:
  break;
 }
 }
 }

可以看到上面启动Camera摄像的代码与拍照的代码几乎完全一样,具体解释参见对拍照代码的描述。在该示例代码中,我们通过MediaStore.EXTRA_OUTPUT设置了视频的存放路径,拍照的时候我们也通过它设置了照片的输出路径,但是二者稍有区别:

  • 1. 对于拍照,设置了MediaStore.EXTRA_OUTPUT之后,onActivityResult中的Intent参数是null,不能从Intent中得知照片的存储路径。
  • 2. 对于摄像,设置了MediaStore.EXTRA_OUTPUT之后,onActivityResult中的Intent参数在有的手机上是null,但是在有的手机上不是null,我的手机小米1s得到的intent对象就不是null,所以此处很奇怪。如果intent不是null,可以通过intent.getData()获取到视频文件的存储路径,但是由于intent是否为null不确定,所以尽量不要通过intent.getData()方法获取其路径,而应该自己在Activity中存储一个字段保存我们之前设置的文件路径,这样就没问题了。

以上就是关于Android中常见Intent习惯用法,希望对大家的学习有所帮助。

附源码: 《Android中Intent习惯用法》