关于Android HTML5 audio autoplay无效问题的解决方案
前言:在androidHTML5开发中有不少人遇到过audio标签autoplay在某些设备上无效的问题,网上大多是讲怎么在js中操作,即在特定的时刻调用audio的play()方法,在android上还是无效。
一、解决方案
在android4.2添加了允许用户手势触发音视频播放接口,该接口默认为true,即默认不允许自动播放音视频,只能是用户交互的方式由用户自己促发播放。
WebViewwebView=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的配置
/**
*GetstheWebSettingsobjectusedtocontrolthesettingsforthis
*WebView.
*
*@returnaWebSettingsobjectthatcanbeusedtocontrolthisWebView's
*settings
*/
publicWebSettingsgetSettings(){
checkThread();
returnmProvider.getSettings();
}
这里通过一个mProvider来获取的配置信息,通过看WebView的源码,我们可以看到,WebView的所有操作都是交给mProvider来进行的。
2、mPeovider是在哪初始化的?
/**
*@hide
*/
@SuppressWarnings("deprecation")//forsuper()callintodeprecatedbaseclassconstructor.
protectedWebView(Contextcontext,AttributeSetattrs,intdefStyleAttr,intdefStyleRes,
Map<String,Object>javaScriptInterfaces,booleanprivateBrowsing){
super(context,attrs,defStyleAttr,defStyleRes);
if(context==null){
thrownewIllegalArgumentException("Invalidcontextargument");
}
sEnforceThreadChecking=context.getApplicationInfo().targetSdkVersion>=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
ensureProviderCreated();
mProvider.init(javaScriptInterfaces,privateBrowsing);
//PostconditionofcreatingawebviewistheCookieSyncManager.getInstance()isallowed.
CookieSyncManager.setGetInstanceIsAllowed();
}
可以看到有个ensureProviderCreated()方法,就是在这里创建的mProvider:
privatevoidensureProviderCreated(){
checkThread();
if(mProvider==null){
//Asthiscangetcalledduringthebaseclassconstructorchain,passtheminimum
//numberofdependencieshere;therestaredeferredtoinit().
mProvider=getFactory().createWebView(this,newPrivateAccess());
}
}
OK,到此知道了mProvider是在WebView的构造函数中创建的,并且WebView的所有操作都是交给mProvider进行的。
3、但是这个mPeovider到底是谁派来的呢?
看下WebViewFactory#getFactory()做了什么操作:
staticWebViewFactoryProvidergetProvider(){
synchronized(sProviderLock){
//Fornowthemainpurposeofthisfunction(andthefactoryabstraction)istokeep
//ushonestandminimizeusageofWebViewinternalswhenbindingtheproxy.
if(sProviderInstance!=null)returnsProviderInstance;
finalintuid=android.os.Process.myUid();
if(uid==android.os.Process.ROOT_UID||uid==android.os.Process.SYSTEM_UID){
thrownewUnsupportedOperationException(
"Forsecurityreasons,WebViewisnotallowedinprivilegedprocesses");
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,"WebViewFactory.getProvider()");
try{
Class<WebViewFactoryProvider>providerClass=getProviderClass();
StrictMode.ThreadPolicyoldPolicy=StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,"providerClass.newInstance()");
try{
sProviderInstance=providerClass.getConstructor(WebViewDelegate.class)
.newInstance(newWebViewDelegate());
if(DEBUG)Log.v(LOGTAG,"Loadedprovider:"+sProviderInstance);
returnsProviderInstance;
}catch(Exceptione){
Log.e(LOGTAG,"errorinstantiatingprovider",e);
thrownewAndroidRuntimeException(e);
}finally{
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
StrictMode.setThreadPolicy(oldPolicy);
}
}finally{
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}
}
可见在23行返回了sProviderInstance,是由providerClass通过反射创建的,15行中通过getProviderClass()得到了providerClass.
privatestaticClass<WebViewFactoryProvider>getProviderClass(){
try{
//Firstfetchthepackageinfosowecanlogthewebviewpackageversion.
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{
returngetChromiumProviderClass();
}catch(ClassNotFoundExceptione){
Log.e(LOGTAG,"errorloadingprovider",e);
thrownewAndroidRuntimeException(e);
}finally{
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}catch(MissingWebViewPackageExceptione){
//Ifthepackagedoesn'texist,thentryloadingthenullWebViewinstead.
//Ifthatsucceeds,thenthisisadevicewithoutWebViewsupport;ifitfailsthen
//swallowthefailure,complainthattherealWebViewismissingandrethrowthe
//originalexception.
try{
return(Class<WebViewFactoryProvider>)Class.forName(NULL_WEBVIEW_FACTORY);
}catch(ClassNotFoundExceptione2){
//Ignore.
}
Log.e(LOGTAG,"ChromiumWebViewpackagedoesnotexist",e);
thrownewAndroidRuntimeException(e);
}
}
主要的14行返回了一个getChromiumProviderClass();是不是有点熟悉,没错Android在4.4开始使用强大的Chromium替换掉了原来的WebKit。来看下这个getChromiumProviderClass()。
//throwsMissingWebViewPackageException
privatestaticClass<WebViewFactoryProvider>getChromiumProviderClass()
throwsClassNotFoundException{
ApplicationinitialApplication=AppGlobals.getInitialApplication();
try{
//ConstructapackagecontexttoloadtheJavacodeintothecurrentapp.
ContextwebViewContext=initialApplication.createPackageContext(
sPackageInfo.packageName,
Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY);
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
ClassLoaderclazzLoader=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.NameNotFoundExceptione){
thrownewMissingWebViewPackageException(e);
}
}
最后找到了这个CHROMIUM_WEBVIEW_FACTORY,可以看到在WebViewFactory中的定义:
privatestaticfinalStringCHROMIUM_WEBVIEW_FACTORY= "com.android.webview.chromium.WebViewChromiumFactoryProvider";
回答2小节的mProvider的初始化,在WebViewChromiumFactoryProvider的createWebView(…)中进行了mProvider的初始化:
@Override
publicWebViewProvidercreateWebView(WebViewwebView,WebView.PrivateAccessprivateAccess){
WebViewChromiumwvc=newWebViewChromium(this,webView,privateAccess);
synchronized(mLock){
if(mWebViewsToStart!=null){
mWebViewsToStart.add(newWeakReference<WebViewChromium>(wvc));
}
}
ResourceProvider.registerResources(webView.getContext());
returnwvc;
}
OK,到这里就真正找到了mProvider的真正初始化位置,其实它就是一个WebViewChromium,不要忘了我们为什么费这么大劲找mProvider,其实是为了分析webView.getSettings(),这样就回到了第一小节,通过getSettings()获取到的WebView的配置。
4、Settings的初始化
通过第一小节,我们知道Settings是mProvider的一个变量,要想找到Settings就要到WebViewChromium来看下:
@Override
publicWebSettingsgetSettings(){
returnmWebSettings;
}
接下来就是Settings初始化的地方啦
@Override
//BUG=6790250|javaScriptInterfaces|wasonlyeverusedbytheobsoleteDumpRenderTree
//soisignored.TODO:removeitfromWebViewProvider.
publicvoidinit(finalMap<String,Object>javaScriptInterfaces,
finalbooleanprivateBrowsing){
if(privateBrowsing){
mFactory.startYourEngines(true);
finalStringmsg="PrivatebrowsingisnotsupportedinWebView.";
if(mAppTargetSdkVersion>=Build.VERSION_CODES.KITKAT){
thrownewIllegalArgumentException(msg);
}else{
Log.w(TAG,msg);
TextViewwarningLabel=newTextView(mWebView.getContext());
warningLabel.setText(mWebView.getContext().getString(
com.android.internal.R.string.webviewchromium_private_browsing_warning));
mWebView.addView(warningLabel);
}
}
//Wewilldeferrealinitializationuntilweknowwhichthreadtodoiton,unless:
//-weareonthemainthreadalready(commoncase),
//-theappistargeting>=JBMR2,inwhichcasecheckThreadenforcesthatallusage
//comesfromasinglethread.(NoteinJBMR2thisexceptionwasinWebView.java).
if(mAppTargetSdkVersion>=Build.VERSION_CODES.JELLY_BEAN_MR2){
mFactory.startYourEngines(false);
checkThread();
}elseif(!mFactory.hasStarted()){
if(Looper.myLooper()==Looper.getMainLooper()){
mFactory.startYourEngines(true);
}
}
finalbooleanisAccessFromFileURLsGrantedByDefault=
mAppTargetSdkVersion<Build.VERSION_CODES.JELLY_BEAN;
finalbooleanareLegacyQuirksEnabled=
mAppTargetSdkVersion<Build.VERSION_CODES.KITKAT;
mContentsClientAdapter=newWebViewContentsClientAdapter(mWebView);
mWebSettings=newContentSettingsAdapter(newAwSettings(
mWebView.getContext(),isAccessFromFileURLsGrantedByDefault,
areLegacyQuirksEnabled));
mRunQueue.addTask(newRunnable(){
@Override
publicvoidrun(){
initForReal();
if(privateBrowsing){
//Intentionallyirreversiblydisablethewebviewinstance,sothatprivate
//userdatacannotleakthroughmisuseofanon-privateBrowingWebView
//instance.Can'tjustnulloutmAwContentsaswenevernull-checkit
//beforeuse.
destroy();
}
}
});
}
在第39行进行了mWebSettings的初始化,原来是ContentSettingsAdapter。
5、setMediaPlaybackRequiresUserGesture()分析
经过以上我们队Google大神的膜拜,我们找到了mWebSettings,下面来看下setMediaPlaybackRequiresUserGesture方法:
@Override
publicvoidsetMediaPlaybackRequiresUserGesture(booleanrequire){
mAwSettings.setMediaPlaybackRequiresUserGesture(require);
}
好吧,又是调用的mAwSettings的setMediaPlaybackRequiresUserGesture方法,那mAwSettings是什么呢?
publicContentSettingsAdapter(AwSettingsawSettings){
mAwSettings=awSettings;
}
原来是在构造函数中注入的,回到第4小节的最后,这里new了一个AwSettings。
mWebSettings=newContentSettingsAdapter(newAwSettings( mWebView.getContext(),isAccessFromFileURLsGrantedByDefault, areLegacyQuirksEnabled));
那么久来AwSettings中看下setMediaPlaybackRequiresUserGesture吧:
该类位于系统源码external/chromium_org/android_webview/java/src/org/chromium/android_webview/AwSettings.java
/**
*See{@linkandroid.webkit.WebSettings#setMediaPlaybackRequiresUserGesture}.
*/
publicvoidsetMediaPlaybackRequiresUserGesture(booleanrequire){
synchronized(mAwSettingsLock){
if(mMediaPlaybackRequiresUserGesture!=require){
mMediaPlaybackRequiresUserGesture=require;
mEventHandler.updateWebkitPreferencesLocked();
}
}
}
可以看到这里只是给一个变量mMediaPlaybackRequiresUserGesture设置了值,然后看到下面一个方法,豁然开朗:
@CalledByNative
privatebooleangetMediaPlaybackRequiresUserGestureLocked(){
returnmMediaPlaybackRequiresUserGesture;
}
该方法是由JNI层调用的,external/chromium_org/android_webview/native/aw_settings.cc中我们看到了:
web_prefs->user_gesture_required_for_media_playback= Java_AwSettings_getMediaPlaybackRequiresUserGestureLocked(env,obj);
可见在内核中去调用该接口,判断是否允许音视频的自动播放。
以上所述是小编给大家介绍的关于AndroidHTML5audioautoplay无效问题的解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!