關(guān)于Android HTML5 audio autoplay無(wú)效問(wèn)題的解決方案
前言:在android HTML5 開(kāi)發(fā)中有不少人遇到過(guò) audio 標(biāo)簽 autoplay在某些設(shè)備上無(wú)效的問(wèn)題,網(wǎng)上大多是講怎么在js中操作,即在特定的時(shí)刻調(diào)用audio的play()方法,在android上還是無(wú)效。
一、解決方案
在android 4.2添加了允許用戶手勢(shì)觸發(fā)音視頻播放接口,該接口默認(rèn)為 true ,即默認(rèn)不允許自動(dòng)播放音視頻,只能是用戶交互的方式由用戶自己促發(fā)播放。
WebView webView = this.finishActivity(R.id.main_act_webview); // ... ... // 其他配置 // ... ... // 設(shè)置4.2以后版本支持autoPlay,非用戶手勢(shì)促發(fā) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { webView.getSettings().setMediaPlaybackRequiresUserGesture(false); }
通過(guò)以上配置就可以加載帶有自動(dòng)播放的音視頻啦!
二、 源碼分析
下面我們沿著該問(wèn)題來(lái)窺探下WebView的系統(tǒng)源碼:
1、 通過(guò)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(); }
這里通過(guò)一個(gè) mProvider來(lái)獲取的配置信息,通過(guò)看WebView的源碼,我們可以看到,WebView的所有操作都是交給 mProvider來(lái)進(jìn)行的。
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(); }
可以看到有個(gè)ensureProviderCreated()方法,就是在這里創(chuàng)建的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的構(gòu)造函數(shù)中創(chuàng)建的,并且WebView的所有操作都是交給mProvider進(jìn)行的。
3、 但是這個(gè)mPeovider到底是誰(shuí)派來(lái)的呢?
看下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); } } }
可見(jiàn)在23行返回了sProviderInstance, 是由 providerClass 通過(guò)反射創(chuàng)建的,15行中通過(guò)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行 返回了一個(gè) getChromiumProviderClass(); 是不是有點(diǎn)熟悉,沒(méi)錯(cuò)Android在4.4開(kāi)始使用強(qiáng)大的Chromium替換掉了原來(lái)的WebKit。來(lái)看下這個(gè)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); } }
最后找到了這個(gè) CHROMIUM_WEBVIEW_FACTORY, 可以看到在 WebViewFactory 中的定義:
private static final String CHROMIUM_WEBVIEW_FACTORY = "com.android.webview.chromium.WebViewChromiumFactoryProvider";
回答2小節(jié)的mProvider的初始化,在WebViewChromiumFactoryProvider 的 createWebView(…) 中進(jìn)行了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 的真正初始化位置,其實(shí)它就是一個(gè)WebViewChromium,不要忘了我們?yōu)槭裁促M(fèi)這么大勁找mProvider,其實(shí)是為了分析 webView.getSettings(),這樣就回到了第一小節(jié),通過(guò)getSettings()獲取到的WebView的配置。
4、 Settings的初始化
通過(guò)第一小節(jié),我們知道Settings是mProvider的一個(gè)變量,要想找到Settings就要到 WebViewChromium 來(lái)看下:
@Override public WebSettings getSettings() { return mWebSettings; }
接下來(lái)就是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行進(jìn)行了 mWebSettings 的初始化,原來(lái)是 ContentSettingsAdapter。
5、 setMediaPlaybackRequiresUserGesture() 分析
經(jīng)過(guò)以上我們隊(duì)Google大神的膜拜,我們找到了mWebSettings,下面來(lái)看下 setMediaPlaybackRequiresUserGesture方法:
@Override public void setMediaPlaybackRequiresUserGesture(boolean require) { mAwSettings.setMediaPlaybackRequiresUserGesture(require); }
好吧,又是調(diào)用的 mAwSettings 的 setMediaPlaybackRequiresUserGesture 方法,那 mAwSettings 是什么呢?
public ContentSettingsAdapter(AwSettings awSettings) { mAwSettings = awSettings; }
原來(lái)是在構(gòu)造函數(shù)中注入的,回到第4小節(jié)的最后,這里 new 了一個(gè)AwSettings。
mWebSettings = new ContentSettingsAdapter(new AwSettings( mWebView.getContext(), isAccessFromFileURLsGrantedByDefault, areLegacyQuirksEnabled));
那么久來(lái) AwSettings 中看下 setMediaPlaybackRequiresUserGesture 吧:
該類位于系統(tǒng)源碼 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(); } } }
可以看到這里只是給一個(gè)變量 mMediaPlaybackRequiresUserGesture 設(shè)置了值,然后看到下面一個(gè)方法,豁然開(kāi)朗:
@CalledByNative private boolean getMediaPlaybackRequiresUserGestureLocked() { return mMediaPlaybackRequiresUserGesture; }
該方法是由JNI層調(diào)用的,external/chromium_org/android_webview/native/aw_settings.cc 中我們看到了:
web_prefs->user_gesture_required_for_media_playback = Java_AwSettings_getMediaPlaybackRequiresUserGestureLocked(env, obj);
可見(jiàn)在內(nèi)核中去調(diào)用該接口,判斷是否允許音視頻的自動(dòng)播放。
以上所述是小編給大家介紹的關(guān)于Android HTML5 audio autoplay無(wú)效問(wèn)題的解決方案,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- jquery html5 視頻播放控制代碼
- javascript實(shí)現(xiàn)簡(jiǎn)單的html5視頻播放器
- 詳解HTML5 使用video標(biāo)簽實(shí)現(xiàn)選擇攝像頭功能
- transform實(shí)現(xiàn)HTML5 video標(biāo)簽視頻比例拉伸實(shí)例詳解
- Android編程使WebView支持HTML5 Video全屏播放的解決方法
- 一個(gè)html5播放視頻的video控件只支持android的默認(rèn)格式mp4和3gp
- 使用js檢測(cè)瀏覽器是否支持html5中的video標(biāo)簽的方法
- HTML5視頻播放標(biāo)簽video和音頻播放標(biāo)簽audio標(biāo)簽的正確用法
相關(guān)文章
Android資源文件與層次式導(dǎo)航超詳細(xì)講解
這篇文章主要介紹了Android資源文件與層次式導(dǎo)航,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12Android實(shí)現(xiàn)Flip翻轉(zhuǎn)動(dòng)畫(huà)效果
這篇文章主要介紹了Android實(shí)現(xiàn)Flip翻轉(zhuǎn)動(dòng)畫(huà)效果,對(duì)Android程序設(shè)計(jì)人員有很好的參考借鑒價(jià)值,需要的朋友可以參考下2014-08-08淺析Android中build.gradle的實(shí)用技巧
這篇文章主要介紹了淺析Android中build.gradle的實(shí)用技巧,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03internal修飾符探索kotlin可見(jiàn)性控制詳解
這篇文章主要為大家介紹了internal修飾符探索kotlin可見(jiàn)性控制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android Studio設(shè)置、改變字體和主題的方法
這篇文章主要介紹了Android Studio設(shè)置、改變字體和主題的方法,需要的朋友可以參考下2018-03-03Android 開(kāi)發(fā)中使用Linux Shell實(shí)例詳解
這篇文章主要介紹了Android 開(kāi)發(fā)中使用Linux Shell實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03Android實(shí)現(xiàn)EditText圖文混合插入上傳功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)EditText圖文混合插入上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Android訪問(wèn)php取回json數(shù)據(jù)實(shí)例
Android訪問(wèn)php取回json數(shù)據(jù),實(shí)現(xiàn)代碼如下,遇到訪問(wèn)網(wǎng)絡(luò)的權(quán)限不足在AndroidManifest.xml中,需要進(jìn)行如下配置2013-06-06