前言:在android HTML5 开发中有不少人遇到过 audio 标签 autoplay在某些设备上无效的问题,网上大多是讲怎么在js中操作,即在特定的时刻调用audio的play()方法,在android上还是无效。

一、解决方案

在android 4.2添加了允许用户手势触发音视频播放接口,该接口默认为 true ,即默认不允许自动播放音视频,只能是用户交互的方式由用户自己促发播放。

WebView webView = this.finishActivity(R.id.main_act_webview);// ... ...// 其他配置// ... ...// 设置4.2以后版本支持autoPlay,非用户手势促发if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {webView.getSettings().setMediaPlaybackRequiresUserGesture(false);}

通过以上配置就可以加载带有自动播放的音视频啦!

二、 源码分析

下面我们沿着该问题来窥探下WebView的系统源码:

1、 通过getSettings()获取到的WebView的配置

/*** Gets the WebSettings object used to control the settings for this* WebView.** @return a WebSettings object that can be used to control this WebView's* settings*/public WebSettings getSettings() {checkThread();return mProvider.getSettings();}

这里通过一个 mProvider来获取的配置信息,通过看WebView的源码,我们可以看到,WebView的所有操作都是交给 mProvider来进行的。

2、 mPeovider是在哪初始化的?

/*** @hide*/@SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,Map javaScriptInterfaces, boolean privateBrowsing) {super(context, attrs, defStyleAttr, defStyleRes);if (context == null) {throw new IllegalArgumentException("Invalid context argument");}sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=Build.VERSION_CODES.JELLY_BEAN_MR2;checkThread();ensureProviderCreated();mProvider.init(javaScriptInterfaces, privateBrowsing);// Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.CookieSyncManager.setGetInstanceIsAllowed();}

可以看到有个ensureProviderCreated()方法,就是在这里创建的mProvider:

private void ensureProviderCreated() {checkThread();if (mProvider == null) {// As this can get called during the base class constructor chain, pass the minimum// number of dependencies here; the rest are deferred to init().mProvider = getFactory().createWebView(this, new PrivateAccess());}}

OK,到此知道了mProvider是在WebView的构造函数中创建的,并且WebView的所有操作都是交给mProvider进行的。

3、 但是这个mPeovider到底是谁派来的呢?

看下WebViewFactory#getFactory()做了什么操作:

static WebViewFactoryProvider getProvider() {synchronized (sProviderLock) {// For now the main purpose of this function (and the factory abstraction) is to keep// us honest and minimize usage of WebView internals when binding the proxy.if (sProviderInstance != null) return sProviderInstance;final int uid = android.os.Process.myUid();if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {throw new UnsupportedOperationException("For security reasons, WebView is not allowed in privileged processes");}Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");try {Class providerClass = getProviderClass();StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");try {sProviderInstance = providerClass.getConstructor(WebViewDelegate.class).newInstance(new WebViewDelegate());if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);return sProviderInstance;} catch (Exception e) {Log.e(LOGTAG, "error instantiating provider", e);throw new AndroidRuntimeException(e);} finally {Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);StrictMode.setThreadPolicy(oldPolicy);}} finally {Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);}}}

可见在23行返回了sProviderInstance, 是由 providerClass 通过反射创建的,15行中通过getProviderClass() 得到了providerClass.

private static Class getProviderClass() {try {// First fetch the package info so we can log the webview package version.sPackageInfo = fetchPackageInfo();Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");loadNativeLibrary();Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");try {return getChromiumProviderClass();} catch (ClassNotFoundException e) {Log.e(LOGTAG, "error loading provider", e);throw new AndroidRuntimeException(e);} finally {Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);}} catch (MissingWebViewPackageException e) {// If the package doesn't exist, then try loading the null WebView instead.// If that succeeds, then this is a device without WebView support; if it fails then// swallow the failure, complain that the real WebView is missing and rethrow the// original exception.try {return (Class) Class.forName(NULL_WEBVIEW_FACTORY);} catch (ClassNotFoundException e2) {// Ignore.}Log.e(LOGTAG, "Chromium WebView package does not exist", e);throw new AndroidRuntimeException(e);}}

主要的 14行 返回了一个 getChromiumProviderClass(); 是不是有点熟悉,没错Android在4.4开始使用强大的Chromium替换掉了原来的WebKit。来看下这个getChromiumProviderClass()。

// throws MissingWebViewPackageExceptionprivate static Class getChromiumProviderClass()throws ClassNotFoundException {Application initialApplication = AppGlobals.getInitialApplication();try {// Construct a package context to load the Java code into the current app.Context webViewContext = initialApplication.createPackageContext(sPackageInfo.packageName,Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);initialApplication.getAssets().addAssetPath(webViewContext.getApplicationInfo().sourceDir);ClassLoader clazzLoader = webViewContext.getClassLoader();Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");try {return (Class) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,clazzLoader);} finally {Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);}} catch (PackageManager.NameNotFoundException e) {throw new MissingWebViewPackageException(e);}}

最后找到了这个 CHROMIUM_WEBVIEW_FACTORY, 可以看到在 WebViewFactory 中的定义:

private static final String CHROMIUM_WEBVIEW_FACTORY ="com.android.webview.chromium.WebViewChromiumFactoryProvider";

回答2小节的mProvider的初始化,在WebViewChromiumFactoryProvider 的 createWebView(…) 中进行了mProvider的初始化:

@Overridepublic WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);synchronized (mLock) {if (mWebViewsToStart != null) {mWebViewsToStart.add(new WeakReference(wvc));}}ResourceProvider.registerResources(webView.getContext());return wvc;}

OK,到这里就真正找到了mProvider 的真正初始化位置,其实它就是一个WebViewChromium,不要忘了我们为什么费这么大劲找mProvider,其实是为了分析 webView.getSettings(),这样就回到了第一小节,通过getSettings()获取到的WebView的配置。

4、 Settings的初始化

通过第一小节,我们知道Settings是mProvider的一个变量,要想找到Settings就要到 WebViewChromium 来看下:

@Overridepublic WebSettings getSettings() {return mWebSettings;}

接下来就是Settings初始化的地方啦

@Override// BUG=6790250 |javaScriptInterfaces| was only ever used by the obsolete DumpRenderTree// so is ignored. TODO: remove it from WebViewProvider.public void init(final Map javaScriptInterfaces,final boolean privateBrowsing) {if (privateBrowsing) {mFactory.startYourEngines(true);final String msg = "Private browsing is not supported in WebView.";if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) {throw new IllegalArgumentException(msg);} else {Log.w(TAG, msg);TextView warningLabel = new TextView(mWebView.getContext());warningLabel.setText(mWebView.getContext().getString(com.android.internal.R.string.webviewchromium_private_browsing_warning));mWebView.addView(warningLabel);}}// We will defer real initialization until we know which thread to do it on, unless:// - we are on the main thread already (common case),// - the app is targeting >= JB MR2, in which case checkThread enforces that all usage// comes from a single thread. (Note in JB MR2 this exception was in WebView.java).if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) {mFactory.startYourEngines(false);checkThread();} else if (!mFactory.hasStarted()) {if (Looper.myLooper() == Looper.getMainLooper()) {mFactory.startYourEngines(true);}}final boolean isAccessFromFileURLsGrantedByDefault =mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;final boolean areLegacyQuirksEnabled =mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT;mContentsClientAdapter = new WebViewContentsClientAdapter(mWebView);mWebSettings = new ContentSettingsAdapter(new AwSettings(mWebView.getContext(), isAccessFromFileURLsGrantedByDefault,areLegacyQuirksEnabled));mRunQueue.addTask(new Runnable() {@Overridepublic void run() {initForReal();if (privateBrowsing) {// Intentionally irreversibly disable the webview instance, so that private// user data cannot leak through misuse of a non-privateBrowing WebView// instance. Can't just null out mAwContents as we never null-check it// before use.destroy();}}});}

在第39行进行了 mWebSettings 的初始化,原来是 ContentSettingsAdapter。

5、 setMediaPlaybackRequiresUserGesture() 分析

经过以上我们队Google大神的膜拜,我们找到了mWebSettings,下面来看下 setMediaPlaybackRequiresUserGesture方法:

@Overridepublic void setMediaPlaybackRequiresUserGesture(boolean require) {mAwSettings.setMediaPlaybackRequiresUserGesture(require);}

好吧,又是调用的 mAwSettings 的 setMediaPlaybackRequiresUserGesture 方法,那 mAwSettings 是什么呢?

public ContentSettingsAdapter(AwSettings awSettings) {mAwSettings = awSettings;}

原来是在构造函数中注入的,回到第4小节的最后,这里 new 了一个AwSettings。

mWebSettings = new ContentSettingsAdapter(new AwSettings(mWebView.getContext(), isAccessFromFileURLsGrantedByDefault,areLegacyQuirksEnabled));

那么久来 AwSettings 中看下 setMediaPlaybackRequiresUserGesture 吧:

该类位于系统源码 external/​chromium_org/​android_webview/​java/​src/​org/​chromium/​android_webview/​AwSettings.java

/*** See {@link android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture}.*/public void setMediaPlaybackRequiresUserGesture(boolean require) {synchronized (mAwSettingsLock) {if (mMediaPlaybackRequiresUserGesture != require) {mMediaPlaybackRequiresUserGesture = require;mEventHandler.updateWebkitPreferencesLocked();}}}

可以看到这里只是给一个变量 mMediaPlaybackRequiresUserGesture 设置了值,然后看到下面一个方法,豁然开朗:

@CalledByNativeprivate boolean getMediaPlaybackRequiresUserGestureLocked() {return mMediaPlaybackRequiresUserGesture;}

该方法是由JNI层调用的,external/​chromium_org/​android_webview/native/aw_settings.cc 中我们看到了:

web_prefs->user_gesture_required_for_media_playback =Java_AwSettings_getMediaPlaybackRequiresUserGestureLocked(env, obj);

可见在内核中去调用该接口,判断是否允许音视频的自动播放。

以上所述是小编给大家介绍的关于Android HTML5 audio autoplay无效问题的解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

更多相关文章

  1. Android(安卓)ViewPager 使用详解
  2. Android微信右上角弹出的对话选择框实现
  3. Android中给系统控件添加配置的自定义属性
  4. android 动态库死机调试方法
  5. 大厂OPPO面试— Android(安卓)开发技术面总结
  6. 【读书笔记】【Android(安卓)开发艺术探索】第3章 View 的事件体
  7. 更改android avd emulator 按键不可用
  8. 菜鸟学android---ListView和checkBox组合常见问题
  9. Android(安卓)Studio中Button等控件的Text属性英文默认大写的解

随机推荐

  1. android sqlite 不存在插入,存在更新语句
  2. Android(安卓)设计模式-单例模式
  3. Spring For Android初体验
  4. android中popupwindow弹出后,屏幕背景变成
  5. Android 编译错误总结及收集
  6. android功耗相关资料
  7. Android MVP模式
  8. [置顶] android使用getItemViewType时出
  9. android TIF启动流程--转载
  10. 如何在Android studio 配置github