关于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);
}

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

二、 源码分析

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

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

可见在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);
}
}

主要的 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);
}
}

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

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

回答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;
}

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

4、 Settings的初始化

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

@Override
public 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<String, Object> 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() {
@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();
}
}
});
}

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

5、 setMediaPlaybackRequiresUserGesture() 分析

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

@Override
public 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 设置了值,然后看到下面一个方法,豁然开朗:

@CalledByNative
private 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无效问题的解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • HTML5 实现的一个俄罗斯方块实例代码

    示例简单,运行地址为:http://chendd.cn/demo/html/canvas/elsfk.html,得需要支持html5浏览器的环境. 实现的功能:方块旋转(W键).自动下落.移动(ASD).消行.快速下落(空格键).下落阴影.游戏结束. 为实现功能:消行时的计分.等级.以及不同等级的下落速度等. 学习了xiaoE的Java版本的俄罗斯方块后,自己动手使用html5的canvas实现的, 参考效果图如下: 详细代码如下: <!DOCTYPE html> <html> &

  • 使用HTML5+Boostrap打造简单的音乐播放器

    前言:这个是综合一下我最近在学的东西做的小Demo,到实际使用还有距离,但是用来练手巩固知识点还是不错的,最近在二刷JS书和Boostrap.css的源码,做完这个Demo也算是暂告一段落,接下来是jQuery的源码和Boostrap.js的源码,任务很艰巨呢,加油~在此就不整篇的贴代码了,如果感兴趣的小伙伴可以发消息给我,可以把代码传给你们~ 正文: 先上效果图 1.布局:Boostrap里的响应式和自适应布局是自然跑不掉的,container中嵌套row再分别嵌套aside和main(H5新

  • NodeJS与HTML5相结合实现拖拽多个文件上传到服务器的实现方法

    实现多文件拖拽上传的简易Node项目,可以在github上下载,你可以先下载下来:https://github.com/Johnharvy/upLoadFiles/. 解开下载下的zip格式包,建议用webstom 运行该项目,通过app.js启动项目,如果提示找不到node.exe执行环境,请指定好你的node.exe安装位置.这里我用的express框架是3.21.2版本. 我们来简单介绍下拖拽效果是怎么实现的. 这里先看代码: <!DOCTYPE html> <html lang=

  • JS+HTML5手机开发之滚动和惯性缓动实现方法分析

    本文实例讲述了JS+HTML5手机开发之滚动和惯性缓动实现方法.分享给大家供大家参考,具体如下: 1. 滚动 以下是三种实现方式: 1) 利用原生的css属性 overflow: scroll div id= parent style = overflow:scroll; divid='content'内容区域/div /div Notice: 在android 有bug, 滚动完后会回退到最顶端的内容区域,解决办法是使用后两种方式实现 2)js 编程实现 思路:对比手指在屏幕上移动前后位置变化

  • js HTML5多图片上传及预览实例解析(不含前端的文件分割)

    本文实例为大家分享了js HTML5多图片上传及预览实例,供大家参考,具体内容如下 主要运用  1.HTML5 files可以选择多文件,以及获取多文件信息  2.XMLHTTPRequest对象,发送HTTP传输请求  3.将每一个文件放在FormData,进行传输  4.利用readAsDataURL将图片内容直接变成url,放在img标签的src当中,生成可预览的图片 html+css+js代码 <!DOCTYPE html> <head> <meta http-equ

  • HTML5 canvas 9绘制图片实例详解

    绘制图片 Var image=new Image(); image.src=" http://img4.duitang.com/uploads/item/201406/25/20140625182321_4MTau.thumb.700_0.jpeg"; image.onload=function(){} Context.drawImage(image,x,y); Context.drawImage(image,x,y,w,h); Context.drawIamge(image,sx,s

  • 手机端 HTML5使用photoswipe.js仿微信朋友圈图片放大效果

    先来几张效果图: 点击其中一张照片可放大,可支持图片文字描述: 同时支持分享功能: 支持手势放大缩小 使用js框架是PhotoSwipe.  PhotoSwipe是一个图片放大插件,兼容pc和移动端,经历过多个版本的迭代且一直在不断更新,踩过的坑不知凡几,在移动端有着巨大的优势. 1.可控制多种风格如: 标题.分享.全屏按钮,点击事件.是否加入字幕,背景透明等. 2.可支持移动端触摸手势兼容pc端 所有的基本手势支持:滑动下一个或上一个,拖动平移.缩放.放大或关闭,点击切换控件,双击放大或缩放.

  • HTML5 移动页面自适应手机屏幕宽度详解

    网上关于这方面的文章有很多,重复的东西本文不再赘述,仅提供思路,并解释一些其他文章讲述模糊的地方. 1.使用meta标签,这也是普遍使用的方法,理论上讲使用这个标签是可以适应所有尺寸的屏幕的,但是各设备对该标签的解释方式及支持程度不同造成了不能兼容所有浏览器或系统. 首先解释该标签的含义: <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, max

  • 正则表达式与HTML5新元素

    ProcessOn是一个在线协作绘图平台,为用户提供最强大.易用的作图工具! 它可以很方便的在线简单绘制一些东西,让自己去具体理解. 正则表达式 今日的正则只是学习正则的简单使用.在HTML5的标签属性的强大面前,我们已经可以不用去先学习javascript才能再去正则了,因为HTML5的input标签的pattern可以实现标签的验证,近似取代复杂的javascript使用正则验证表单,在未来一定会完全取代的. 正则的简单使用: [0-9] 查找任何从 0 至 9 的数字. [a-z] 查找任

  • 关于Android HTML5 audio autoplay无效问题的解决方案

    前言:在android HTML5 开发中有不少人遇到过 audio 标签 autoplay在某些设备上无效的问题,网上大多是讲怎么在js中操作,即在特定的时刻调用audio的play()方法,在android上还是无效. 一.解决方案 在android 4.2添加了允许用户手势触发音视频播放接口,该接口默认为 true ,即默认不允许自动播放音视频,只能是用户交互的方式由用户自己促发播放. WebView webView = this.finishActivity(R.id.main_act_

  • Android开发中requestfocus()无效的原因及解决办法

    前言 最近做公司项目的时候,经常会遇到一个问题,就是我为某个控件如EditText设置requestfocus()的时候不管用,比如说登陆的时候,我判断下用户输入的密码,如果正确就登陆,错误就提示密码错误,并且输入框获取焦点,但是实际中确不起作用 package com.example.hfs.requestfocusdemo; import android.content.Intent; import android.support.v7.app.AppCompatActivity; impo

  • Android+Html5混合开发仿微信朋友圈

    开发之前 大约从去年开始吧, 也可能是前年 Html5好像火得不得了, 不得了...总能从网上听说到 XXX混合开发, 为了紧跟潮流(虽然有点儿晚了), 咱们也看看Android+Html5混合开发是怎样的! 今天带来的案例是微信的朋友圈, 因为我觉得是微信把H5给"捧红了". 不过丑话说在前头, 咱们的仿朋友圈可是"低仿", 只是把混合开发的大致流程说说, 界面可能不堪入目...见谅.. 开发环境 Android Studio 2.2.2 JDK1.7 API 2

  • Android activity动画不生效原因及解决方案总结

    activity动画方式 在AndroidMenifest中添加activity的动画属性windowAnimationStyle <item name="android:windowAnimationStyle">@style/anim_fade</item> 在activity代码中添加 overridePendingTransition overridePendingTransition(int enterAnim,int exitAnim) 问题汇总 一

  • Android笔记整理之常见错误及解决方案汇总

    一.No active compatible AVD's or devices found. Relaunch this configuration after connecting a device or starting an AVD. 修改adb server的端口,设置一个系统环境变量ANDROID_ADB_SERVER_PORT,值7913(随意一个数字),再启动adb server 步骤: a)进入cmd命令模式 b)cd XXX\platform-tools c)adb start

  • Android模拟器安装APP出现INSTALL_FAILED_NO_MATCHING_ABIS错误解决方案

     Android模拟器安装APP出现INSTALL_FAILED_NO_MATCHING_ABIS错误解决方案 当我们想在电脑的Android模拟器中安装APP的时候,会报INSTALL_FAILED_NO_MATCHING_ABIS错误[如图1],导致APP无法在模拟器中运行.下面给出我的解决方案,希望能对你有所帮助! Android模拟器下安装APP报INSTALL_FAILED_NO_MATCHING_ABIS错误解决方案 [图1]adb install 命令安装APP 原因分析: 由于安

  • Android软键盘挡住输入框的终极解决方案

    前言 开发做得久了,总免不了会遇到各种坑. 而在Android开发的路上,『软键盘挡住了输入框』这个坑,可谓是一个旷日持久的巨坑--来来来,我们慢慢看. 入门篇 最基本的情况,如图所示:在页面底部有一个EditText,如果不做任何处理,那么在软键盘弹出的时候,就有可能会挡住EditText. 对于这种情况的处理其实很简单,只需要在AndroidManifest文件中对activity设置:android:windowSoftInputMode的值adjustPan或者adjustResize即

  • android webview 中localStorage无效的解决方法

    我在 android里面 使用html5的 localStorage 为什么存不进去也读不出来呀? 网上搜了好多都没效果 复制代码 代码如下: mainWebView = (WebView)this.findViewById(R.id.mainWebView);         WebSettings settings = mainWebView.getSettings();         settings.setJavaScriptEnabled(true);         setting

  • Android闹钟启动时间设置无效问题的解决方法

    Android开发中,alarmManager在5.0以上系统,启动时间设置无效的问题 做一个app,需要后台保持发送心跳包.由于锁屏后CPU休眠,导致心跳包线程被挂起,所以尝试使用alarmManager定时唤醒Service发送心跳包. 以下是开启alarmManager的代码 //开启轮询服务 public static void startPollingService(Context context, int seconds, Class<?> cls,String action) {

  • 低版本IE正常运行HTML5+CSS3网站的3种解决方案

    如果开发时按最低标准,比如IE6不支持的特性,我们统统不用,那就没有必要写这篇文章了:但我想这种情况极少,更多的开发,是在现代浏览器上使用它们支持的特性,而在早期浏览器上做一定的降级处理,这就是所谓 "渐进增强",也是本文要说的重点.通过下面的方法,希望可以让网站也能够在低版本IE浏览器上奇迹般地运行得更好!htmlshiv.jsRemy的 HTML5shiv通过JavaScript 来创建HTML5元素(如 main, header, footer等).在某种程度上通过JavaScr

随机推荐