Android多媒体深入之MediaPlayer分析

引入MediaPlayer

加载SO

当MediaPlayer类被加载时,触发静态代码块,开始加载SO。

media_jni动态库的代码在frameworks/base/media/jni/目录下,里面包含了整个Media模块的jni代码。

1
2
3
4
5
6
static {
// 加载动态库
System.loadLibrary("media_jni");
// 注册MediaPlayer的JNI函数
native_init();
}

native_init

通过JNI获取Java层相关类的字段和方法句柄。

mNativeContext用来存储Native层Player地址,postEventFromNative用来给Native层进行事件回调,mNativeSurfaceTexture就是Surface中的BufferQueue的生产者句柄,是Surfaceflinger是该Surface的BufferQueue的消费者,使用该句柄可以给Surface的BufferQueue中传递数据。

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
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
jclass clazz;

clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return;
}

fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}

fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}

fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
if (fields.surface_texture == NULL) {
return;
}

env->DeleteLocalRef(clazz);
...
// 获取网络和DRM相关字段方法
...

// 获取android/media/PlaybackParams相关字段方法
gPlaybackParamsFields.init(env);
// 获取android/media/SyncParams相关字段方法
gSyncParamsFields.init(env);
// 获取android/media/VolumeShaper$Configuration相关字段方法
gVolumeShaperFields.init(env);
}

Android多媒体深入之ALooper分析

在media模块中,会涉及到很多多线程的场景,所以在media模块中,实现了一套与Java层的Looper/Handler一样的机制,相关类有ALooper/AHandler/AMessage。(源码在av/media/libstagefright/foundation中)

ALooper

ALooper声明
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
struct ALooper : public RefBase {
typedef int32_t event_id;
typedef int32_t handler_id;

ALooper();

// Takes effect in a subsequent call to start().
void setName(const char *name);
// 注册AHandler和当前ALooper的映射关系
handler_id registerHandler(const sp<AHandler> &handler);
// 解注册该HandlerID的映射关系
void unregisterHandler(handler_id handlerID);
// 启动线程,并开始loop轮询
status_t start(
bool runOnCallingThread = false,
bool canCallJava = false,
int32_t priority = PRIORITY_DEFAULT
);
// 退出线程
status_t stop();

static int64_t GetNowUs();

const char *getName() const {
return mName.c_str();
}

protected:
virtual ~ALooper();

private:
friend struct AMessage; // post()

struct Event {
int64_t mWhenUs;
sp<AMessage> mMessage;
};
// 锁
Mutex mLock;
// 互斥量
Condition mQueueChangedCondition;
// 线程name
AString mName;
// 事件队列
List<Event> mEventQueue;
// 定义内部类
struct LooperThread;
// 作为Loop的线程
sp<LooperThread> mThread;
// 是否在调用线程中直接loop
bool mRunningLocally;

// use a separate lock for reply handling, as it is always on another thread
// use a central lock, however, to avoid creating a mutex for each reply
Mutex mRepliesLock;
Condition mRepliesCondition;

// START --- methods used only by AMessage

// posts a message on this looper with the given timeout
void post(const sp<AMessage> &msg, int64_t delayUs);

// creates a reply token to be used with this looper
sp<AReplyToken> createReplyToken();
// waits for a response for the reply token. If status is OK, the response
// is stored into the supplied variable. Otherwise, it is unchanged.
status_t awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response);
// posts a reply for a reply token. If the reply could be successfully posted,
// it returns OK. Otherwise, it returns an error value.
status_t postReply(const sp<AReplyToken> &replyToken, const sp<AMessage> &msg);

// END --- methods used only by AMessage

// 轮询处理事件
bool loop();

DISALLOW_EVIL_CONSTRUCTORS(ALooper);
};

Android多媒体架构总览

在AOSP中,多媒体相关代码主要在av/mediabase/media两个目录。其中base/media中重点在其jni目录,这里面就是libmedia_jni.so的代码,也就是Java层多媒体API对应的JNI模块。av/media中包含了Android多媒体服务的具体实现,包含了MediaPlayerService、Stagefright引擎等。

Java层API通过JNI调用Native层(libmedia_jni.so),Native层通过Binder IPC调用到相关Service进程,获取相关服务,而Service进程通过Open MAX调用硬件层实现

下面这张图是AOSP官网中展示的多媒体架构图
7aa032a7cf128cb6660a82a4b31326c3

Java层API

base/media/java目录中包含了全部的多媒体模块API,包含了我们熟悉的MediaPlayer、MediaRecorder、MediaCodec等。这些API在import的时候,都会开始加载libmedia_jni.so,并注册native方法映射。

1
2
3
4
static {
System.loadLibrary("media_jni");
native_init();
}

Native层JNI

base/media/jni/android_media_MediaPlayer.cpp中定义了libmedia_jni.so的JNI_OnLoad方法,在方法中注册了整个多媒体API需要的native方法映射。

Flutter深入之插件新API——Embedding-V2

在Flutter1.12版本中正式将Embedding-V2API在Android平台默认开启,所有官方插件都迁移到了新的API。Embedding-V2APi的优势在于针对混合开发提供了更好的支持。

版本配置

GeneratedPluginRegistrant生成内容配置,通过在AndroidManifest.xml中配置Embedding的版本来确定工具生成的GeneratedPluginRegistrant类的内容。

1
2
3
4
5
<!-- Don't delete the meta-data below.
It is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />

具体判定逻辑在flutter_tools项目中

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
  AndroidEmbeddingVersion getEmbeddingVersion() {
if (isModule) {
// A module type's Android project is used in add-to-app scenarios and
// only supports the V2 embedding.
return AndroidEmbeddingVersion.v2;
}
if (appManifestFile == null || !appManifestFile.existsSync()) {
return AndroidEmbeddingVersion.v1;
}
xml.XmlDocument document;
try {
document = xml.parse(appManifestFile.readAsStringSync());
} on xml.XmlParserException {
throwToolExit('Error parsing $appManifestFile '
'Please ensure that the android manifest is a valid XML document and try again.');
} on FileSystemException {
throwToolExit('Error reading $appManifestFile even though it exists. '
'Please ensure that you have read permission to this file and try again.');
}
for (xml.XmlElement metaData in document.findAllElements('meta-data')) {
final String name = metaData.getAttribute('android:name');
if (name == 'flutterEmbedding') {
final String embeddingVersionString = metaData.getAttribute('android:value');
if (embeddingVersionString == '1') {
return AndroidEmbeddingVersion.v1;
}
if (embeddingVersionString == '2') {
return AndroidEmbeddingVersion.v2;
}
}
}
return AndroidEmbeddingVersion.v1;
}
}

创建时机变更

旧API

旧的创建插件的时机一般是在FlutterActivity的子类onCreate()回调时,手动调用下面的方法来创建所有的插件。

1
GeneratedPluginRegistrant.registerWith(PluginRegistry registry);

新API

新的API将插件与视图进行解绑,将插件与引擎进行绑定,并且在引擎创建的时候提供了自动创建插件的功能。

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
public FlutterEngine(
@NonNull Context context,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins
) {

this.pluginRegistry = new FlutterEnginePluginRegistry(
context.getApplicationContext(),
this,
flutterLoader
);
// 默认为true,自动创建插件
if (automaticallyRegisterPlugins) {
registerPlugins();
}
}

// 通过反射调用GeneratedPluginRegistrant.registerWith(FlutterEngine engine)方法
private void registerPlugins() {
try {
Class<?> generatedPluginRegistrant = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
Method registrationMethod = generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
registrationMethod.invoke(null, this);
} catch (Exception e) {
Log.w(TAG, "Tried to automatically register plugins with FlutterEngine ("
+ this + ") but could not find and invoke the GeneratedPluginRegistrant.");
}
}

SurfaceFlinger深入之Layer创建与渲染

一切的开始resumeActivity

在Activity第一次执行resume的时候,会将DecorView添加到WindowManager中,由此开始了Activity视图显示的第一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
...
}
}
...
}
...
}

WindowManager.addView

WindowManagerImpl最终会调用到WindowManagerGlobal中,在WindowManagerGlobal中会创建一个ViewRootImpl对象,每个DecorView都会有对应的ViewRootImpl对象,而一个Window中可以有多个DecorView。

ViewRootImpl负责与WMS通信,通过其维护的IWindowSession对象来与WMS进行通信。

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
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
throw e;
}
}
}

Android图形架构总览

图形渲染流程

​ 在Android中,所有的渲染都是将内容绘制到对应的Surface上,Surface作为BufferQueue的生产方,每当发生入队操作时,都会通知BufferQueue创建者进行消费,也就是SurfaceFlinger,由SurfaceFlinger进行合并,然后发送到显示器进行显示。

下面这张图描述了关键组件的工作流程:

工作流程

  1. 当前一个可见的界面被创建时,会通过WindowManager去创建一个Window对象,而每一个Window对象中都会包含一个Surface对象,该对象由WindowManager向SurfaceFlinger请求创建。Surface中包含一个BufferQueue,它由SurfaceFlinger向HAL层调用Gralloc对其进行内存分配。

Flutter深入之flutter-build-bundle命令如何编译Dart?

开篇

上一篇我们讲到,在flutterTask中会调用flutter build bundle命令来编译dart代码,生成dart资源。那么build bundle命令是如何编译dart代码的?编译后生成了哪些资源?这些资源都是些什么?

flutter-build-bundle-产物

  • app.dill : 这就是dart代码编译后的二级制文件

  • Frontend_server.d : 这里面放的是frontend_server.dart.snapshot的绝对路径,使用该snapshot来编译dart代码生成上面的app.dill

    ​ flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot

  • snapshot_blob.bin.d : 这里面放的是所有参与编译的dart文件的绝对路径的集合,包括项目的代码和flutterSdk的代码以及pub库中的三方代码。

  • snapshot_blob.bin.d.fingerprint : 这里面放的是snapshot_blob.bin.d中的所有文件的绝对路径以及每个文件所对应的md5值。使用这个md5来判断该文件是否有修改。在每次编译的时候会判断,如果没有文件修改,则直接跳过编译。

Flutter深入之flutter初始化

开篇

前面两篇我们讲到了Flutter应用如何编译,并将编译结果如何放入APK中。那么当应用启动时,又是如何执行到main.dart中的main方法呢?Flutter的绘制内容又是如何被显示的呢?

flutter-启动流程

FlutterApplication

在应用启动的Application中会执行一些初始化操作,比如初始化一些路径文件夹等配置,搬移flutter资源,最为重要的是安装flutter.so。在前面的编译讲解中,我们知道该so就是flutter-engine。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# FlutterMain.java
public static void startInitialization(Context applicationContext, Settings settings) {
...
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
// 初始化路径等配置
initConfig(applicationContext);
// 检测aot
initAot(applicationContext);
// 搬移flutter资源
initResources(applicationContext);
// 加载so
System.loadLibrary("flutter");

// initTimeMicros是java层初始化loadLibrary所花费的时间。
// 因为so库中需要记录flutter开始的时间,但是在加载so库之前,
// 是无法调用so中的api,所以将加载so库所花费的时间传下去,然后so库中使用当前时间回退该花费时间,就是应用启动的真正时间。
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
1
2
3
4
5
6
7
8
9
# flutter_main.cc
static void RecordStartTimestamp(JNIEnv* env,
jclass jcaller,
jlong initTimeMillis) {
int64_t initTimeMicros =
static_cast<int64_t>(initTimeMillis) * static_cast<int64_t>(1000);
// initTimeMicros是java层初始化所花费的时间,使用当前时间回退该花费时间,就是应用启动的真正时间。
blink::engine_main_enter_ts = Dart_TimelineGetMicros() - initTimeMicros;
}

在加载so库完成后,会触发JNI回调JNI_OnLoad,开始动态注册JNI方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# library_loader.cc
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// 将javaVM保存成全局变量
fml::jni::InitJavaVM(vm);
// 获取JNIEnv,通过JNIEnv.RegisterNatives来动态注册
JNIEnv* env = fml::jni::AttachCurrentThread();
bool result = false;

// 注册FlutterMain.java中的native方法
result = shell::FlutterMain::Register(env);
FML_CHECK(result);

// 注册FlutterCallbackInformation.java、FlutterView.java、FlutterNativeView.java中的native方法
result = shell::PlatformViewAndroid::Register(env);
FML_CHECK(result);

// 注册VsyncWaiter.java中的native方法
result = shell::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);

return JNI_VERSION_1_4;
}

Flutter深入之flutter run命令究竟做了什么?

开篇

当我们创建一个Flutter App项目后,在当前项目路径下运行命令flutter run,就可以编译生成一个APK,并且将APK安装到模拟器中并启动。那么Flutter究竟是如何编译Dart资源的?又是如何将Dart资源放入到APK中?

接下来让我们慢慢跟着代码来分析flutter run 命令的执行过程。

flutter命令

在Mac上,flutter命令是指 ${flutterSdk}/bin/flutter 这个shell程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
... 
update逻辑
...
PROG_NAME="$(path_uri "$(follow_links "$BASH_SOURCE")")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"

FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"
DART="$DART_SDK_PATH/bin/dart"
PUB="$DART_SDK_PATH/bin/pub"

// 实际上抛除更新逻辑,flutter的这个shell就是直接执行了dart命令
// $FLUTTER_TOOL_ARGS 这个参数可以无视
// $SNAPSHOT_PATH 这个指的就是$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot这个snapshot
// $@ 这个就是flutter 后面跟的参数
"$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"

我们可以看到,实际上flutter run 命令,执行的就是 dart flutter_tools.snapshot run

Android-ReactNative渲染流程

注册组件

在RN项目中必须使用AppRegistry.registerComponent(appkey,component)来注册组件。在上一篇中的runJSBundle里面,加载js文件后,交给JSC运行(evaluateSourceCode函数),就会开始执行index.js,执行里面的代码,在index.js里面就会调用AppRegistry来注册组件。

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
registerComponent(
appKey: string,
componentProvider: ComponentProvider,
section?: boolean,
): string {
// 为该appkey准备了一个runnable
runnables[appKey] = {
componentProvider,
run: appParameters => {
// appParameters启动参数,来自原生调用传入的
// 开始渲染
renderApplication(
componentProviderInstrumentationHook(componentProvider),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
appParameters.fabric,
);
},
};
if (section) {
sections[appKey] = runnables[appKey];
}
return appKey;
},

启动组件

在上一篇说到,当Context创建完毕后,会启动js组件(defaultJSEntryPoint函数),通过调用js函数AppRegister.runApplication来启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
runApplication(appKey: string, appParameters: any): void {
invariant(
runnables[appKey] && runnables[appKey].run,
'Application ' +
appKey +
' has not been registered.\n\n' +
"Hint: This error often happens when you're running the packager " +
'(local dev server) from a wrong folder. For example you have ' +
'multiple apps and the packager is still running for the app you ' +
'were working on before.\nIf this is the case, simply kill the old ' +
'packager instance (e.g. close the packager terminal window) ' +
'and start the packager in the correct app folder (e.g. cd into app ' +
"folder and run 'npm start').\n\n" +
'This error can also happen due to a require() error during ' +
'initialization or failure to call AppRegistry.registerComponent.\n\n',
);

// 运行该appkey注册的runnable
runnables[appKey].run(appParameters);
}

解析组件

通过ReactNativeRenderer来解析组件视图,然后会调用组件的render方法获取渲染内容。

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
function renderApplication<Props: Object>(
RootComponent: React.ComponentType<Props>,
initialProps: Props,
rootTag: any,
WrapperComponent?: ?React.ComponentType<*>,
fabric?: boolean,
) {
let renderable = (
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
);
if (
RootComponent.prototype != null &&
RootComponent.prototype.unstable_isAsyncReactComponent === true
) {
const AsyncMode = React.unstable_AsyncMode;
renderable = <AsyncMode>{renderable}</AsyncMode>;
}
if (fabric) {
require('ReactFabric').render(renderable, rootTag);
} else {
//
require('ReactNative').render(renderable, rootTag);
}
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×