第十章 多媒体应用
Android 多媒体框架支持捕获和编码各种常见的音频和视频格式。
10.1 MediaRecorder概述
用于录制音频和视频的一个类。
10.1.1 状态转换图
说明:
下面是关于MediaRecorder状态图的各个状态的介绍:
Initial:初始状态,当使用new()方法创建一个MediaRecorder对象或者调用了reset()方法时,该MediaRecorder对象处于Initial状态。在设定视频源或者音频源之后将转换为Initialized状态。另外,在除Released状态外的其它状态通过调用reset()方法都可以使MediaRecorder进入该状态。
Initialized:已初始化状态,可以通过在Initial状态调用setAudioSource()或setVideoSource()方法进入该状态。在这个状态可以通过setOutputFormat()方法设置输出格式,此时MediaRecorder转换为DataSourceConfigured状态。另外,通过reset()方法进入Initial状态。
DataSourceConfigured:数据源配置状态,这期间可以设定编码方式、输出文件、屏幕旋转、预览显示等等。可以在Initialized状态通过setOutputFormat()方法进入该状态。另外,可以通过reset()方法回到Initial状态,或者通过prepare()方法到达Prepared状态。
Prepared:就绪状态,在DataSourceConfigured状态通过prepare()方法进入该状态。在这个状态可以通过start()进入录制状态。另外,可以通过reset()方法回到Initialized状态。
Recording:录制状态,可以在Prepared状态通过调用start()方法进入该状态。另外,它可以通过stop()方法或reset()方法回到Initial状态。
Released:释放状态(官方文档给出的词叫做Idle state 空闲状态),可以通过在Initial状态调用release()方法来进入这个状态,这时将会释放所有和MediaRecorder对象绑定的资源。
Error:错误状态,当错误发生的时候进入这个状态,它可以通过reset()方法进入Initial状态。
提示:使用MediaRecorder录音录像时需要严格遵守状态图说明中的函数调用先后顺序,在不同的状态调用不同的函数,否则会出现异常。
10.1.2 使用MediaRecorder的使用
1、添加权限
<!-- 授予该程序录制声音的权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- 授予该程序使用摄像头的权限 -->
<uses-permission android:name="android.permission.CAMERA"/>
2、MainActivity.java(含申请权限)
import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
public void init(){
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA},
1000);
Button btn_record = findViewById(R.id.btn_record);
Button btn_playVideo = findViewById(R.id.btn_playVideo);
Button btn_playAudio = findViewById(R.id.btn_playAudio);
Button btn_playVideoVV = findViewById(R.id.btn_playVideoVV);
btn_record.setOnClickListener(this);
btn_playVideo.setOnClickListener(this);
btn_playAudio.setOnClickListener(this);
btn_playVideoVV.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_record:
startActivity(new Intent(this, RecordActivity.class));
break;
case R.id.btn_playVideo:
startActivity(new Intent(this, VideoActivity.class));
break;
case R.id.btn_playVideoVV:
startActivity(new Intent(this, VideoViewActivity.class));
break;
case R.id.btn_playAudio:
startActivity(new Intent(this, RecordActivity.class));
break;
}
}
}
3、RecordActivity.java(问题:声音和图像分开播放,未解决)
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.IOException;
public class RecordActivity extends AppCompatActivity implements View.OnClickListener {
// private SurfaceView surfaceView;
private TextureView textureView;
private Button btn_opt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record);
// surfaceView = findViewById(R.id.surfaceView);
textureView = findViewById(R.id.textureView);
btn_opt = findViewById(R.id.btn_opt);
btn_opt.setOnClickListener(this);
}
@Override
public void onClick(View view) {
CharSequence btn_optText = btn_opt.getText();
MediaRecorder mediaRecorder = null;
Camera camera = Camera.open();
if (TextUtils.equals("开始", btn_optText)){
btn_opt.setText("结束");
camera.setDisplayOrientation(90); //竖屏
camera.unlock();
mediaRecorder = new MediaRecorder();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置音频源 麦克风
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //设置视频源 麦克风
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //设置视频输出格式
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //设置音频编码格式
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); //设置视频格式
mediaRecorder.setOrientationHint(90); //设置文件正向
mediaRecorder.setOutputFile(new File(getExternalFilesDir(""), "a.mp4").getAbsolutePath()); //设置文件输出路径
mediaRecorder.setVideoSize(640, 480); //设置视频显示大小
// mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface()); //低版本不行
mediaRecorder.setPreviewDisplay(new Surface(textureView.getSurfaceTexture()));
try {
mediaRecorder.prepare(); //录制准备
} catch (IOException e) {
e.printStackTrace();
}
mediaRecorder.start(); //开始录制
} else {
btn_opt.setText("开始");
mediaRecorder.stop();
mediaRecorder.release(); //释放mediaRecorder
camera.stopPreview();
camera.release();
}
}
}
4、activity_record.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RecordActivity">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btn_opt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始"
app:layout_constraintBottom_toBottomOf="@+id/textureView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
10.2 MediaPlayer简介
MediaPlayer类是媒体框架最重要的组成部分之一。此类的对象能够获取、解码以及播放音频和视频,而且只需极少量设置。它支持多种不同的媒体源,例如:
- 本地资源
- 内部URI,例如您可能从内容解析器那获取的URI·外部网址(流式传输)
媒体格式列表:https://developer.android.google.cn/guide/topics/media/media-formats?hl=zh_cn
10.2.1 状态转换图
10.2.2 自定义
1、VideoActivity.java
import android.media.MediaPlayer;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.IOException;
public class VideoActivity extends AppCompatActivity implements View.OnClickListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
private TextureView textureView;
private Button btn_opt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video);
textureView = findViewById(R.id.textureView);
btn_opt = findViewById(R.id.btn_opt);
btn_opt.setOnClickListener(this);
}
@Override
public void onClick(View view) {
CharSequence btn_optText = btn_opt.getText();
MediaPlayer mediaPlayer = null;
if (TextUtils.equals("开始", btn_optText)) {
btn_opt.setText("结束");
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnPreparedListener(this); //加载完成监听
mediaPlayer.setOnCompletionListener(this); //播放完成监听
try {
mediaPlayer.setDataSource(new File(getExternalFilesDir(""), "a.mp4").getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.setSurface(new Surface(textureView.getSurfaceTexture())); //播放画布
mediaPlayer.prepareAsync(); //异步加载
} else {
btn_opt.setText("开始");
mediaPlayer.stop();
mediaPlayer.release();
}
}
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start(); //播放
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
btn_opt.setText("开始");
mediaPlayer.stop();
mediaPlayer.release();
}
}
2、activity_video.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RecordActivity">
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
10.2.3 Android自带的VideoView
1、VideoViewActivity.java
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.MediaController;
import android.widget.VideoView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
public class VideoViewActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_videoview);
VideoView videoView = findViewById(R.id.videoView);
MediaController mediaController = new MediaController(this);
mediaController.setPrevNextListeners(this, this); //上一曲、下一曲
videoView.setMediaController(mediaController);
videoView.setVideoPath(new File(getExternalFilesDir(""), "a.mp4").getAbsolutePath());
videoView.start();
}
@Override
public void onClick(View view) {
Log.i("VideoView", "======");
}
}
2、activity_videoview.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".VideoViewActivity">
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
10.3 SoundPool音效播放
10.3.1 SoundPool简介
MediaPlayer虽然也能播放音频,但是它有资源占用量较高、延迟时间较长、不支持多个音频同时播放等缺点。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的场景。而SoundPool一般用来播放密集、急促而又短暂的音效,比如:"滴滴一下,马上出发”。
10.3.2 使用SounPool
1、Music.java
public class Music {
private String name;
private int musicId;
public Music() {
}
public Music(String name, int musicId) {
this.name = name;
this.musicId = musicId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMusicId() {
return musicId;
}
public void setMusicId(int musicId) {
this.musicId = musicId;
}
@Override
public String toString() {
return "Music{" +
"name='" + name + '\'' +
", musicId=" + musicId +
'}';
}
}
2、创建SoundActivity
activity_sound.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".SoundActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/teal_200"/>
</androidx.constraintlayout.widget.ConstraintLayout>
3、music_item.xml
<?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="wrap_content"
android:orientation="vertical"
android:background="@color/red">
<TextView
android:id="@+id/tv_musicName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black"/>
</LinearLayout>
4、MusicAdamter.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class MusicAdapter extends RecyclerView.Adapter<MusicAdapter.ViewHolder> {
private List<Music> musicList;
private Context context;
public MusicAdapter(List<Music> musicList, Context context){
this.musicList = musicList;
this.context = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//用来创建ViewHolder实例,再将加载好的布局传入构造函数,最后返回ViewHolder实例
// View view = View.inflate(context, R.layout.music_item, null);
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.music_item, parent, false); //解决宽度不能铺满
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = view.getVerticalScrollbarPosition();
Toast.makeText(context, musicList.get(position).getName(), Toast.LENGTH_SHORT).show();
}
});
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(musicList.get(position).getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, pos);
}
}
});
}
@Override
public int getItemCount() {
return musicList.size();
}
public interface OnItemClickListener{
void onItemClick(View view, int position);
}
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(MusicAdapter.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
public class ViewHolder extends RecyclerView.ViewHolder{
protected TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_musicName);
}
}
}
5、SoundActivity.java
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class SoundActivity extends AppCompatActivity {
private List<Music> musicList = new ArrayList<>();
private SoundPool soundPool;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sound);
initMusics();
RecyclerView recyclerView = findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL); //设置方向
recyclerView.setLayoutManager(layoutManager);
MusicAdapter musicAdapter = new MusicAdapter(musicList, this);
musicAdapter.setOnItemClickListener(new MusicAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(SoundActivity.this, "点击了"+musicList.get(position).getName(), Toast.LENGTH_SHORT).show();
Music music = musicList.get(position);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int volumem = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
soundPool.play(music.getMusicId(), volumem, volume, 1, 1, 1);
}
});
recyclerView.setAdapter(musicAdapter);
}
private void initMusics() {
soundPool = new SoundPool.Builder().setMaxStreams(6).build();
for (int i = 0; i < 6; i++) {
musicList.add(new Music("a"+(i+1), soundPool.load(this, R.raw.a1+i, 1))); //不建议
}
// musicList.add(new Music("a2", soundPool.load(this, R.raw.a2, 1)));
// musicList.add(new Music("a3", soundPool.load(this, R.raw.a3, 1)));
// musicList.add(new Music("a4", soundPool.load(this, R.raw.a4, 1)));
// musicList.add(new Music("a5", soundPool.load(this, R.raw.a5, 1)));
// musicList.add(new Music("a6", soundPool.load(this, R.raw.a6, 1)));
}
@Override
protected void onDestroy() {
super.onDestroy();
for (Music music : musicList) {
soundPool.unload(music.getMusicId());
}
soundPool.release();
}
音效资源链接:https://pan.baidu.com/s/1yIY5l7xQf0kG7lO_U6A4uQ
提取码:neyt
第十一章 项目发布
11.1 项目安全
11.1.1 加固
为什么应用需要加固:防止应用被逆向分析、反编译、二次打包,防止嵌入各类病毒、广告等恶意代码,从源头保护数据安全和开发者利益。
11.1.2 设置多渠道
1、统计各个渠道包的情况,例如哪个渠道的下载量更大,哪个渠道下载的客户活跃度或者粘性更高等信息。
2、针对不同的渠道做一些不同的操作。
目前常用的多渠道打包工具有三种:
- 友盟
- 美团
- 360
11.1.3 生成release apk
常用:【Build】-->【Generate Signed Bundle / APK...】-->【APK】-->【Next】--> 【Key story path】-->【Next】-->【使用加固工具进行加固】 -->【应用发布】