有关核心分析请见这里~
作者: mznewfacer 时间:2012年11月27日
所有内容都是自己的分析,现在是简单罗列代码位置及整体结构,细节的东西会慢慢充实,欢迎讨论纠正,我会及时更改。
一、简单背景
简单背景:随着无线互联的深入,不管是蓝牙、WIFI或者各种基于此的规范不管是UPNP还是DLNA都随着用户的需求得到了很大的发展,google 自从android 4.0引入wifi direct后,又在11月份公布的android 4.2中引入了Miracast无线显示共享,其协议在此可以下载。具体的协议部分内容比较多,本人由于水平有限,就不在这里罗列协议的内容了,只附上一份架构图供大家对其有个大致的印象。
英文缩写对应如下:
HIDC: Human Interface Device Class
UIBC: User Input Back Channel
PES: Packetized Elementary Stream
HDCP: High-bandwidth Digital Content Protection
MPEG2-TS: Moving Picture Experts Group 2 Transport Stream
RTSP: Real-Time Streaming Protocol
RTP: Real-time Transport Protocol
Wi-Fi P2P: Wi-Fi Direct
TDLS: Tunneled Direct Link Setup
二、应用层简介
好了,接下来首先来看一看android 4.2 提供了哪些与其相关的应用:
首先,需要注意的自然是API文档中公布的 http://developer.android.com/about/versions/android-4.2.html#SecondaryDisplays
Presentation应用,在源码中路径为:development/samples/ApiDemos/src/com/example/android/apis/app/下面的两个文件
PresentationActivity.java
以及 PresentationWithMediaRouterActivity.java 。
这两个应用所使用的Presentation基类在frameworks/base/core/java/android/app/Presentation.java,可以看到其继承了dialog类,并复用了如show()以及cancel()函数。
由于官方文档已经有了关于Presentation以及MediaRouter的简要介绍,这里先不再结合framework层详细介绍,以后有机会一并再结合源码分析一下。
简单来说,Display Manager 可以列举出可以直连显示的多个设备,MediaRouter提供了快速获得系统中用于演示(presentations)默认显示设备的方法。可以利用
frameworks/base/media/java/android/media/MediaRouter.java下的getSelectedRoute(int type){ }函数来获得当前所选择type类型的Router信息。对于PresentationWithMediaRouterActivity应用而言,
MediaRouter mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO); Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;可以看到这里传入的是ROUTE_TYPE_LIVE_VIDEO类型,供其获取已选择的route信息。之后,则是判断route信息是否为空,如果不为空则返回被选择演示(presentation)设备。值得一提的是,该方法只对 route信息类型为ROUTE_TYPE_LIVE_VIDEO有效。
接下来,只要将该Display对象作为自己重构的演示(Presentation)类构造函数参数传入,这样自己重构的演示就会出现在第二个显示设备上。
mPresentation = new DemoPresentation(this, presentationDisplay); ... try { mPresentation.show(); } catch (WindowManager.InvalidDisplayException ex) { Log.w(TAG, "Couldn't show presentation! Display was removed in " + "the meantime.", ex); mPresentation = null; } } ...
private final static class DemoPresentation extends Presentation { ... public DemoPresentation(Context context, Display display) { super(context, display); } ... }
为了进一步优化附加显示设备自定义演示UI的显示效果,你可以在<style>属性中指定相关应用主题为android:presentationTheme。
为了在运行时检测外设显示设备的连接状态,你需要在自己的实现类中创建一个 MediaRouter.SimpleCallback的一个实例,该实例中需要自己实现onRoutePresentationDisplayChanged() 等回调函数。当一个新的演示显示设备连接时,系统就会回调该函数,进一步其就会调用上面提到的MediaRouter.getSelectedRoute()函数。
private final MediaRouter.SimpleCallback mMediaRouterCallback = new MediaRouter.SimpleCallback() { public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { updatePresentation(); } public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { updatePresentation(); } public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { updatePresentation(); } };
当然,使用者需要使用MediaRouter.addCallback()函数完成注册,如同在PresentationWithMediaRouterActivity应用中,
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);
这里可以简单看看调用流程,首先可以看到onRoutePresentationDisplayChanged()回调函数在MediaRouter.java会先触发dispatchRoutePresentationDisplayChanged()函数,
frameworks/base/media/java/android/media/MediaRouter.java
static void dispatchRoutePresentationDisplayChanged(RouteInfo info) { for (CallbackInfo cbi : sStatic.mCallbacks) { if ((cbi.type & info.mSupportedTypes) != 0) { cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info); } } }
进一步可以看到该分发函数被用于更新演示设备的状态,并将其提供给frameworks/base/core/java/android/app/Presentation.java中注册的监听函数,
frameworks/base/media/java/android/media/MediaRouter.java
private void updatePresentationDisplays(int changedDisplayId) { final Display[] displays = getAllPresentationDisplays(); final int count = mRoutes.size(); for (int i = 0; i < count; i++) { final RouteInfo info = mRoutes.get(i); Display display = choosePresentationDisplayForRoute(info, displays); //根据displays的地址信息从所有显示类型为Presentation displays的设备中选择对应的显示设备 if (display != info.mPresentationDisplay || (display != null && display.getDisplayId() == changedDisplayId)) { info.mPresentationDisplay = display; dispatchRoutePresentationDisplayChanged(info); } } }
@Override public void onDisplayAdded(int displayId) { updatePresentationDisplays(displayId); } @Override public void onDisplayChanged(int displayId) { updatePresentationDisplays(displayId); } @Override public void onDisplayRemoved(int displayId) { updatePresentationDisplays(displayId); }
frameworks/base/core/java/android/app/Presentation.java
@Override protected void onStart() { super.onStart(); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);// Presentation线程一启动就会注册Display Manager中负责监听演示设备变化的三个监听器 ... }
private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayAdded(int displayId) { } @Override public void onDisplayRemoved(int displayId) { if (displayId == mDisplay.getDisplayId()) { handleDisplayRemoved(); } } @Override public void onDisplayChanged(int displayId) { if (displayId == mDisplay.getDisplayId()) { handleDisplayChanged(); } } };
该注册函数的实现实际是在DisplayManagerGlobal类中,该类主要负责管理显示管理器(Display Manager)与显示管理服务(Display Manager Service)之间的通信。
frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
public void registerDisplayListener(DisplayListener listener, Handler handler) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { mDisplayListeners.add(new DisplayListenerDelegate(listener, handler)); //给动态数组中增添显示监听处理代理 registerCallbackIfNeededLocked(); //实际负责注册回调函数的方法 } } }
private void registerCallbackIfNeededLocked() { if (mCallback == null) { mCallback = new DisplayManagerCallback(); try { mDm.registerCallback(mCallback); } catch (RemoteException ex) { Log.e(TAG, "Failed to register callback with display manager service.", ex); mCallback = null; } } }
可以看到,registerCallbackIfNeededLocked()函数中新建的回调函数实际上是IDisplayManagerCallback的AIDL接口实现,
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, int event) { if (DEBUG) { Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event); } handleDisplayEvent(displayId, event); } }frameworks/base/core/java/android/hardware/display/IDisplayManagerCallback.aidl
package android.hardware.display; /** @hide */ interface IDisplayManagerCallback { oneway void onDisplayEvent(int displayId, int event); }
之后则是将新建的mCallback作为参数传入IDisplayManager中的registerCallback(in IDisplayManagerCallback callback); 接口函数中。
最后,来看看与IDisplayManager AIDL接口对应的Service实现。
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
@Override // Binder call public void registerCallback(IDisplayManagerCallback callback) { if (callback == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (mSyncRoot) { int callingPid = Binder.getCallingPid(); if (mCallbacks.get(callingPid) != null) { throw new SecurityException("The calling process has already " + "registered an IDisplayManagerCallback."); } CallbackRecord record = new CallbackRecord(callingPid, callback); try { IBinder binder = callback.asBinder(); binder.linkToDeath(record, 0); } catch (RemoteException ex) { // give up throw new RuntimeException(ex); } mCallbacks.put(callingPid, record); } }
函数notifyDisplayEventAsync( )会在DisplayManager处理线程DisplayManagerHandler中,当msg类型为MSG_DELIVER_DISPLAY_EVENT时被函数deliverDisplayEvent( )调用。进一步而言,类型为MSG_DELIVER_DISPLAY_EVENT的消息,是由函数void sendDisplayEventLocked(int displayId, int event)发送的。该函数的使用者addLogicalDisplayLocked()以及 updateLogicalDisplaysLocked( )正好通过sendDisplayEventLocked( )将显示设备ID displayid 与 三种显示事件类型联系在了一起。最后正是函数handleDisplayDeviceAdded( )、handleDisplayDeviceChanged()以及handleDisplayDeviceRemoved()将显示设备的变化状态通过注册在显示管理服务中的监听器DisplayAdapter.Listener以异步方式传递给Display adapter。
private final class DisplayAdapterListener implements DisplayAdapter.Listener { @Override public void onDisplayDeviceEvent(DisplayDevice device, int event) { switch (event) { case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: handleDisplayDeviceAdded(device); break; case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: handleDisplayDeviceChanged(device); break; case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: handleDisplayDeviceRemoved(device); break; } } ... }
接下来,让我们进入正题,来通过WiFi Display Setting应用来大致分析一下Wifidisplay具体的流程,其在源码目录下
packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java
关于此应用的细节这里就不再详述,我们首先来看Wifi Display 的设备发现,这里首先需要查看Wifi Display的设备状态,通过调用getFeatureState()可以获得进行该操作的设备是否可以支持Wifi Display,以及该功能是否被用户使能等信息。如果此时的设备状态表示Widfi display功能已经开启,那么就开始进行设备发现
if (mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { mDisplayManager.scanWifiDisplays(); }
在搜寻完设备后,用户可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference instanceof WifiDisplayPreference) { WifiDisplayPreference p = (WifiDisplayPreference)preference; WifiDisplay display = p.getDisplay(); if (display.equals(mWifiDisplayStatus.getActiveDisplay())) { showDisconnectDialog(display); } else { mDisplayManager.connectWifiDisplay(display.getDeviceAddress()); } } return super.onPreferenceTreeClick(preferenceScreen, preference); }
最后,该应用还提供对设备重命名以及剔除Wifi Display 设备连接历史信息的方法。
三、Frameworks层分析
首先,从应用层的设备发现来往下分析,我们容易看到,如同对上面PresentationWithMediaRouterActivity应用的分析,这里采取的也是AIDL进程间通信方式。来看一看接口定义文件,
frameworks/base/core/java/android/hardware/display/IDisplayManager.aidl
package android.hardware.display; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.view.DisplayInfo; /** @hide */ interface IDisplayManager { DisplayInfo getDisplayInfo(int displayId); int[] getDisplayIds(); void registerCallback(in IDisplayManagerCallback callback); // No permissions required. void scanWifiDisplays(); // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device. // No permissions required to connect to a known device. void connectWifiDisplay(String address); // No permissions required. void disconnectWifiDisplay(); // Requires CONFIGURE_WIFI_DISPLAY permission. void renameWifiDisplay(String address, String alias); // Requires CONFIGURE_WIFI_DISPLAY permission. void forgetWifiDisplay(String address); // No permissions required. WifiDisplayStatus getWifiDisplayStatus(); }
可以看到,该接口中定义了DisplayManger所需要交互的全部函数,包括设备发现和设备连接等函数。DisplayManager是根据DisplayManagerGlobal提供的单实例来访问相应的接口函数,并与Display manager service建立起联系。以下是DisplayManagerGlobal提供的获取其单例模式的函数,
frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java
public static DisplayManagerGlobal getInstance() { synchronized (DisplayManagerGlobal.class) { if (sInstance == null) { IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE); if (b != null) { sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b)); //获取DISPLAY_SERVICE服务代理并用于填充构造函数 } } return sInstance; } } private final IDisplayManager mDm; // AIDL接口对象 private DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; } public void scanWifiDisplays() { try { mDm.scanWifiDisplays(); } catch (RemoteException ex) { Log.e(TAG, "Failed to scan for Wifi displays.", ex); } }
frameworks/base/core/java/android/hardware/display/DisplayManager.java
public DisplayManager(Context context) { mContext = context; mGlobal = DisplayManagerGlobal.getInstance(); } public void scanWifiDisplays() { mGlobal.scanWifiDisplays(); }
接下来,再看一看scanWifiDisplays()在Display Manager Service中的实现,也就是AIDL接口的实际实现,
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
public final class DisplayManagerService extends IDisplayManager.Stub { ... @Override // Binder call public void scanWifiDisplays() { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { if (mWifiDisplayAdapter != null) { mWifiDisplayAdapter.requestScanLocked(); } } } finally { Binder.restoreCallingIdentity(token); } } ... }以上程序使用了mWifiDisplayAdapter对象,WifiDisplayAdapter类继承于显示适配器(DisplayAdapter),该类负责处理完成在连接到Wifi display设备时与媒体服务、Surface Flinger以及显示管理服务之间的各种交互及操作。在继续分析WifiDisplayAdapter中的流程前,我们先来看看系统是启动该服务的大致流程,
首先在ServerThread.run中通过addService(Context.DISPLAY_SERVICE,…)来注册该服务
frameworks/base/services/java/com/android/server/SystemServer.java
display = new DisplayManagerService(context, wmHandler, uiHandler); ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
通过display.systemReady(safeMode,onlyCore)来初始化。
当系统属性persist.debug.wfd.enable为1或config_enableWifiDisplay为1,并且不是内核模式和安全模式才进行初始化。该服务是displays的全局管理者,决定如何根据当前链接的物理显示设备来配置逻辑显示器。当状态发生变化时发送通知给系统和应用程序,等等。包括的适配器有OverlayDisplayAdapter,WifiDisplayAdapter,HeadlessDisplayAdapter,LocalDisplayAdapter。对于WifiDisplayAdapter而言,流程大致如下图所示,
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
public void requestScanLocked() { if (DEBUG) { Slog.d(TAG, "requestScanLocked"); } getHandler().post(new Runnable() { @Override public void run() { if (mDisplayController != null) { mDisplayController.requestScan(); } } }); }
可以看到此函数又调用了WifiDisplayController类中的requestScan()方法,值得注意的是WifiDisplayController对象必须在handler线程中实例化,该类负责处理控制在WifiDisplayAdapter与WifiP2pManager之间的各种异步操作。
frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
public void requestScan() { discoverPeers(); } private void discoverPeers() { if (!mDiscoverPeersInProgress) { mDiscoverPeersInProgress = true; mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; //尝试发现配对设备次数,默认值为10 handleScanStarted(); tryDiscoverPeers(); } private void handleScanStarted() { mHandler.post(new Runnable() { @Override public void run() { mListener.onScanStarted(); //供WifiDisplayAdapter使用的监听器接口函数 } }); } private void tryDiscoverPeers() { mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { //直接调用 WifiP2pManager接口 @Override public void onSuccess() { ... mDiscoverPeersInProgress = false; requestPeers(); //获得P2P已经配对的设备在判断是否是Wifidisplay设备,如果是加入WifiP2pDevice动态数组中 } @Override public void onFailure(int reason) { ... if (mDiscoverPeersInProgress) { if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { mHandler.postDelayed(new Runnable() { @Override public void run() { if (mDiscoverPeersInProgress) { if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { mDiscoverPeersRetriesLeft -= 1; ... tryDiscoverPeers(); } else { handleScanFinished(); mDiscoverPeersInProgress = false; } } } }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); //一次发现不成功后延时定长时间后继续尝试连接,递归函数 } else { handleScanFinished(); mDiscoverPeersInProgress = false; } } } }); }
可以看到,这里直接调用了void discoverPeers(Channel c, ActionListener listener){}函数,该函数发起WIFI对等点发现,该函数会收到发现成功或失败的监听回调。发现过程会一直保持到连接初始化完成或者一个P2P组建立完成。另外,在WifiDisplayController.java中可以看到还注册了WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION这一intent,以确定当p2p peers更改时(即收到WIFI_P2P_PEERS_CHANGED_ACTION广播后),重新获取Wifi Display配对列表,并结束设备发现任务完成相应工作,该流程由函数requestPeers()完成
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)) { //根据设备wfdInfo来判断其是否支持wifi display;并且判断其设备类型是否是主sink设备 mAvailableWifiDisplayPeers.add(device); } } handleScanFinished(); //结束设备发现,对所有符合要求的wifidisplay设备创建Parcelable对象 } }); }
类似与handleScanStarted()函数,这里结束设备发现任务并且完成相应处理工作的函数handleScanFinished(),也开启监听线程。这些监听线程将在WifiDisplayAdapter被注册使用,
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
private final WifiDisplayController.Listener mWifiDisplayListener = new WifiDisplayController.Listener() { @Override public void onFeatureStateChanged(int featureState) { synchronized (getSyncRoot()) { if (mFeatureState != featureState) { mFeatureState = featureState; scheduleStatusChangedBroadcastLocked(); } } } @Override public void onScanStarted() { synchronized (getSyncRoot()) { if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) { mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING; scheduleStatusChangedBroadcastLocked(); } } } @Override public void onScanFinished(WifiDisplay[] availableDisplays) { synchronized (getSyncRoot()) { availableDisplays = mPersistentDataStore.applyWifiDisplayAliases( availableDisplays); if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || !Arrays.equals(mAvailableDisplays, availableDisplays)) { mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; mAvailableDisplays = availableDisplays; scheduleStatusChangedBroadcastLocked(); } } } ... };
可以看到,这些监听接口函数在触发时,都会调用同一个函数scheduleStatusChangedBroadcastLocked(),
private final WifiDisplayHandler mHandler; private void scheduleStatusChangedBroadcastLocked() { mCurrentStatus = null; if (!mPendingStatusChangeBroadcast) { mPendingStatusChangeBroadcast = true; mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST); } } private final class WifiDisplayHandler extends Handler { public WifiDisplayHandler(Looper looper) { super(looper, null, true /*async*/); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SEND_STATUS_CHANGE_BROADCAST: handleSendStatusChangeBroadcast(); break; ... } } } }
函数scheduleStatusChangedBroadcastLocked()会向内类注册的Handler处理函数发送MSG_SEND_STATUS_CHANGE_BROADCAST消息,处理函数接收到该消息后由handleSendStatusChangeBroadcast()向设备上所有注册过ACTION_WIFI_DISPLAY_STATUS_CHANGED这一intent的接受者发送WifiDisplayStatus广播,
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); }
最后,我们来看看对于Wifi Display 设备发现最后需要注意的一个部分,即在WifidisplayController中调用的WifiP2pManager中的discoverPeers()接口函数,
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
public void discoverPeers(Channel c, ActionListener listener) { checkChannel(c); c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener)); }
当用户在搜寻设备时,该函数会向Channel中发送DISCOVER_PEERS信号,并注册监听器监听响应结果。Channel的初始化在WifiDisplayController的构造函数中由函数Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener){}完成,该函数将P2phandler连接到P2p处理函数框架中。当设备进入P2pEnabledState状态中,并且处理函数接受到DISCOVER_PEERS信号后,真正调用WifiNative的接口函数p2pFind(),并且执行wifi_command()函数调用Wifi设备底层的命令,
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pService.java
clearSupplicantServiceRequest(); if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED); sendP2pDiscoveryChangedBroadcast(true); } else { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.ERROR); }
如果p2pFind(int timeout)调用doBooleanCommand("P2P_FIND " + timeout)并且执行成功,则向先前连接的P2pHandler处理函数回复DISCOVER_PEERS_SUCCEEDED信号,并且调用监听函数回调接口((ActionListener) listener).onSuccess(),回调WifidisplayController中的discoverPeers()函数做发现设备成功后的获得设备列表工作即执行函数requestPeers()。最后,还会给在boot之前注册的接收者发送WIFI_P2P_DISCOVERY_CHANGED_ACTION广播。
至此,本文已经基本讲清楚了Wifi Display在设备发现时的基本调用流程,关于连接和开始传送数据流等内容将会再下一回的内容讨论,谢谢关注!