一、使用通知Notification:
当某个应用程序希望向用户发出一些提示信息,而该程序又不在前台运行时,就可以借助通知来实现。
1.通知的基本用法:
通知可以在活动、在广播接收器或在服务里创建。
创建通知的步骤:
a.获取NotificationManager实例:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
b.使用Builder构造器创建一个Notification对象。由于Android每个版本API不稳定,使用support库中的兼容API(support-4v中提供NotificationCompat类),保证程序在所有Android系统版本上正常工作:
Notification notification = new NotificationCompat.Builder(context).bulid();
上面的代码仅仅创建了一个Notification对象,可在build()方法之前连缀任意多设置方法来创建丰富的Notification对象:
Notification notification = new NotificationCompat.Builder(context).setContentTitle("This is content title").setContentText("This is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.drawable.small_icon).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon)).build();
调用了五个设置方法:指定通知的标题内容(下拉时显示);指定通知的正文内容(下拉时显示);指定通知被创建的时间(单位毫秒,下拉时显示在通知上);设置通知的小图标(只能用纯alpha图层的图片,显示在系统状态栏上);设置通知的大图标(下拉时显示)。
c.调用通知管理器类的notify方法即可使通知显示出来:
manager.notify(1, notification);
notify方法有两个参数:第一个参数是id,保证为每个通知创建的id是不同的;第二个参数是通知对象。
2.具体范例:创建一个NotificationTest项目。
(1)修改主活动的界面文件:
<?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">
<Button
android:id="@+id/send_notice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send notice"
/>
</LinearLayout>
(2)修改主活动代码:
package com.example.notificationtest;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //重写超类方法
setContentView(R.layout.activity_main); //绑定布局
Button button = (Button) findViewById(R.id.send_notice); //获取按钮id
button.setOnClickListener(this);
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.send_notice:
String id ="channel_1";//channel的id
String description = "123";//channel的描述信息
int importance = NotificationManager.IMPORTANCE_LOW;//channel的重要性
NotificationChannel channel = new NotificationChannel(id, description, importance);//生成channel
//创建通知管理器
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);//添加channel
Notification notification = new Notification.Builder(MainActivity.this,id)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setWhen(System.currentTimeMillis())
.setContentTitle("这是通知的标题内容")
.setContentText("这是通知的内容")
.build();
manager.notify(1,notification);
break;
default:
break;
}
}
}
注:如果根据上述操作仍无法显示消息,则需检查一下手机对当前应用的通知设置。
(3)消息通常情况下是可以点进去的,但根据上述操作显示的通知点击它时,没有任何效果,如果想要实现通知的点击效果,则需要PendingIntent。
PendingIntent与Intent类似,相同点:都可以用于指名一个“意图”,都可以用于启动活动、服务以及广播等。不同点:Intent更倾向于立刻执行某个动作,而PendingIntent更倾向于在某个合适的时机去执行某个动作。(可以把PendingIntent理解为延迟执行的Intent)
PendingIntent提供了几个静态方法用于获取PendingIntent实例,根据需求选择getActivity()方法、getBroadcast()方法、getService()方法,这几个方法接收的参数是相同的:
a.Context
b.该参数一般用不到,通常传入0即可
c.Intent对象
d.确定PendingIntent的行为(FLAG_ONE_SHOT/FLAG_NO_CREATE/FLAG_CANCLE_CURRENT/FLAG_UPDATE_CURRENT四种值可选,通常传入0)
NotificationCompat.Builder还可再连缀一个SetContent()方法(参数正是PendingIntent对象)。
*行动:优化NotificationTest项目。新建另一个活动NotificationActivity,在其布局文件上添加一个TextView:
<?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">
<TextView
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInparent="true"
android:text="This is notification layout!"
android:textSize="24sp"
/>
</LinearLayout>
修改主活动的代码,将onClick方法的代码修改为:
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.send_notice:
String id ="channel_1";//channel的id
String description = "123";//channel的描述信息
int importance = NotificationManager.IMPORTANCE_LOW;//channel的重要性
NotificationChannel channel = new NotificationChannel(id, description, importance);//生成channel
Intent intent = new Intent(MainActivity.this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0,intent, 0); //消息触发后调用
//创建通知管理器
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);//添加channel
Notification notification = new Notification.Builder(MainActivity.this,id)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setWhen(System.currentTimeMillis())
.setContentIntent(pi)
.setContentTitle("这是通知的标题内容")
.setContentText("这是通知的内容")
.build();
manager.notify(1,notification);
break;
default:
break;
}
}
运行程序:点击通知消息,会进入NotificationActivity。
2.通知的进阶技巧:
//NotificationCompat.Builder还可再连缀下列方法
//发出通知时播放一段音频
.setSound(Uri.fromFile(new File("system/media/audio/ringtones/Luna.ogg")))
//发出通知时震动
.setVibrate(new long[] {0,1000,1000,1000})
//发出通知时闪光灯提示
.setLights(Color.GREEN,1000,1000)
//发出通知时使用默认的通知效果
.setDefaults(NotificationCompat.DEFAULT_ALL)
3.通知的高级功能:
(1)通知内容较长时,是无法完整显示的,多余的部分会用省略号代替,但如果需要显示完整的内容,setStyle()方法可以实现:
.setStyle(new Notification.BigTextStyle().bigText("这是通知的内容,在这里我想说明,我真的很想养只狗或者猫,我也想买一台拍立得!但是我没有充分的预算,这算不算白日做梦、痴心妄想呢?"))
上述操作是在setStyle()方法中创建了一个Notification.BigTextStyle对象,这个对象是用于封装长文字信息的,调用它的bigText()方法并将文字内容传入即可。
(2)在通知里显示一张大图片,在setStyle()方法中创建了一个Notification.BigPictureStyle对象,调用它的bigPicture()方法并将图片传入即可。
.setStyle(new Notification.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.pear)))
(3)setPriority()方法用于设置通知的重要程度。5个常量值可选:PRIORITY_DEFAULT(默认,不设置即为默认)、PRIORITY_MIN(最低,可能只会在特定场景显示,如下拉状态栏时)、PRIORITY_LOW(较低,可能会缩小或改变显示顺序在更重要通知之后)、PRIORITY_HIGH(较高,能能会放大或排在比较靠前位置)、PRIORITY_MAX(最高,必须让用户立即看到并做出响应)。
二、调用摄像头和相册:
1.调用摄像头拍照:
(1)新建一个CameraAlbumTest项目,修改activity_main.xml的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button //拍照按钮
android:id="@+id/take_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Photo" />
<ImageView //显示拍到的图片
android:id="@+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
(2)修改MainActivity的代码:
package com.example.cameraalbumtest;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MainActivity<GoogleApiClient> extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
private ImageView picture;
private Uri imageUri;
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button 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对象,用于存储摄像头拍摄的图片,图片命名为output_image.jpg
* 将它存放在手机SD卡的应用关联缓存目录下,关联缓存目录指SD卡中专门用于存放当前应用缓存数据的位置,getExternalCacheDir()方法可得到这个目录
* 具体路径是/sdcard/Android/data/<package name>/cache
* 从Android6.0开始,读写SD卡被列为危险权限,如果存放在SD卡其他目录需进行运行时权限处理,使用此目录则不用
*/
File outputImage = new File(getExternalCacheDir(), "output_image.jpg"); //创建一个File对象,用于存放摄像头拍下的图片,这里图片命名为output_image.jpg
try {
if (outputImage.exists()) { //如果这个文件存在,就删除这个文件
outputImage.delete();
}
outputImage.createNewFile(); //创建这个文件
} catch (IOException e) { //捕获异常
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {//如果运行设备的系统版本等于或高于Android7.0
imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.cameraalbumtest.fileprovider", outputImage);
//将File对象转换成一个封装过的Uri对象。三个参数:Context对象,任意唯一字符串,File对象
//Android7.0后,直接访问本地真实路径的Uri被认为不安全,会抛出异常,而FileProvider是一种特殊的内容提供器(使用和内容提供器类似的机制保护数据),可选择性地将封装过的Uri共享给外部,提高应用安全
} else {
//如果运行设备的系统版本低于Android7.0,此Uri对象标识着output_image.jpg图片的本地真实路径
imageUri = Uri.fromFile(outputImage);
}
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){ //动态申请权限
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},1); //向用户申请获取权限
}else{
takePhoto();
}
}
});
}
private void takePhoto(){
try{
//启动相机程序
//构建一个Intent对象,将这个Intent的action指定为android.media.action.IMAGE_CAPTURE
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//指定图片输出地址为得到的Uri对象
startActivityForResult(intent,TAKE_PHOTO);//启动相机
}catch(SecurityException e){
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) { //回调,不论权限是否申请成功,均会回到这个方法
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
takePhoto();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { //startActivityResult方法的回调
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
try {
//将照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
注:按照《Android第一行代码》(第2版)闪退是未申请权限的问题,必须动态申请权限才可以使项目正常运行。(在Android6.0+系统以上,除了在AndroidManifest文件里注册权限,还需要在活动里动态申请权限,才可保证APP正常运行)
(3)在AndroidManifest.xml中对内容提供器进行注册:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameraalbumtest">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CameraAlbumTest">
<provider
android:authorities="com.example.cameraalbumtest.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
(4)在res目录下创建一个文件夹File,在File下创建一个file_paths.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--指定Uri共享,name属性值随便填,path的值表示共享的具体路径,空值表示将整个SD卡进行共享-->
<external-path
name="my_images"
path="."/>
</paths>
2.从相册中选择照片:
package com.example.cameraalbumtest;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MainActivity<GoogleApiClient> extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
private ImageView picture;
private Uri imageUri;
public static final int CHOOSE_PHOTO = 2;
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo); //获取按钮的实例
Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);
picture = (ImageView) findViewById(R.id.picture); //获取图片显示控件的实例
takePhoto.setOnClickListener(new View.OnClickListener() { //注册监听事件
@Override
public void onClick(View v) {
/*
* 创建File对象,用于存储摄像头拍摄的图片,图片命名为output_image.jpg
* 将它存放在手机SD卡的应用关联缓存目录下,关联缓存目录指SD卡中专门用于存放当前应用缓存数据的位置,getExternalCacheDir()方法可得到这个目录
* 具体路径是/sdcard/Android/data/<package name>/cache
* 从Android6.0开始,读写SD卡被列为危险权限,如果存放在SD卡其他目录需进行运行时权限处理,使用此目录则不用
*/
File outputImage = new File(getExternalCacheDir(), "output_image.jpg"); //创建一个File对象,用于存放摄像头拍下的图片,这里图片命名为output_image.jpg
try {
if (outputImage.exists()) { //如果这个文件存在,就删除这个文件
outputImage.delete();
}
outputImage.createNewFile(); //创建这个文件
} catch (IOException e) { //捕获异常
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {//如果运行设备的系统版本等于或高于Android7.0
imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.cameraalbumtest.fileprovider", outputImage);
//将File对象转换成一个封装过的Uri对象。三个参数:Context对象,任意唯一字符串,File对象
//Android7.0后,直接访问本地真实路径的Uri被认为不安全,会抛出异常,而FileProvider是一种特殊的内容提供器(使用和内容提供器类似的机制保护数据),可选择性地将封装过的Uri共享给外部,提高应用安全
} else {
//如果运行设备的系统版本低于Android7.0,此Uri对象标识着output_image.jpg图片的本地真实路径
imageUri = Uri.fromFile(outputImage);
}
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED){ //动态申请权限
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},1); //向用户申请获取权限
}else{
takePhoto();
}
}
});
chooseFromAlbum.setOnClickListener(new View.OnClickListener(){ //注册监听事件
@Override
public void onClick(View view){
//动态申请权限:WRITE_EXTERNAL_STORAGE表示同时授予程序对SD卡的读和写的能力
if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},2);
}else{
openAlbum();
}
}
});
}
private void takePhoto(){
try{
//启动相机程序
//构建一个Intent对象,将这个Intent的action指定为android.media.action.IMAGE_CAPTURE
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//指定图片输出地址为得到的Uri对象
startActivityForResult(intent,TAKE_PHOTO);//启动相机
}catch(SecurityException e){
e.printStackTrace();
}
}
//打开相册程序
private void openAlbum(){
Intent intent = new Intent("android.intent.action.GET_CONTENT"); //新建一个意图,将它的action指定为"android.intent.action.GET_CONTENT"
intent.setType("image/*"); //选择图片
startActivityForResult(intent,CHOOSE_PHOTO); //打开相册
}
@Override
public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) { //回调,不论权限是否申请成功,均会回到这个方法
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
takePhoto();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
case 2:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { //startActivityResult方法的回调
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
try {
//将照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK){
//判断手机的版本号
if (Build.VERSION.SDK_INT>=19){
try {
handleImageOnKitKat(data);//4.4及以上系统使用这个方法处理图片,因为Android系统从4.4版本开始,选取相册中的图片不再返回真实的Uri了,而是封装后的uri
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else{
try {
handleImageBeforeKitKat(data);//4.4以下系统使用这个方法处理图片
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
break;
default:
break;
}
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data) throws FileNotFoundException { //4.4版本以上的系统需要对图片封装的Uri进行解析
String imagePath = null;
Uri uri = data.getData();
if(DocumentsContract.isDocumentUri(this,uri)){
//如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())){ //如果Uri的authority是media格式的话,document id需要载进行一次解析
String id = docId.split(":")[1]; //解析出数字格式的id
String selection = MediaStore.Images.Media._ID+"="+id; //构建新的Uri
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
}else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
imagePath = getImagePath(contentUri,null);
}
}else if("content".equalsIgnoreCase(uri.getScheme())){
//如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(uri,null);
}else if("file".equalsIgnoreCase(uri.getScheme())){
//如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath); //根据图片路径显示图片
}
private void handleImageBeforeKitKat(Intent data) throws FileNotFoundException { //处理4.4版本以下的Uri
Uri uri = data.getData();
String imagePath = getImagePath(uri,null);
displayImage(imagePath); //根据图片路径显示图片
}
@SuppressLint("Range")
private String getImagePath(Uri uri, String selection){
String path = null;
//通过Uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
if(cursor!=null){
if(cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
private void displayImage(String imagePath) throws FileNotFoundException {
if(imagePath!=null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
}else{
Toast.makeText(this,"failed to get image",Toast.LENGTH_SHORT).show();
}
}
}
运行程序:
如果运行时后提示错误BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: file:/storage/emulated/0/test.jpg: open failed
错误原因:版本问题(如果你正在使用你的“华为”手机进行调试,那么看一下你的系统是否为鸿蒙系统)
解决方案:
//方法声明处声明出可能出现的异常throws FileNotFoundException
bitmap=BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
(通过获取数据流的方式比较安全)
四、播放多媒体文件:
Android在播放音频和视频方面做了相当不错的支持,提供了一套较为完整的API。
1.播放音频:
在Android中播放音频文件是使用MediaPlayer类来实现的,它对多种格式的音频文件提供了非常全面的控制方法:
MediaPlayer类的工作流程:
a.创建一个MediaPlayer类的对象,调用setDataSource()方法来设置音频文件的路径;
b.调用prepare()方法使MediaPlayer进入准备状态;
c.调用start()方法开始播放音频;
d.调用pause()方法暂停播放;
e.调用reset()方法会停止播放。
*动手操作:创建一个项目PlayAudioTest。
(1)修改主活动的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Play"
/>
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pause"
/>
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop"
/>
</LinearLayout>
(2)修改主活动代码:
package com.example.playaudiotest;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private MediaPlayer mediaPlayer = new MediaPlayer();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button play = (Button) findViewById(R.id.play); //获取播放按钮控件id
Button pause = (Button) findViewById(R.id.pause); //获取暂停按钮控件id
Button stop = (Button) findViewById(R.id.stop); //获取停止按钮控件id
play.setOnClickListener(this); //注册监听器
pause.setOnClickListener(this); //注册监听器
stop.setOnClickListener(this); //注册监听器
//动态申请权限
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
initMediaPlayer();
}
}
private void initMediaPlayer(){ //音频文件播放器初始化
try {
File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");//SD卡根目录下,音乐文件夹下的音乐
mediaPlayer.setDataSource(file.getPath());//指定音频文件路径
mediaPlayer.prepare();//让MediaPlayer进入准备状态
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //判断权限是否获取成功
initMediaPlayer();
} else {
Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
finish(); //拒绝权限直接finish(),因此会直接退出应用
}
break;
default:
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.play:
if (! mediaPlayer.isPlaying()){ //判断当前mediaPlayer是否在播放音频
mediaPlayer.start(); //开始播放
}
break;
case R.id.pause:
if (mediaPlayer.isPlaying()){ //判断当前mediaPlayer是否在播放音频
mediaPlayer.pause(); //暂停播放
}
break;
case R.id.stop:
if (mediaPlayer.isPlaying()){ //判断当前mediaPlayer是否在播放音频
mediaPlayer.reset(); //停止播放
initMediaPlayer(); //初始化音频文件播放器
}
break;
default:
break;
}
}
@Override
protected void onDestroy() { //活动销毁时暂停播放并释放资源
super.onDestroy();
if (mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release(); //释放资源
}
}
}
注:在AndroidManifest.xml文件中一定要声明使用到的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2.播放视频:
在Android中播放视频文件是使用VideoView类来实现的,它将视频的显示与控制集于一身,其主要有以下常用方法:
*动手操作:在上面的项目中新建一个活动。
(1)修改新建活动的页面布局文件代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/play_video"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Play"/>
<Button
android:id="@+id/pause_video"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Pause"/>
<Button
android:id="@+id/replay_video"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Replay"/>
</LinearLayout>
<VideoView
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
(2)修改新建活动代码:
package com.example.playaudiotest;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.widget.VideoView;
import java.io.File;
public class MainActivity2 extends AppCompatActivity implements View.OnClickListener{
private VideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Button playVideo = (Button) findViewById(R.id.play_video);
Button pauseVideo = (Button) findViewById(R.id.pause_video);
Button replayVideo = (Button) findViewById(R.id.replay_video);
videoView = (VideoView) findViewById(R.id.video);
playVideo.setOnClickListener(this);
pauseVideo.setOnClickListener(this);
replayVideo.setOnClickListener(this);
//动态申请权限
if(ContextCompat.checkSelfPermission(MainActivity2.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity2.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}else{
initVideoPath(); //初始化视频播放器
}
}
private void initVideoPath(){
File file = new File(Environment.getExternalStorageState(),"movie.mp4"); //创建一个文件读取视频文件
videoView.setVideoPath(file.getPath());
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //判断权限是否获取成功
initVideoPath();
} else {
Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
finish(); //拒绝权限直接finish(),因此会直接退出应用
}
break;
default:
}
}
@Override
public void onClick(View view){
switch(view.getId()){
case R.id.play_video:
if(!videoView.isPlaying()){
videoView.start();
}
break;
case R.id.pause_video:
if(!videoView.isPlaying()){
videoView.pause();
}
break;
case R.id.replay_video:
if(videoView.isPlaying()){
videoView.resume();
}
break;
}
}
}
——参考《Android第一行代码》整理上述笔记