Android   发布时间:2022-04-28  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了关于Android HTML5 audio autoplay无效问题的解决方案大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

前言:在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);
}@H_419_12@

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

二、 源码分析

下面我们沿着该问题来窥探下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();
}@H_419_12@

这里通过一个 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<String,Object> 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();
}@H_419_12@

可以看到有个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());
}
}@H_419_12@

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<WebViewFactoryProvider> 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);
}
}
}@H_419_12@

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

private static Class<WebViewFactoryProvider> 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<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
} catch (ClassnotFoundException e2) {
// Ignore.
}
Log.e(LOGTAG,"Chromium WebView package does not exist",E);
throw new AndroidRuntimeException(E);
}
}@H_419_12@

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

// throws MissingWebViewPackageException
private static Class<WebViewFactoryProvider> 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<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,true,clazzLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (PackageManager.NameNotFoundException E) {
throw new MissingWebViewPackageException(E);
}
}@H_419_12@

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

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

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

@Override
public WebViewProvider createWebView(WebView webView,WebView.PrivateAccess privateAccess) {
WebViewChromium wvc = new WebViewChromium(this,webView,privateAccess);
synchronized (mLock) {
if (mWebViewsToStart != null) {
mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvC));
}
}
resourceProvider.registerresources(webView.getContext());
return wvc;
}@H_419_12@

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

4、 SetTings的初始化

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

@Override
public WebSetTings getSetTings() {
return mWebSetTings;
}@H_419_12@

接下来就是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<String,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() {
@Override
public 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();
}
}
});
}@H_419_12@

在第39行进行了 mWebSetTings 的初始化,原来是 ContentSetTingsAdapter。

5、 setMediaPlayBACkrequiresUserGesture() 分析

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

@Override
public void setMediaPlayBACkrequiresUserGesture(Boolean requirE) {
mAwSetTings.setMediaPlayBACkrequiresUserGesture(requirE);
}@H_419_12@

好吧,又是调用的 mAwSetTings 的 setMediaPlayBACkrequiresUserGesture 方法,那 mAwSetTings 是什么呢?

public ContentSetTingsAdapter(AwSetTings awSetTings) {
mAwSetTings = awSetTings;
}@H_419_12@

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

mWebSetTings = new ContentSetTingsAdapter(new AwSetTings(
mWebView.getContext(),areLegacyQuirksEnabled));@H_419_12@

那么久来 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();
}
}
}@H_419_12@

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

@CalledByNative
private Boolean getMediaPlayBACkrequiresUserGestureLocked() {
return mMediaPlayBACkrequiresUserGesture;
}@H_419_12@

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

web_prefs->user_gesture_required_for_media_playBACk =
Java_AwSetTings_getMediaPlayBACkrequiresUserGestureLocked(env,obj);@H_419_12@

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

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

大佬总结

以上是大佬教程为你收集整理的关于Android HTML5 audio autoplay无效问题的解决方案全部内容,希望文章能够帮你解决关于Android HTML5 audio autoplay无效问题的解决方案所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。