「android」 多媒体

Posted by Dawn-K's Blog on January 20, 2021

多媒体

[toc]

通知

书上的代码有一点过时, 所调用的方法的参数已经被废弃了. 如果想在自己手机测试的话一定要在应用的设置中打开此应用的通知提示以及声音开关

首先新建一个按钮, 名为 send_notice 然后新建一个活动, 名为 NotificationActivity , 里面随便写个 TextView , 作为点击通知后的显示界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    testNotification();
}

void testNotification() {
    Button sendNotice = (Button) findViewById(R.id.send_notice);
    sendNotice.setOnClickListener(this);
}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.send_notice:
            // 创建显示打开 NotificationActivity 的 Intent
            Intent intent = new Intent(this, NotificationActivity.class);
            // 创建点击按钮时的 Intent , 第一个是 content 第二个书上未提及,就填0即可  第三个是刚才创建的 intent , 第四个是行为,此处用不到
            PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            // 如果版本比较高,需要先新建 channel,否则后面代码运行报错(找不到 channel )
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                String channelId = "default";
                String channelName = "默认通知";
                manager.createNotificationChannel(new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH));
            }
            // 这里是建造者模式,先设置通知的属性再建造出通知
            // 起初 Builder 的构造函数不用指定 channel ,但是现在必须要指定了,就用上段代码创建的 default
            // ContentIntent 是为了设置点击通知时的切换事件
            // AutoCancel 是设置点击通知后,通知不会继续驻留在通知栏中
            // setWhen() 此处传入的参数是系统当前的以毫秒为单位的时间
            // setStyle应该是只能设置一种风格(此处存疑),要么是大量文字(与 setContentText 冲突),要么是大图片
            // 大量文字的显示只能在下拉通知栏里看到
            // setPriority 是设置通知的优先级 
            Notification notification = new NotificationCompat.Builder(this, "default")
                    .setContentTitle("This is content title")
                    .setContentText("This is content text")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                    .setContentIntent(pi)
                    .setAutoCancel(true)
                    .setSound(Uri.fromFile(new File("/system/demia/audio/ringtones/Luna.ogg")))
                    .setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications,send and sync data, and use voice actions.Get the official Android IDE and developer tools to build apps for Android."))
                    .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))
                    .setPriority(NotificationCompat.PRIORITY_MAX)
                    .build();
            // 让通知显示出来,第一个参数是给通知加一个标识符(可以通过另一种方法关闭的时候直接传入此标识符就可以关闭了),第二个参数传入通知本身
            manager.notify(1, notification);
            break;
    }
}

相机

相机的调用稍微复杂一些.

界面

首先新建一个按钮和一个图片视图.

1
2
3
4
5
6
7
8
9
10
11
<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" />

并且在 MainActivity上实例化(将 ImageView 的实例化设置为成员变量)

打开相机

给 按钮设置监听器, 监听器代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static final int TAKE_PHOTO = 1;
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.take_photo:
            //  存放在应用关联缓存目录
            // /sdcard/Android/data/<package name>/cache
            // 存放在和这个位置是不需要运行权限处理的,其他的位置是需要的
            File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
            try {
                if (outputImage.exists()) {
                    outputImage.delete();
                }
                outputImage.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 高版本的必须使用这种特殊的内容提供器,否则会报错
            if (Build.VERSION.SDK_INT >= 24) {
                // 三个参数: 上下文,任意唯一的字符串,刚才生成的图像对象
                imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.dvtester.fileprovider", outputImage);
            } else {
                imageUri = Uri.fromFile(outputImage);
            }

            // 打开相机
            Intent intent1 = new Intent("android.media.action.IMAGE_CAPTURE");
            intent1.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            // 回调
            startActivityForResult(intent1, TAKE_PHOTO);
            break;
    }
}

上文代码中提到的内容提供器, 需要自己构建 在 AndroidManifest 文件下, <application> 标签中, 加入如下内容

1
2
3
4
5
6
7
8
9
10
11
12
<!--书上的 andriod开头的 name已经失效了,需要用这个才能编译通过-->
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.dvtester.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <!--一定注意拼写,刚才就有个地方拼错了-->
    <!-- 指定共享路径 -->
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

上文的@xml部分会报错, 下面就来创建这个xml文件 新建 res/xml/file_paths , 填入如下内容 这个文件的作用可以看 这个博文

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- path部分填入 . 或者 / 均可,但是不能不填(书上给的不填已经无法通过编译了) -->
    <external-path
        name="my_images"
        path="." />
</paths>

最后为了兼容老版本, 还需要在 AndroidManifest中 加入一个访问SD卡的权限

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

显示图片

既然刚才在设置监听器的时候已经指定了 startActivityForResult , 所以还需要重载 onActivityResult 函数 重载如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    // 书上没有call super,但是不加会错误
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case TAKE_PHOTO:
            if (resultCode == RESULT_OK) {
                try {
                    Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                    // 设置 photoView 显示
                    picture.setImageBitmap(bitmap);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } 
            }
            break;
        default:
            break;
    }
}

从相册中打开

界面

绘制一个按钮和 ImageView即可

1
2
3
4
5
6
7
8
9
10
11
<Button
    android:id="@+id/choose_from_album"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Choose From Album" />

<ImageView
    android:id="@+id/picture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal" />

对象实例化

1
2
3
4
5
6
7
ImageView picture;
public static final int CHOOSE_PHOTO = 2;
void testOpenAlbum() {
    Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);
    chooseFromAlbum.setOnClickListener(this);
    picture = (ImageView) findViewById(R.id.picture);
}

实现功能

构建打开相册的Intent

1
2
3
4
5
private void openAlbum() {
    Intent intent = new Intent("android.intent.action.GET_CONTENT");
    intent.setType("image/*");
    startActivityForResult(intent, CHOOSE_PHOTO);
}

注册点击按钮的监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.choose_from_album:
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, OPEN_ALBUM_REQUEST_CODE);
            } else {
                openAlbum();
            }
        break;
    }
}
// 申请权限的回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
        case OPEN_ALBUM_REQUEST_CODE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 刚才的权限申请成功,可以读取了
                openAlbum();
            } else {
                Toast.makeText(this, "You deied the permission", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}

由于读取图片的api在19版本发生了变动, 所以就要判断版本然后再打开.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 通过uri获取图片路径
private String getImagePath(Uri uri, String selection) {
    String path = null;
    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;
}

// 展示图片(通用)
// 先从路径读取并解码为bitmap,然后传给 ImageView
private void displayImage(String imagePath) {
    if (imagePath != null) {
        Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
        picture.setImageBitmap(bitmap);
    } else {
        Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
    }
}

// 略复杂
@TargetApi(19)
private void handleImagaeOnKitKat(Intent data) {
    String imagePath = null;
    Uri uri = data.getData();
    if (DocumentsContract.isDocumentUri(this, uri)) {
        String docId = DocumentsContract.getDocumentId(uri);
        if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
            String id = docId.split(":")[1];
            String selection = MediaStore.Images.Media._ID + "=" + id;
            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/pubilc)downloads"), Long.valueOf(docId));
            imagePath = getImagePath(contentUri, null);
        }
    } else if ("content".equalsIgnoreCase(uri.getScheme())) {
        imagePath = getImagePath(uri, null);
    } else if ("file".equalsIgnoreCase(uri.getScheme())) {
        imagePath = uri.getPath();
    }
    displayImage(imagePath);
}

// 这个是比较简单的
// 直接从 Intent 里面读取 Uri,找到图片的路径,然后显示图片
private void handleImageBeforeKitKat(Intent data) {
    Uri uri = data.getData();
    String imagePath = getImagePath(uri, null);
    displayImage(imagePath);
}

由于之前的Intent要求回调, 所以这里是重载 onActivityResult 函数, 然后使用刚才实现的的两个方法进行图片的显示.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    // 书上没有call super
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case CHOOSE_PHOTO:
            if (resultCode == RESULT_OK) {
                if (Build.VERSION.SDK_INT >= 19) {
                    handleImagaeOnKitKat(data);
                } else {
                    handleImageBeforeKitKat(data);
                }
            }
            break;
        default:
            break;
    }
}

播放器

播放视频与播放音频几乎一致, 故此处仅以音频为例

新增播放, 暂停, 停止 按钮.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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" />

实例化界面, 并且在手机sd卡的根目录放置一个 music.mp3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 初始化播放器
private void initMediaPlayer() {
    try {
        File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");
        mediaPlayer.setDataSource(file.getPath());
        mediaPlayer.prepare();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
// 实例化控件 + 申请权限
private void testPlay() {
    Button play = (Button) findViewById(R.id.play);
    Button pause = (Button) findViewById(R.id.pause);
    Button stop = (Button) findViewById(R.id.stop);
    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}, MEDIA_PLAYER_REQUEST_CODE);
    } else {
        initMediaPlayer();
    }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
        case MEDIA_PLAYER_REQUEST_CODE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 刚才的权限申请成功,可以读取了
                initMediaPlayer();
            } else {
                Toast.makeText(this, "You deied the permission", Toast.LENGTH_SHORT).show();
                // 未申请权限直接关闭程序
                finish();
            }
            break;
        default:
    }
}

public static final int MEDIA_PLAYER_REQUEST_CODE = 4;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    testPlay();
}

监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.play:
            if (!mediaPlayer.isPlaying()) {
                mediaPlayer.start();
            }
            break;
        case R.id.pause:
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
            }
            break;
        case R.id.stop:
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.reset();
                initMediaPlayer();
            }
            break;
        default:
            break;
    }
}

回收资源

1
2
3
4
5
6
7
8
9
// 资源回收
@Override
protected void onDestroy() {
    super.onDestroy();
    if (mediaPlayer != null) {
        mediaPlayer.stop();
        mediaPlayer.release();
    }
}