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