本系列文章分析的安卓源码版本:【Android 10.0 版本】
一、MediaPlayer的简单调用关键流程
【这只是一个简单使用过程示例】
// 此处变量声明省略
private void play() {
// 先获取SurfaceHolder
mSurfaceView = findViewById(R.id.surface_view);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(mSurfaceHolderCallback);
// 创建播放器
mMediaPlayer = new MediaPlayer();
// 设置各种监听事件
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnInfoListener(mInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
mAudioSessionId = AudioManager.generateAudioSessionId();
// 音频播放属性,该变量使用来向AudioManager进行申请AudioFocus使用的,
// 通常情况下需要遵守安卓系统的audio focus机制
mAudioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
mMediaPlayer.setAudioAttributes(mAudioAttributes);
mMediaPlayer.setAudioSessionId(mAudioSessionId);
// 设置数据源
mMediaPlayer.setDataSource(mPath);
// 设置视频显示surface
mMediaPlayer.setDisplay(mSurfaceHolder);
mMediaPlayer.setScreenOnWhilePlaying(true);
// 推荐都使用异步进行,然后在【mPreparedListener】异步监听回调中,
// 调用start()方法【mMediaPlayer.start();】即可开始播放
mMediaPlayer.prepareAsync();
}
如上调用步骤,可将分析按照这些步骤顺序分析即可。
先不分析SurfaceView,因此从【new MediaPlayer()】初始化和创建MediaPlayer对象开始分析。
主要处理流程如下:
1、MediaPlayer的初始化和创建源码实现分析;【本章内容分析】
2、MediaPlayer设置各种Listener的事件回调处理流程;
3、setDataSource方法实现流程分析;
4、setDisplay方法实现流程分析;
5、prepareAsync/prepare方法实现流程分析;
6、start()方法实现流程分析;
7、seek流程分析:
关于seek流程此处不再分析,请查看此前已有章节分析:其实际它就是seek流程源码的分析。
Android MediaPlayer在seek视频时可能会黑屏卡顿好几秒且进度条不动但有声音播放的问题源码解析
8、setScreenOnWhilePlaying方法实现流程分析;【TODO】
大致先分为以上处理流程分析
二、MediaPlayer的初始化和创建源码实现分析
1、在MediaPlayer.java类中,有这样一段静态代码:
// [android/media/MediaPlayer.java]
static {
// 加载系统中名叫media_jni的so动态库,该库位于系统system目录中
// 【/system/lib64/libmedia_jni.so】和【/system/lib/libmedia_jni.so】
// 该库就是MediaPlayer.java调用实现的JNI层库实现,
// 用于和C++底层MediaPlayer进行相互链接调用。
// 该实现代码实际上就是对应后面分析的jni层实现,
// 例如[framework/base/media/jni/android_media_MediaPlayer.cpp]
// 该加载具体流程实现不在我们的分析范围内
System.loadLibrary("media_jni");
// native层jni方法的调用,初始化native层
// 见1.1小节分析
native_init();
}
该代码的执行时机是,加载类class对象时执行,并且每个类只会执行一次,但可以创建多个类对象。
由此可知此处代码执行,早于类对象的创建时机。因此需先分析。
1.1、native_init()实现分析:
由安卓常用jni调用流程可知,native层jni的实现文件应该是包名加类名,中间用下划线组成的cpp文件。即如下文件中找到该方法:
// [framework/base/media/jni/android_media_MediaPlayer.cpp]
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaPlayer, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
// 根据注释可知,该方法只有在java层MediaPlayer类第一次class类加载时才会唯一执行一次,
// 主要就是关联Java层MediaPlayer类的一些字段和方法,用于访问这些信息
jclass clazz;
// 获取该类加载的class类对象
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return;
}
// 获取该类加载的class类对象字段表中【mNativeContext】字段的字段ID索引,
// 以供后续访问和修改该值(经过后面的分析可知该值存储的是native层MediaPlayer对象的指针值)。
// 备注:java层声明为【private long mNativeContext;】
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
// 类似上面处理,此处获取class类对象方法表中的【postEventFromNative】静态方法的方法索引,以供后续调用
// 备注:该方法是Java层MediaPlayer内部用于接收native层的通知事件
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
// 关联Java层MediaPlayer的 mNativeSurfaceTexture 字段
// 备注:用于缓存native层的surface信息
fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
if (fields.surface_texture == NULL) {
return;
}
// 释放上面native创建的class局部变量
env->DeleteLocalRef(clazz);
// 该类是网络代理配置信息,此处我们暂不分析
clazz = env->FindClass("android/net/ProxyInfo");
if (clazz == NULL) {
return;
}
fields.proxyConfigGetHost =
env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");
fields.proxyConfigGetPort =
env->GetMethodID(clazz, "getPort", "()I");
fields.proxyConfigGetExclusionList =
env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
env->DeleteLocalRef(clazz);
// 此处为视频数字版权管理信息,一般用不上,因此暂不分析
// (安卓高版本可用)
// Modular DRM
FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
if (clazz) {
GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V");
gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz)