網址:http://www.2cto.com/kf/201404/290996.html
?
最近在學習Android 4.4上面的WifiDisplay(Miracast)相關的模塊,這里先從WifiDisplay用到的各個Service講起,然后再從WifiDisplaySettings里面講解打開wfd的流程。首先看下面的主要幾個Service的架構圖:
相關Service的啟動
圖中主要有以下幾個模塊,DisplayManagerService、MediaRouterService、WifiDisplayAdapter和WifiDisplayController。其中:
DisplayManagerService用于管理系統顯示設備的生命周期,包含物理屏幕、虛擬屏幕、wifi display等,它用一組DiaplayAdapter來管理這些顯示設備。
MediaRouterService用于管理各個應用程序的多媒體播放的行為。
MediaRouter用于和MediaRouterService交互一起管理多媒體的播放行為,并維護當前已經配對上的remote display設備,包括Wifi diplay、藍牙A2DP設備、chromecast設備。
WifiDisplayAdapter是用于DisplayManagerService管理Wifi display顯示的adapter。
WifiDisplayController用于控制掃描wifi display設備、連接、斷開等操作。
?
先來順著上面的架構圖看各個Service的啟動。首先來看DisplayManagerService,在SystemServer中先創建一個DisplayManagerService對象,然后調用systemReady方法:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public DisplayManagerService(Context context, Handler mainHandler) { ???? mContext = context; ???? mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals( "1" ); ???? mHandler = new DisplayManagerHandler(mainHandler.getLooper()); ???? mUiHandler = UiThread.getHandler(); ???? mDisplayAdapterListener = new DisplayAdapterListener(); ???? mSingleDisplayDemoMode = SystemProperties.getBoolean( "persist.demo.singledisplay" , false ); ???? mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER); } public void systemReady( boolean safeMode, boolean onlyCore) { ???? synchronized (mSyncRoot) { ???????? mSafeMode = safeMode; ???????? mOnlyCore = onlyCore; ???? } ???? mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); } |
在DisplayManagerService的構造函數中,首先獲取SYSTEM_HEADLESS屬性,用于表明系統是否支持headless模式,默認為0。然后創建一個DisplayManagerHandler用于處理DisplayManagerService中的消息,mSigleDisplayDemoMode用于開發模式中。然后給自己發送MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER,我們到DisplayManagerHandler看如何處理這個消息:
?
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private final class DisplayManagerHandler extends Handler { ???? public DisplayManagerHandler(Looper looper) { ???????? super (looper, null , true /*async*/ ); ???? } ???? @Override ???? public void handleMessage(Message msg) { ???????? switch (msg.what) { ???????????? case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER: ???????????????? registerDefaultDisplayAdapter(); ???????????????? break ; ???????????? case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS: ???????????????? registerAdditionalDisplayAdapters(); ???????????????? break ; |
處理MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER消息就是調用registerDefaultDisplayAdapter來注冊一個默認的DiaplayAdapter,DisplayManagerService維護一組DiaplayAdapter,用于管理這些顯示設備。默認的DiaplayAdapter就是系統的物理屏幕,通過Surface flinger來控制輸出。
?
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private void registerDefaultDisplayAdapter() { ???? // Register default display adapter. ???? synchronized (mSyncRoot) { ???????? if (mHeadless) { ???????????? registerDisplayAdapterLocked( new HeadlessDisplayAdapter( ???????????????????? mSyncRoot, mContext, mHandler, mDisplayAdapterListener)); ???????? } else { ???????????? registerDisplayAdapterLocked( new LocalDisplayAdapter( ???????????????????? mSyncRoot, mContext, mHandler, mDisplayAdapterListener)); ???????? } ???? } } private void registerDisplayAdapterLocked(DisplayAdapter adapter) { ???? mDisplayAdapters.add(adapter); ???? adapter.registerLocked(); } |
管理surface finger的知識就不講解了。接著來看systemReady函數中會發送MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS,這里就會調用registerAdditionalDisplayAdapters來注冊其它的顯示設備:
?
?
1 2 3 4 5 6 7 8 9 | private void registerAdditionalDisplayAdapters() { ???? synchronized (mSyncRoot) { ???????? if (shouldRegisterNonEssentialDisplayAdaptersLocked()) { ???????????? registerOverlayDisplayAdapterLocked(); ???????????? registerWifiDisplayAdapterLocked(); ???????????? registerVirtualDisplayAdapterLocked(); ???????? } ???? } } |
這里主要注冊三種DisplayAdapter,一種是OverlayDiaplayAdapter用于開發模式用;一種是WifiDisplayAdapter用于wifi display,也是我們接下來要講的;還有一種是虛擬顯示。接下來只看registerWifiDisplayAdapterLocked:
?
?
1 2 3 4 5 6 7 8 9 10 | private void registerWifiDisplayAdapterLocked() { ???? if (mContext.getResources().getBoolean( ???????????? com.android.internal.R.bool.config_enableWifiDisplay) ???????????? || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, - 1 ) == 1 ) { ???????? mWifiDisplayAdapter = new WifiDisplayAdapter( ???????????????? mSyncRoot, mContext, mHandler, mDisplayAdapterListener, ???????????????? mPersistentDataStore); ???????? registerDisplayAdapterLocked(mWifiDisplayAdapter); ???? } } |
這里會創建WifiDisplayAdapter對象,我們到它的構造函數中去分析,并調用registerDisplayAdapterLocked添加到mDisplayAdapter中,這里會回調WifiDisplayAdapter的registerLocked方法:
?
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, ???????? Context context, Handler handler, Listener listener, ???????? PersistentDataStore persistentDataStore) { ???? super (syncRoot, context, handler, listener, TAG); ???? mHandler = new WifiDisplayHandler(handler.getLooper()); ???? mPersistentDataStore = persistentDataStore; ???? mSupportsProtectedBuffers = context.getResources().getBoolean( ???????????? com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers); ???? mNotificationManager = (NotificationManager)context.getSystemService( ???????????? Context.NOTIFICATION_SERVICE); } public void registerLocked() { ???? super .registerLocked(); ???? updateRememberedDisplaysLocked(); ???? getHandler().post( new Runnable() { ???????? @Override ???????? public void run() { ???????????? mDisplayController = new WifiDisplayController( ???????????????????? getContext(), getHandler(), mWifiDisplayListener); ???????????? getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, ???????????????????? new IntentFilter(ACTION_DISCONNECT), null , mHandler); ???????? } ???? }); } |
PersistentDateStore用于持久性存儲連過的wifi display設備,用于在WifiDisplaySettings中顯示前面已經連接過的設備列表。SupportsProtectedBuffer與gralloc顯示相關。在registerLocked通過updateRememberedDisplaysLocked去加載/data/system/display-manager-state.xml中保存過的列表,并記錄在mRememberedDisplays中。接著實例化一個WifiDisplayController對象,同時注冊對ACTION_DISCONNECT的receiver。接著到WifiDisplayController去分析,注意WifiDisplayController最后一個參數用于回調通知WifiDisplayAdapter相關狀態的改變,比如wifi display打開/關閉、wifi display連接/斷開等。
?
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public WifiDisplayController(Context context, Handler handler, Listener listener) { ???? mContext = context; ???? mHandler = handler; ???? mListener = listener; ???? mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); ???? mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null ); ???? IntentFilter intentFilter = new IntentFilter(); ???? intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); ???? intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); ???? intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); ???? intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ???? context.registerReceiver(mWifiP2pReceiver, intentFilter, null , mHandler); ???? ContentObserver settingsObserver = new ContentObserver(mHandler) { ???????? @Override ???????? public void onChange( boolean selfChange, Uri uri) { ???????????? updateSettings(); ???????? } ???? }; ???? final ContentResolver resolver = mContext.getContentResolver(); ???? resolver.registerContentObserver(Settings.Global.getUriFor( ???????????? Settings.Global.WIFI_DISPLAY_ON), false , settingsObserver); ???? resolver.registerContentObserver(Settings.Global.getUriFor( ???????????? Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false , settingsObserver); ???? resolver.registerContentObserver(Settings.Global.getUriFor( ???????????? Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false , settingsObserver); ???? updateSettings(); } |
?
這里主要注冊WifiP2pReceiver用于接收處理WIFI_P2P_STATE_CHANGED_ACTION、WIFI_P2P_PEERS_CHANGED_ACTION、WIFI_P2P_CONNECTION_CHANGED_ACTION、WIFI_P2P_THIS_DEVICE_CHANGED_ACTION消息,然后注冊ContentObserver來監控Settings.Global這個數據庫里面的WIFI_DISPLAY_ON、WIFI_DISPLAY_CERTIFICATION_ON和WIFI_DISPLAY_WPS_CONFIG,這里比較重要,我們后面會看到在WifiDisplaySettings里面enable wifi display的時候,就會走到這個地方來。接著調用updateSettings來處理默認是否打開Wifi display,這里默認是關閉的,我們后面再來分析這一塊。
?
接著來看MediaRouterService和MediaRouter,MediaRouter通過AIDL調用MediaRouterService的實現來完成一些工作。在SystemServer啟動MediaRouterService的時候,主要創建一個MediaRouterService,然后調用它的systemRunning方法,代碼如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public MediaRouterService(Context context) { ???? mContext = context; ???? Watchdog.getInstance().addMonitor( this ); } public void systemRunning() { ???? IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); ???? mContext.registerReceiver( new BroadcastReceiver() { ???????? @Override ???????? public void onReceive(Context context, Intent intent) { ???????????? if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) { ???????????????? switchUser(); ???????????? } ???????? } ???? }, filter); ???? switchUser(); } |
上面的方法比較簡單,主要就是接收ACTION_USER_SWITCHED,這是關于多用戶切換的操作。MediaRouterService的工作比較少,主要都是MediaRouter通過AIDL調用完成,接下來去看MediaRouter的部分,在Android官方文檔中有說明MediaRouter的調用方法:
?
A MediaRouter is retrieved through?Context.getSystemService()
?of aContext.MEDIA_ROUTER_SERVICE
. 這樣系統是實例化一個MediaRouter對象并返回,下面來看它的構造函數:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public MediaRouter(Context context) { ???? synchronized (Static. class ) { ???????? if (sStatic == null ) { ???????????? final Context appContext = context.getApplicationContext(); ???????????? sStatic = new Static(appContext); ???????????? sStatic.startMonitoringRoutes(appContext); ???????? } ???? } } ???? Static(Context appContext) { ???????? mAppContext = appContext; ???????? mResources = Resources.getSystem(); ???????? mHandler = new Handler(appContext.getMainLooper()); ???????? IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); ???????? mAudioService = IAudioService.Stub.asInterface(b); ???????? mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE); ???????? mMediaRouterService = IMediaRouterService.Stub.asInterface( ???????????????? ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); ???????? mSystemCategory = new RouteCategory( ???????????????? com.android.internal.R.string.default_audio_route_category_name, ???????????????? ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false ); ???????? mSystemCategory.mIsSystem = true ; ???????? mCanConfigureWifiDisplays = appContext.checkPermission( ???????????????? Manifest.permission.CONFIGURE_WIFI_DISPLAY, ???????????????? Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED; ???? } |
MediaRouter中主要通過Static對象來實現其大多數的方法,Static就是一個單例模式,先看Static的構造函數,也可以通過上面的圖看到,MediaRouter包含DisplayManager對象和MediaRouterService的BpBinder引用,MediaRouter還持有AudioService的BpBind,用于控制audio數據的輸出設備,例如可以用于藍牙A2DP中使用。接著看Static的startMonitoringRoutes方法:
?
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | void startMonitoringRoutes(Context appContext) { ???? mDefaultAudioVideo = new RouteInfo(mSystemCategory); ???? mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name; ???? mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; ???? mDefaultAudioVideo.updatePresentationDisplay(); ???? addRouteStatic(mDefaultAudioVideo); ???? // This will select the active wifi display route if there is one. ???? updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus()); ???? appContext.registerReceiver( new WifiDisplayStatusChangedReceiver(), ???????????? new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)); ???? appContext.registerReceiver( new VolumeChangeReceiver(), ???????????? new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); ???? mDisplayService.registerDisplayListener( this , mHandler); ???? // Bind to the media router service. ???? rebindAsUser(UserHandle.myUserId()); ???? // Select the default route if the above didn't sync us up ???? // appropriately with relevant system state. ???? if (mSelectedRoute == null ) { ???????? selectDefaultRouteStatic(); ???? } } |
?
首先注冊系統中默認的AudioVideo輸出設備,如果有處于活動狀態的wifi display連接,就記錄下當前處于活動連接的設備,默認為空。上面會注冊兩個broadcastReceiver,一個用于接收ACTION_WIFI_DISPLAY_STATUS_CHANGED,另一個接收VOLUME_CHANGED_ACTION,我們主要看前面接收ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,如下:
?
1 2 3 4 5 6 7 8 | static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver { ???? @Override ???? public void onReceive(Context context, Intent intent) { ???????? if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) { ???????????? updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra( ???????????????????? DisplayManager.EXTRA_WIFI_DISPLAY_STATUS)); ???????? } ???? } |
上面接收ACTION_WIFI_DISPLAY_STATUS_CHANGED,從Intent里面取出WifiDisplayStatus對象,WifiDisplayStatus內部的變量如下:
?
?
mFeatureState | 表明現在wifi display是關閉還是打開狀態 |
mScanState | 表現現在wifi display是否在scanning狀態 |
mActiveDisplayState | 表明現在wifi display是在連接還是無連接狀態 |
mActiveDisplay | 處于正在連接或者連接中的WifiDisplay對象 |
mDisplays | 掃描到的WifiDisplay對象數組 |
mSessionInfo | 用于過Miracast認證時用 |
?
?
然后向DisplayManager注冊一個回調函數,當有顯示設備增加、刪除或者改變的時候,就會有相應的回調函數來通知Static對象。接著綁定MediaRouterService:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void rebindAsUser( int userId) { ???? if (mCurrentUserId != userId || userId < 0 || mClient == null ) { ???????? mCurrentUserId = userId; ???????? try { ???????????? Client client = new Client(); ???????????? mMediaRouterService.registerClientAsUser(client, ???????????????????? mAppContext.getPackageName(), userId); ???????????? mClient = client; ???????? } catch (RemoteException ex) { ???????????? Log.e(TAG, "Unable to register media router client." , ex); ???????? } ???????? publishClientDiscoveryRequest(); ???????? publishClientSelectedRoute( false ); ???????? updateClientState(); ???? } } |
?
?
Enable WifiDisplay
當用戶進入WifiDisplaySettings界面,會調用其對應的onCreate和onStart方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public void onCreate(Bundle icicle) { ???? super .onCreate(icicle); ???? final Context context = getActivity(); ???? mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); ???? mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); ???? mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); ???? mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null ); ???? addPreferencesFromResource(R.xml.wifi_display_settings); ???? setHasOptionsMenu( true ); } public void onStart() { ???? super .onStart(); ???? mStarted = true ; ???? final Context context = getActivity(); ???? IntentFilter filter = new IntentFilter(); ???? filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED); ???? context.registerReceiver(mReceiver, filter); ???? getContentResolver().registerContentObserver(Settings.Global.getUriFor( ???????????? Settings.Global.WIFI_DISPLAY_ON), false , mSettingsObserver); ???? getContentResolver().registerContentObserver(Settings.Global.getUriFor( ???????????? Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false , mSettingsObserver); ???? getContentResolver().registerContentObserver(Settings.Global.getUriFor( ???????????? Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false , mSettingsObserver); ???? mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback, ???????????? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); ???? update(CHANGE_ALL); } |
首先注冊對ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,這個broadcast會在WifiDisplayAdapter里面當wifi display的狀態發送改變時發送,包括掃描到新的設備、開始連接、連接成功、斷開等消息都會被這個receiver接收到,后面我們會來分析這個receiver干了什么,然后在onStart中想MediaRouter對象注冊一個callback函數,用于獲取系統中remote display的相關回調信息。然后類似WifiDisplayController一樣,注冊一些對數據庫改變的ContentObserver。接著來看MediaRouter.addCallback的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public void addCallback( int types, Callback cb, int flags) { ???? CallbackInfo info; ???? int index = findCallbackInfo(cb); ???? if (index >= 0 ) { ???????? info = sStatic.mCallbacks.get(index); ???????? info.type |= types; ???????? info.flags |= flags; ???? } else { ???????? info = new CallbackInfo(cb, types, flags, this ); ???????? sStatic.mCallbacks.add(info); ???? } ???? sStatic.updateDiscoveryRequest(); } |
Static的mCallbacks是一個CopyOnWriteArrayList數組,記錄所有注冊到MediaRouter中的回調函數。如果已經向MediaRouter注冊過這個callback,則更新相關的type和flag;如果沒有注冊,則新建一個CallbackInfo對象并添加到mCallbacks數組中。然后調用Static的updateDiscoveryRequest去更新是否需要發送Discovery request請求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | void updateDiscoveryRequest() { ???????????? final int count = mCallbacks.size(); ???????????? for ( int i = 0 ; i < count; i++) { ???????????????? CallbackInfo cbi = mCallbacks.get(i); ???????????????? if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN ???????????????????????? | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0 ) { ???????????????????? // Discovery explicitly requested. ???????????????????? routeTypes |= cbi.type; ???????????????? } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0 ) { ???????????????????? // Discovery only passively requested. ???????????????????? passiveRouteTypes |= cbi.type; ???????????????? } else { ???????????????????? // Legacy case since applications don't specify the discovery flag. ???????????????????? // Unfortunately we just have to assume they always need discovery ???????????????????? // whenever they have a callback registered. ???????????????????? routeTypes |= cbi.type; ???????????????? } ???????????????? if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0 ) { ???????????????????? activeScan = true ; ???????????????????? if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0 ) { ???????????????????????? activeScanWifiDisplay = true ; ???????????????????? } ???????????????? } ???????????? } ???????????? if (routeTypes != 0 || activeScan) { ???????????????? // If someone else requests discovery then enable the passive listeners. ???????????????? // This is used by the MediaRouteButton and MediaRouteActionProvider since ???????????????? // they don't receive lifecycle callbacks from the Activity. ???????????????? routeTypes |= passiveRouteTypes; ???????????? } ???????????? // Update wifi display scanning. ???????????? // TODO: All of this should be managed by the media router service. ???????????? if (mCanConfigureWifiDisplays) { ???????????????? if (mSelectedRoute != null ???????????????????????? && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) { ???????????????????? // Don't scan while already connected to a remote display since ???????????????????? // it may interfere with the ongoing transmission. ???????????????????? activeScanWifiDisplay = false ; ???????????????? } ???????????????? if (activeScanWifiDisplay) { ???????????????????? if (!mActivelyScanningWifiDisplays) { ???????????????????????? mActivelyScanningWifiDisplays = true ; ???????????????????????? mDisplayService.startWifiDisplayScan(); ???????????????????? } ???????????????? } else { ???????????????????? if (mActivelyScanningWifiDisplays) { ???????????????????????? mActivelyScanningWifiDisplays = false ; ???????????????????????? mDisplayService.stopWifiDisplayScan(); ???????????????????? } ???????????????? } ???????????? } ???????? } |
這個函數體比較長,主要通過注冊的一系列的callback類型來決定是否要進行wifiDisplay scan的動作,根據在WifiDisplaySettings里面注冊callback的方法: mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN),上面函數中的activeScanWifiDisplay會為true,接著會調用DisplayManagerService中的startWifiDisplayScan,如下圖。?這里會通過WifiDisplayAdapter調用到WifiDisplayController的updateScanState動作,我們到updateScanState中去分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | private void updateScanState() { ???? if (mScanRequested && mWfdEnabled && mDesiredDevice == null ) { ???????? if (!mDiscoverPeersInProgress) { ???????????? Slog.i(TAG, "Starting Wifi display scan." ); ???????????? mDiscoverPeersInProgress = true ; ???????????? handleScanStarted(); ???????????? tryDiscoverPeers(); ???????? } ???? } else { ???????? if (mDiscoverPeersInProgress) { ???????????? // Cancel automatic retry right away. ???????????? mHandler.removeCallbacks(mDiscoverPeers); ???????????? if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) { ???????????????? Slog.i(TAG, "Stopping Wifi display scan." ); ???????????????? mDiscoverPeersInProgress = false ; ???????????????? stopPeerDiscovery(); ???????????????? handleScanFinished(); ???????????? } ???????? } ???? } } |
當初次進入到WifiDisplaySettings中,并沒有去optionMenu中enable wifi display時,上面code中的mWfdEnabled為false,所以會跳出前面的if語句;后面的else語句中mDiscoverPeersInProgress也為false,因為這個變量只有在scan時才會被置為true。?
接著來分析當用戶點擊了optionMenu中enable wifi display后的流程,先看WifiDisplaySettings的代碼:
1 2 3 4 5 6 7 | public boolean onOptionsItemSelected(MenuItem item) { ???? switch (item.getItemId()) { ???????? case MENU_ID_ENABLE_WIFI_DISPLAY: ???????????? mWifiDisplayOnSetting = !item.isChecked(); ???????????? item.setChecked(mWifiDisplayOnSetting); ???????????? Settings.Global.putInt(getContentResolver(), ???????????????????? Settings.Global.WIFI_DISPLAY_ON, mWifiDisplayOnSetting ? 1 : 0 ); |
這里首先改變OptionMenu的狀態,并置mWifiDisplayOnSetting為上次MenuItem相反的狀態,然后改變Settings.Global數據庫中WIFI_DISPLAY_ON的指為1。前面我們介紹過,在WifiDisplaySettings和WifiDisplayController都有注冊ContentObserver來監控這個值的變化。其中WifiDisplaySettings在監控到這個值的變化后,主要是調用MediaRouter和DisplayManager的方法去獲取系統中已經掃描到的remote display設備,并更新到listview列表上,顯然這時候還沒有開始scan,所以listview列表為空。接著看WifiDisplayController處理ContentOberver的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private void updateSettings() { ???? final ContentResolver resolver = mContext.getContentResolver(); ???? mWifiDisplayOnSetting = Settings.Global.getInt(resolver, ???????????? Settings.Global.WIFI_DISPLAY_ON, 0 ) != 0 ; ???? mWifiDisplayCertMode = Settings.Global.getInt(resolver, ???????????? Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0 ) != 0 ; ???? mWifiDisplayWpsConfig = WpsInfo.INVALID; ???? if (mWifiDisplayCertMode) { ???????? mWifiDisplayWpsConfig = Settings.Global.getInt(resolver, ?????????????? Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID); ???? } ???? updateWfdEnableState(); } |
這里主要置mWifiDisplayOnSetting為true,然后就調用updateWfdEnableState去更新wfd的狀態:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | private void updateWfdEnableState() { ???? if (mWifiDisplayOnSetting && mWifiP2pEnabled) { ???????? // WFD should be enabled. ???????? if (!mWfdEnabled && !mWfdEnabling) { ???????????? mWfdEnabling = true ; ???????????? WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); ???????????? wfdInfo.setWfdEnabled( true ); ???????????? wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); ???????????? wfdInfo.setSessionAvailable( true ); ???????????? wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); ???????????? wfdInfo.setMaxThroughput(MAX_THROUGHPUT); ???????????? mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { ???????????????? @Override ???????????????? public void onSuccess() { ???????????????????? if (DEBUG) { ???????????????????????? Slog.d(TAG, "Successfully set WFD info." ); ???????????????????? } ???????????????????? if (mWfdEnabling) { ???????????????????????? mWfdEnabling = false ; ???????????????????????? mWfdEnabled = true ; ???????????????????????? reportFeatureState(); ???????????????????????? updateScanState(); ???????????????????? } ???????????????? } ???????????????? @Override ???????????????? public void onFailure( int reason) { ???????????????????? if (DEBUG) { ???????????????????????? Slog.d(TAG, "Failed to set WFD info with reason " + reason + "." ); ???????????????????? } ???????????????????? mWfdEnabling = false ; ???????????????? } ???????????? }); ???????? } |
首先調用WifiP2pMananger的setWFDInfo把與wifi display相關的信息設置到wpa_supplicant,這些信息包括enable狀態、device type(指為source還是sink)、session available(當前可否連接)、control port(用于rtsp連接)、maxThroughput(吞吐量),這些信息最終會隨著P2P的IE信息在掃描階段被對方知道。接著會調用reportFeatureState來通知WifiDisplayAdapter相應狀態的變化,這里我們先看一下下面的流程圖來了解一下WifiDisplaySettings、MediaRouter、DisplayMananger、WifiDisplayAdapter、WifiDisplayController是如何相互通知信息的,這其中有簡單的callback,也有發送/接收broadcast,如下圖:?
通過上面的圖我們可以看到實線部分是調用關系,虛線部分是回調關系。接著我們來看reportFeatureState的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private void reportFeatureState() { ???? final int featureState = computeFeatureState(); ???? mHandler.post( new Runnable() { ???????? @Override ???????? public void run() { ???????????? mListener.onFeatureStateChanged(featureState); ???????? } ???? }); } private int computeFeatureState() { ???? if (!mWifiP2pEnabled) { ???????? return WifiDisplayStatus.FEATURE_STATE_DISABLED; ???? } ???? return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON : ???????????? WifiDisplayStatus.FEATURE_STATE_OFF; } |
直接回調WifiDisplayListener的onFeatureStateChanged,從上面的圖我們可以看著WifiDisplayListener會由WifiDisplayAdapter注冊的,去看這部分的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ???? public void onFeatureStateChanged( int featureState) { ???????? synchronized (getSyncRoot()) { ???????????? if (mFeatureState != featureState) { ???????????????? mFeatureState = featureState; ???????????????? scheduleStatusChangedBroadcastLocked(); ???????????? } ???????? } ???? } private void scheduleStatusChangedBroadcastLocked() { ???? mCurrentStatus = null ; ???? if (!mPendingStatusChangeBroadcast) { ???????? mPendingStatusChangeBroadcast = true ; ???????? mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST); ???? } } |
這里最后通過WifiDisplayHandler的sendEmptyMessage的方法實現,目的是不要卡住了WifiDisplayController后面代碼的執行,來看WifiDisplayHandler如何處理MSG_SEND_STATUS_CHANGE_BROADCAST:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ???? public void handleMessage(Message msg) { ???????? switch (msg.what) { ???????????? case MSG_SEND_STATUS_CHANGE_BROADCAST: ???????????????? handleSendStatusChangeBroadcast(); ???????????????? break ; ???????????? case MSG_UPDATE_NOTIFICATION: ???????????????? handleUpdateNotification(); ???????????????? break ; ???????? } private void handleSendStatusChangeBroadcast() { ???? final Intent intent; ???? synchronized (getSyncRoot()) { ???????? if (!mPendingStatusChangeBroadcast) { ???????????? return ; ???????? } ???????? mPendingStatusChangeBroadcast = false ; ???????? intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED); ???????? intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); ???????? intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS, ???????????????? getWifiDisplayStatusLocked()); ???? } ???? // Send protected broadcast about wifi display status to registered receivers. ???? getContext().sendBroadcastAsUser(intent, UserHandle.ALL); } |
上面的代碼都比較簡單,在getWifiDisplayStatusLocked中會根據WifiDisplayAdapter中的變量mFeatureState、mScanState、mActiveDisplayState、mActiveDisplay、mDisplays、mSessionInfo去構造一個WifiDisplayStatus對象,在前面我們介紹過這幾個變量的含義了,當然這幾個變量會從WifiDisplayListener的各個callback分別去改變自己的值。接著我們到MediaRouter中去看如何處理這個broadcastReceiver,前面我們已經講過了,WifiDisplayStatusChangedReceiver會接收這個broadcast,然后調用updateWifiDisplayStatus來更新狀態,我們稍后來看這部分的實現。回到WifiDisplayController的updateWfdEnableState方法中,接著會調用updateScanState方法開始掃描WifiDisplay設備:
1 2 3 4 5 6 7 8 9 | private void updateScanState() { ???? if (mScanRequested && mWfdEnabled && mDesiredDevice == null ) { ???????? if (!mDiscoverPeersInProgress) { ???????????? Slog.i(TAG, "Starting Wifi display scan." ); ???????????? mDiscoverPeersInProgress = true ; ???????????? handleScanStarted(); ???????????? tryDiscoverPeers(); ???????? } ???? } |
handleScanStarted用于通知WifiDisplayAdapter掃描開始了,當然WifiDisplayAdapter也會發broadcast給MediaRouter。接著會調用tryDiscoverPeers:
1 2 3 4 5 6 7 8 9 10 11 12 13 | private void tryDiscoverPeers() { ???? mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { ???????? @Override ???????? public void onSuccess() { ???????????? if (DEBUG) { ???????????????? Slog.d(TAG, "Discover peers succeeded.? Requesting peers now." ); ???????????? } ???????????? if (mDiscoverPeersInProgress) { ???????????????? requestPeers(); ???????????? } ???????? } ???? mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS); } |
這里調用WifiP2pManager的discoverPeers去掃描所有的p2p設備,比較重要是后面有發一個delay message,表示每間隔10秒就去發一下P2P_FIND。當然下了P2P_FIND命令后,并不能馬上獲取到對方設備,但因為我們前面有講過在/data/system/display-manager-state.xml有保存過前面連接過的設備列表,所以這里會馬上調用requestPeers去獲取設備列表。當然在WifiDisplayController也會注冊對WIFI_P2P_PEERS_CHANGED_ACTION的receiver,最終還是會調用reqeustPeers去獲取所有掃描到的設備列表,下面來看這個函數的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | private void requestPeers() { ???? mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() { ???????? @Override ???????? public void onPeersAvailable(WifiP2pDeviceList peers) { ???????????? if (DEBUG) { ???????????????? Slog.d(TAG, "Received list of peers." ); ???????????? } ???????????? mAvailableWifiDisplayPeers.clear(); ???????????? for (WifiP2pDevice device : peers.getDeviceList()) { ???????????????? if (DEBUG) { ???????????????????? Slog.d(TAG, "? " + describeWifiP2pDevice(device)); ???????????????? } ???????????????? if (isWifiDisplay(device)) { ???????????????????? mAvailableWifiDisplayPeers.add(device); ???????????????? } ???????????? } ???????????? if (mDiscoverPeersInProgress) { ???????????????? handleScanResults(); ???????????? } ???????? } ???? }); } |
首先從掃描的設備列表中過濾掉不能做wifi display的設備,主要從三個方面過濾,一是純粹的P2P設備,不會待用WfdInfo;第二是帶有WfdInfo,但是暫時沒有被enable;三是只能是PrimarySinkDevice,看起來Android還不支持SecondSink。并將過濾掉剩下的設備加入到mAvailableWifiDisplayPeers列表中,接著調用handleScanResults來組裝WifiDisplay列表數組并notify給WifiDisplayAdapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private void handleScanResults() { ???? final int count = mAvailableWifiDisplayPeers.size(); ???? final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); ???? for ( int i = 0 ; i < count; i++) { ???????? WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i); ???????? displays[i] = createWifiDisplay(device); ???????? updateDesiredDevice(device); ???? } ???? mHandler.post( new Runnable() { ???????? @Override ???????? public void run() { ???????????? mListener.onScanResults(displays); ???????? } ???? }); } |
這里首先根據mAvailableWifiDisplayPeers的數目創建一個WifiDisplay數組,然后一個個構造WifiDisplay對象,WifiDiplay對象包含以下幾個變量:
mDeviceAddress | 設備的Mac地址 |
mDeviceName | 設備的名字 |
mDeviceAlias | 設備的別名,一般為NULL |
mIsAvailable | 是否可用狀態 |
mCanConnect | WfdInfo中的SessionAvailable是否為1 |
mIsRemembered | 是否被記錄的 |
接著調用updateDesiredDevice用于判斷掃描到的這個設備是否是現在正在連接或者連接上的設備,如果是,則更新它的一些信息,以后在連接Wifi display的時候再來分析這一塊。接著就會向WifiDisplayAdapter回調onScanResults,回調函數中帶有已經掃描到的wifi display設備列表(如果有):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | ???????? public void onScanResults(WifiDisplay[] availableDisplays) { ???????????? synchronized (getSyncRoot()) { ???????????????? availableDisplays = mPersistentDataStore.applyWifiDisplayAliases( ???????????????????????? availableDisplays); ???????????????? boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays); ???????????????? // Check whether any of the available displays changed canConnect status. ???????????????? for ( int i = 0 ; !changed && i<br> 這里首先調用PersistentDateStore的applyWifiDisplayAliases方法去判斷掃描到的設備中有沒有以前連接過并記錄下來的wifi display設備,比較方法是比較兩者的MAC地址,如果在PersistentDateStore中找到,再比較兩者的別名(Alias),如果不相同則更新results列表,細節的代碼可以看applyWifiDisplayAlias中的實現。 <pre class = "brush:java;" >??? public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) { ???????? WifiDisplay[] results = displays; ???????? if (results != null ) { ???????????? int count = displays.length; ???????????? for ( int i = 0 ; i < count; i++) { ???????????????? WifiDisplay result = applyWifiDisplayAlias(displays[i]); ???????????????? if (result != displays[i]) { ???????????????????? if (results == displays) { ???????????????????????? results = new WifiDisplay[count]; ???????????????????????? System.arraycopy(displays, 0 , results, 0 , count); ???????????????????? } ???????????????????? results[i] = result; ???????????????? } ???????????? } ???????? } ???????? return results; ???? }</pre><br> 回到上面的onScanResults中,接著判斷剛掃描到的設備列表(availableDisplays)和之前存儲的設備列表(mAvailableDisplays)之間有沒有變化,可以數組內容以及是否可連兩個方面檢查。如果有變化,則把剛掃描到的設備列表(availableDisplays)賦值給存儲的設備列表(mAvailableDisplays)。接下來調用fixRememberedDisplayNamesFromAvailableDisplaysLocked來更新PersistentDateStore中存儲的已經連接過的wifi ? display設備,更新的條件是設備的MAC地址一樣,但設備的DeviceName和DeviceAlias有變化,這是就要更新到PersistentDateStore中,代碼如下: <pre class = "brush:java;" >??? private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() { ???????? boolean changed = false ; ???????? for ( int i = 0 ; i < mRememberedDisplays.length; i++) { ???????????? WifiDisplay rememberedDisplay = mRememberedDisplays[i]; ???????????? WifiDisplay availableDisplay = findAvailableDisplayLocked( ???????????????????? rememberedDisplay.getDeviceAddress()); ???????????? if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) { ???????????????? mRememberedDisplays[i] = availableDisplay; ???????????????? changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay); ???????????? } ???????? } ???????? if (changed) { ???????????? mPersistentDataStore.saveIfNeeded(); ???????? } ???? }</pre>如果掃描到的設備列表中有wifi display設備的名字或者別名發生了變化,就會調用到PersistentDataStore.saveIfNeeded方法把數據寫到/data/system/display-manager-state.xml中。 <br> 回到onScanResults中,接下來會調用updateDisplaysLocked來更新返回給MediaRouter的設備列表信息,在這里會把掃描到的設備以及之前存儲下來的設備做一次合并,共同保存到mDisplays數組中,后面在發送broadcast的時候,就會把mDisplays保存到WifiDisplayStatus對象中,并在broadcast帶上這個對象。 <pre class = "brush:java;" >??? private void updateDisplaysLocked() { ???????? List<wifidisplay> displays = new ArrayList<wifidisplay>( ???????????????? mAvailableDisplays.length + mRememberedDisplays.length); ???????? boolean [] remembered = new boolean [mAvailableDisplays.length]; ???????? for (WifiDisplay d : mRememberedDisplays) { ???????????? boolean available = false ; ???????????? for ( int i = 0 ; i < mAvailableDisplays.length; i++) { ???????????????? if (d.equals(mAvailableDisplays[i])) { ???????????????????? remembered[i] = available = true ; ???????????????????? break ; ???????????????? } ???????????? } ???????????? if (!available) { ???????????????? displays.add( new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(), ???????????????????????? d.getDeviceAlias(), false , false , true )); ???????????? } ???????? } ???????? for ( int i = 0 ; i < mAvailableDisplays.length; i++) { ???????????? WifiDisplay d = mAvailableDisplays[i]; ???????????? displays.add( new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(), ???????????????????? d.getDeviceAlias(), true , d.canConnect(), remembered[i])); ???????? } ???????? mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY); ???? }</wifidisplay></wifidisplay></pre><br> 上面的實現中先從mRememberedDisplays逐個添加wifi display設備到displays數組中,如果在mAvailableDisplays有相同的設備,則不添加到displays數組;后面再把mAvailableDisplays所有元素添加到displays數組,并全部賦值給mDisplays數組。 <br> 再回到onScanResults中,就會調用scheduleStatusChangedBroadcastLocked向WifiDisplayHandler發送MSG_SEND_STATUS_CHANGE_BROADCAST消息,這個我們在前面已經講過了,然后會發送broadcast,并帶上一個WifiDisplayStatus對象。現在我們再到MediaRouter和WifiDisplaySettings中看如何處理這個broadcast,先來看MediaRouter如何解析WifiDisplayStatus對象。updateWifiDisplayStatus的實現如下:<br> <pre class = "brush:java;" >??? static void updateWifiDisplayStatus(WifiDisplayStatus status) { ???????? WifiDisplay[] displays; ???????? WifiDisplay activeDisplay; ???????? if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { ???????????? displays = status.getDisplays(); ???????????? activeDisplay = status.getActiveDisplay(); ???????? } else { ???????????? displays = WifiDisplay.EMPTY_ARRAY; ???????????? activeDisplay = null ; ???????? } ???????? String activeDisplayAddress = activeDisplay != null ? ???????????????? activeDisplay.getDeviceAddress() : null ; ???????? // Add or update routes. ???????? for ( int i = 0 ; i < displays.length; i++) { ???????????? final WifiDisplay d = displays[i]; ???????????? if (shouldShowWifiDisplay(d, activeDisplay)) { ???????????????? RouteInfo route = findWifiDisplayRoute(d); ???????????????? if (route == null ) { ???????????????????? route = makeWifiDisplayRoute(d, status); ???????????????????? addRouteStatic(route); ???????????????? } else { ???????????????????? String address = d.getDeviceAddress(); ???????????????????? boolean disconnected = !address.equals(activeDisplayAddress) ???????????????????????????? && address.equals(sStatic.mPreviousActiveWifiDisplayAddress); ???????????????????? updateWifiDisplayRoute(route, d, status, disconnected); ???????????????? } ???????????????? if (d.equals(activeDisplay)) { ???????????????????? selectRouteStatic(route.getSupportedTypes(), route, false ); ???????????????? } ???????????? } ???????? } ???????? // Remove stale routes. ???????? for ( int i = sStatic.mRoutes.size(); i-- > 0 ; ) { ???????????? RouteInfo route = sStatic.mRoutes.get(i); ???????????? if (route.mDeviceAddress != null ) { ???????????????? WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress); ???????????????? if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) { ???????????????????? removeRouteStatic(route); ???????????????? } ???????????? } ???????? } ???????? sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress; ???? }</pre> <br> 上面的代碼中,首先從WifiDisplayStatus取出已經掃描到的WifiDisplay設備數組和當前處于連接狀態的WifiDisplay設備,然后shouldShowWifiDisplay用于過濾是否將這個wifi display設備加入到mRoutes數組中,判斷條件是這個設備已經連過并且有保存在PersistentDateStore或者這個設備就是當前正在連接中的設備,對于其它的設備并沒有加入到mRoutes中,這里就有個疑問了,其它沒連過的設備將在哪里加入呢? 我們后面分析WifiDisplaySettings再來看這部分。如果在mRoutes沒有找到相同的wifi ? display設備,就會把這個設備加入到mRoutes中,并通知WifiDisplaySettings相應的變化;如果在mRoutes存在相同的wifi display設備,則檢查它的名字或者狀態(available、canConnect)有沒有變化,如果有變化,則通知WifiDisplaySettings相應的改變。selectRouteStatic用于更新是否默認的router并dispatch相應的回調消息。最后會從mRoutes踢出有錯誤的wifi display設備。 <br> 我的一些簡單理解:MediaRouter只保存已經配對上的remote display設備,包括Wifi diplay、藍牙A2DP設備、chromecast設備等,用于提供給其它應用程序使用,比如youtube可以直接chromecast,當我們前面有成功和一個chromecast設備配對過后,youtube應用就可以從MediaRouter對象中獲取到當前已經配對的chromecast設備信息,并可以把youtube的視頻推送到chromecast上面播放;再舉個例子,百度視頻應用可以訪問MediaRouter中的wifi ? display設備,當我們設備中有已經連接或已經保存的wifi display設備時,就可以很方便的從直接百度視頻上面直接開始wifi display,而不需要用戶再去Settings里面掃描連接。 <br> 再來看WifiDisplaySettings中如何處理MSG_SEND_STATUS_CHANGE_BROADCAST: <pre class = "brush:java;" >??? private final BroadcastReceiver mReceiver = new BroadcastReceiver() { ???????? @Override ???????? public void onReceive(Context context, Intent intent) { ???????????? String action = intent.getAction(); ???????????? if (action.equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) { ???????????????? scheduleUpdate(CHANGE_WIFI_DISPLAY_STATUS); ???????????? } ???????? } ???? };</pre><br> 從MediaRouter中的callback消息也會進入到scheduleUpdate中,只是后面的參數不一樣,通過callback進來的參數是CHANGE_ROUTES,而broadcast進來的參數是CHANGE_WIFI_DISPLAY_STATUS,來看scheduleUpdate,最終實現是mUpdateRunnable<strong>中:</strong> <pre class = "brush:java;" >??? private void update( int changes) { ???????? boolean invalidateOptions = false ; ???????? // Update wifi display state. ???????? if ((changes & CHANGE_WIFI_DISPLAY_STATUS) != 0 ) { ???????????? mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus(); ???????????? // The wifi display feature state may have changed. ???????????? invalidateOptions = true ; ???????? } ???????? // Rebuild the routes. ???????? final PreferenceScreen preferenceScreen = getPreferenceScreen(); ???????? preferenceScreen.removeAll(); ???????? // Add all known remote display routes. ???????? final int routeCount = mRouter.getRouteCount(); ???????? for ( int i = 0 ; i < routeCount; i++) { ???????????? MediaRouter.RouteInfo route = mRouter.getRouteAt(i); ???????????? if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) { ???????????????? preferenceScreen.addPreference(createRoutePreference(route)); ???????????? } ???????? } ???????? // Additional features for wifi display routes. ???????? if (mWifiDisplayStatus != null ???????????????? && mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { ???????????? // Add all unpaired wifi displays. ???????????? for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) { ???????????????? if (!display.isRemembered() && display.isAvailable() ???????????????????????? && !display.equals(mWifiDisplayStatus.getActiveDisplay())) { ???????????????????? preferenceScreen.addPreference( new UnpairedWifiDisplayPreference( ???????????????????????????? getActivity(), display)); ???????????????? } ???????????? } ???????? } ???? }</pre><br> 上面的代碼比較簡單,一個是從MediaRouter中獲取mRoutes數組中存著的remote display設備;一個是從broadcast中的WifiDisplayStatus對象中獲取mDisplay數組,兩者相互合并構建整個listview展現給用戶。至此,wifi display的掃描流程就介紹完了,下面是整體的流程圖: <img src= "http://www.2cto.com/uploadfile/Collfiles/20140405/201404050 |