做网站的时候宽度都怎么弄,WordPress推送百家号,介绍东莞网站建设的ppt,青岛网页设计师网址#xff1a;http://www.2cto.com/kf/201404/290996.html 最近在学习Android 4.4上面的WifiDisplay(Miracast)相关的模块#xff0c;这里先从WifiDisplay用到的各个Service讲起#xff0c;然后再从WifiDisplaySettings里面讲解打开wfd的流程。首先看下面的主要几个Servic…网址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来注册一个默认的DiaplayAdapterDisplayManagerService维护一组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和MediaRouterMediaRouter通过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 didnt 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 dont 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 dont 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)) { // Dont 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设备的别名一般为NULLmIsAvailable是否可用状态mCanConnectWfdInfo中的SessionAvailable是否为1mIsRemembered是否被记录的 接着调用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 ibr 这里首先调用PersistentDateStore的applyWifiDisplayAliases方法去判断扫描到的设备中有没有以前连接过并记录下来的wifi display设备比较方法是比较两者的MAC地址如果在PersistentDateStore中找到再比较两者的别名Alias如果不相同则更新results列表细节的代码可以看applyWifiDisplayAlias中的实现。 pre classbrush: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; }/prebr 回到上面的onScanResults中接着判断刚扫描到的设备列表availableDisplays和之前存储的设备列表mAvailableDisplays之间有没有变化可以数组内容以及是否可连两个方面检查。如果有变化则把刚扫描到的设备列表availableDisplays赋值给存储的设备列表mAvailableDisplays。接下来调用fixRememberedDisplayNamesFromAvailableDisplaysLocked来更新PersistentDateStore中存储的已经连接过的wifi display设备更新的条件是设备的MAC地址一样但设备的DeviceName和DeviceAlias有变化这是就要更新到PersistentDateStore中代码如下 pre classbrush: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 classbrush:java; private void updateDisplaysLocked() { Listwifidisplay displays new ArrayListwifidisplay( 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/prebr 上面的实现中先从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 classbrush: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 classbrush: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); } } };/prebr 从MediaRouter中的callback消息也会进入到scheduleUpdate中只是后面的参数不一样通过callback进来的参数是CHANGE_ROUTES而broadcast进来的参数是CHANGE_WIFI_DISPLAY_STATUS来看scheduleUpdate最终实现是mUpdateRunnablestrong中/strong pre classbrush: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)); } } } }/prebr 上面的代码比较简单一个是从MediaRouter中获取mRoutes数组中存着的remote display设备一个是从broadcast中的WifiDisplayStatus对象中获取mDisplay数组两者相互合并构建整个listview展现给用户。至此wifi display的扫描流程就介绍完了下面是整体的流程图 img srchttp://www.2cto.com/uploadfile/Collfiles/20140405/201404050 转载于:https://www.cnblogs.com/senior-engineer/p/4971396.html