所有内容都是自己的分析,现在是简单罗列代码位置及整体结构,细节的东西会慢慢充实,欢迎讨论纠正,我会及时更改。
一、简单背景
简单背景:随着无线互联的深入,不管是蓝牙、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应用而言,
[java] view plaincopy
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)类构造函数参数传入,这样自己重构的演示就会出现在第二个显示设备上。
[java] view plaincopy
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;
}
}
...
[java] view plaincopy
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()函数。
[java] view plaincopy
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应用中,
[java] view plaincopy
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);
这里可以简单看看调用流程,首先可以看到onRoutePresentationDisplayChanged()回调函数在MediaRouter.java会先触发dispatchRoutePresentationDisplayChanged()函数,
frameworks/base/media/java/android/media/MediaRouter.java
[java] view plaincopy
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
[java] view plaincopy
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);
}
}
}
[java] view plaincopy
@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
[java] view plaincopy
@Override
protected void onStart() {
super.onStart();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);// Presentation线程一启动就会注册Display Manager中负责监听演示设备变化的三个监听器
...
}
[java] view plaincopy
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
[java] view plaincopy
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(); //实际负责注册回调函数的方法
}
}
}
[java] view plaincopy
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接口实现,
[java] view plaincopy
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 [java] view plaincopy
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
[java] view plaincopy
@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);
}
}
可以看到该服务采取同步机制,这是因为display manager可能同时被多个线程访问,这里所有属于display manager的对象都会使用同一把锁。本服务将该锁称为同步root,其有唯一的类型DisplayManagerService.SyncRoot, 所有需要该锁的方法都会以“Locked"作为后缀。另外,CallbackRecord函数会绑定IDisplayManagerCallback接 口。并且通过函数notifyDisplayEventAsync( )向回调函数提供显示事件通知,事件类型分为三类EVENT_DISPLAY_ADDED,EVENT_DISPLAY_CHANGED以及 EVENT_DISPLAY_REMOVED等。
函数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。
[java] view plaincopy
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功能已经开启,那么就开始进行设备发现
[java] view plaincopy
if (mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
mDisplayManager.scanWifiDisplays();
}
在搜寻完设备后,用户可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
[java] view plaincopy
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
[java] view plaincopy
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
[java] view plaincopy
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
[java] view plaincopy
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
[java] view plaincopy
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
[java] view plaincopy
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而言,流程大致如下图所示,
接下来让我们接着之前的流程继续分析WifiDisplayAdapter中的发现设备额的调用流程
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
[java] view plaincopy
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
[java] view plaincopy
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()完成
[java] view plaincopy
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
[java] view plaincopy
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(),
[java] view plaincopy
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广播,
[java] view plaincopy
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
[java] view plaincopy
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
[java] view plaincopy
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广播。
首先,回顾下应用层,当用户在搜寻完设备后,可以选择设备进行连接,当然正在进行连接或已经连接配对的设备,再次点击配置后,会弹出对话框供用户选择断开连接。
packages/apps/Settings/src/com/android/settings/wfd/WifiDisplaySettings.java
[java] view plaincopy
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);
}
如 同设备发现的调用流程,当用户选择设备进行连接后,程序会调用DisplayManager的connectWifiDisplay()函数接口。该函数 会进一步根据DisplayManagerGlobal提供的单实例对象调用AIDL提供的接口函数connectWifiDisplay(),这又是上 一回已经提到过的调用模式。其实际的调用实现是Displaymanager service中提供的connectWifiDisplay()函数,
frameworks/base/services/java/com/android/server/display/DisplayManagerService.java
[java] view plaincopy
public void connectWifiDisplay(String address) {
if (address == null) {
throw new IllegalArgumentException("address must not be null");
}
final boolean trusted = canCallerConfigureWifiDisplay();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
if (mWifiDisplayAdapter != null) {
mWifiDisplayAdapter.requestConnectLocked(address, trusted);
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
到此,我们容易发现连接WifiDisplay设备的函数调用流程与发现设备的流程一致,这里将不做多余解释(详见),在此会罗列出之后的基本流程。
frameworks/base/services/java/com/android/server/display/WifiDisplayAdapter.java
[java] view plaincopy
public void requestConnectLocked(final String address, final boolean trusted) {
if (DEBUG) {
Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
}
if (!trusted) {
synchronized (getSyncRoot()) {
if (!isRememberedDisplayLocked(address)) { //如果设备地址不在保存列表中则忽略不做处理
...
return;
}
}
}
getHandler().post(new Runnable() {
@Override
public void run() {
if (mDisplayController != null) {
mDisplayController.requestConnect(address);
}
}
});
}
frameworks/base/services/java/com/android/server/display/WifiDisplayController.java
[java] view plaincopy
public void requestConnect(String address) {
for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
if (device.deviceAddress.equals(address)) {
connect(device);
}
}
}
private void connect(final WifiP2pDevice device) {
if (mDesiredDevice != null
&& !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { //如果设备已经正在连接则返回
if (DEBUG) {
...
}
return;
}
if (mConnectedDevice != null
&& !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
&& mDesiredDevice == null) {//如果设备已经连接则返回
if (DEBUG) {
...
}
return;
}
mDesiredDevice = device;
mConnectionRetriesLeft = CONNECT_MAX_RETRIES; //尝试连接最大次数
updateConnection();
}
接下来,我们将重点看一看updateConnection()函数,此函数是建立Wifidisplay连接,监听RTSP连接的核心实现函数。
[java] view plaincopy
private void updateConnection() {
//在尝试连接到新设备时,需要通知系统这里已经与旧的设备断开连接
if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
...
mRemoteDisplay.dispose(); //释放NativeRemoteDisplay资源停止监听
mRemoteDisplay = null; //监听返回对象置为空
mRemoteDisplayInterface = null; //监听端口置为空
mRemoteDisplayConnected = false; //连接标识为未连接
mHandler.removeCallbacks(mRtspTimeout);//将挂起的mRtspTimeout线程从消息队列中移除
setRemoteSubmixOn(false); //关闭远程混音重建模式
unadvertiseDisplay();
}
if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
...
unadvertiseDisplay();
final WifiP2pDevice oldDevice = mConnectedDevice;
mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
@Override
public void onSuccess() {
...
next();
}
@Override
public void onFailure(int reason) {
...
next();
}
private void next() {
if (mConnectedDevice == oldDevice) { //确保连接设备已经不是旧的设备否则递归调用该函数
mConnectedDevice = null;
updateConnection();
}
}
});
return;
}
if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
...
unadvertiseDisplay();
mHandler.removeCallbacks(mConnectionTimeout);
final WifiP2pDevice oldDevice = mConnectingDevice;
mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { //在尝试连接到新设备之前,取消正在进行的p2p连接
@Override
public void onSuccess() {
...
next();
}
@Override
public void onFailure(int reason) {
...
next();
}
private void next() {
if (mConnectingDevice == oldDevice) {
mConnectingDevice = null;
updateConnection();
}
}
});
return;
}
// 如果想断开连接,则任务结束
if (mDesiredDevice == null) {
unadvertiseDisplay();
return;
}
if (mConnectedDevice == null && mConnectingDevice == null) {
Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
mConnectingDevice = mDesiredDevice;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = mConnectingDevice.deviceAddress;
config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
WifiDisplay display = createWifiDisplay(mConnectingDevice);
advertiseDisplay(display, null, 0, 0, 0);
final WifiP2pDevice newDevice = mDesiredDevice;
mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
//以特定的配置信息开启P2P连接,如果当前设备不是P2P组的一部分,会建立P2P小组并发起连接请求;如果当前设备是现存P2P组的一部分,则加入该组的邀请会发送至该配对设备。
@Override
public void onSuccess() {
//为了防止连接还没有建立成功,这里设定了等待处理函数,如果在定长时间内还没有接受到WIFI_P2P_CONNECTION_CHANGED_ACTION广播,则按照handleConnectionFailure(true)处理。
Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
}
@Override
public void onFailure(int reason) {
if (mConnectingDevice == newDevice) {
Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+ newDevice.deviceName + ", reason=" + reason);
mConnectingDevice = null;
handleConnectionFailure(false);
}
}
});
return;
}
// 根据连接的网络地址和端口号监听Rtsp流连接
if (mConnectedDevice != null && mRemoteDisplay == null) {
Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
if (addr == null) {
Slog.i(TAG, "Failed to get local interface address for communicating "
+ "with Wifi display: " + mConnectedDevice.deviceName);
handleConnectionFailure(false);
return; // done
}
setRemoteSubmixOn(true);
final WifiP2pDevice oldDevice = mConnectedDevice;
final int port = getPortNumber(mConnectedDevice);
final String iface = addr.getHostAddress() + ":" + port;
mRemoteDisplayInterface = iface;
Slog.i(TAG, "Listening for RTSP connection on " + iface
+ " from Wifi display: " + mConnectedDevice.deviceName);
mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
//开始监听连接上的接口
@Override
public void onDisplayConnected(Surface surface,
int width, int height, int flags) {
if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
Slog.i(TAG, "Opened RTSP connection with Wifi display: "
+ mConnectedDevice.deviceName);
mRemoteDisplayConnected = true;
mHandler.removeCallbacks(mRtspTimeout);
final WifiDisplay display = createWifiDisplay(mConnectedDevice);
advertiseDisplay(display, surface, width, height, flags);
}
}
@Override
public void onDisplayDisconnected() {
if (mConnectedDevice == oldDevice) {
Slog.i(TAG, "Closed RTSP connection with Wifi display: "
+ mConnectedDevice.deviceName);
mHandler.removeCallbacks(mRtspTimeout);
disconnect();
}
}
@Override
public void onDisplayError(int error) {
if (mConnectedDevice == oldDevice) {
Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
+ error + ": " + mConnectedDevice.deviceName);
mHandler.removeCallbacks(mRtspTimeout);
handleConnectionFailure(false);
}
}
}, mHandler);
mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
}
}
至此,我们已经了解了建立WifiDisplay连接的基本流程,当然可以继续向底层深入,只要用户选择尝试连接并且已经确认处于连接断开的状态,则会调 用WifiP2pManager中的connect()接口函数,该函数会向Channel中发送CONNECT信号,并注册监听器监听相应结果。在进入 P2pStateMachine状态机后,WifiP2pService会分为两种情况进行处理。如果当前的设备不是P2P组的成 员,WifiP2pService会调用WifiNative类中的p2pConnect()函数,该函数会继续向底层调用,最终会调用 wifi.cwifi_send_command()命令,把groupnegotiation请求发送至wpa_supplicant供其处理;如果这 个设备已经是P2P组的成员,或者自己通过WifiNative类中的p2pGroupAdd()函数创建了一个组,那么会进入 GroupCreatedState,进一步会调用WifiNative类中的p2pInvite()函数向设备发送邀请请求。具体的有关 wpa_supplicant同底层驱动的交互,以及wpa_supplicant同WifiMonitor与WifiP2pService状态机之间的 调用流程以后有机会再讨论。
在本文的最后,还想继续讨论一下监听RTSP连接的核心实现函数RemoteDisplay.listen(...),
frameworks/base/media/java/android/media/RemoteDisplay.java
[java] view plaincopy
public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
...
RemoteDisplay display = new RemoteDisplay(listener, handler);
display.startListening(iface);
return display;
}
可以看到该监听函数会调用以下函数,并把监听端口作为参数进行传递,
private void startListening(String iface) {
mPtr = nativeListen(iface);
if (mPtr == 0) {
throw new IllegalStateException("Could not start listening for "
+ "remote display connection on \"" + iface + "\"");
}
mGuard.open("dispose");
}
以上函数最终会调用JNI层的接口函数nativeListen()进行监听。至于CloseGuardmGuard.open(),不理解的话,我们就把它看作是Android提供的一种资源清理机制。
接下来,可以具体看一下RemoteDisplay在JNI层的接口实现,
frameworks/base/core/jni/android_media_RemoteDisplay.cpp
[java] view plaincopy
static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
ScopedUtfChars iface(env, ifaceStr); //通过智能指针的方式将string类型转化为只读的UTF chars类型
sp<IServiceManager> sm = defaultServiceManager();
sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
sm->getService(String16("media.player")));
//用service manager获得 media player服务的代理实例,即通过interface_cast将其转化成BpMediaPlayerService (Bridge模式)
if (service == NULL) {
ALOGE("Could not obtain IMediaPlayerService from service manager");
return 0;
}
sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));
sp<IRemoteDisplay> display = service->listenForRemoteDisplay(
client, String8(iface.c_str()));
//调用BpMediaPlayerService提供的接口函数,与服务端BnMediaPlayerService进行通讯
if (display == NULL) {
ALOGE("Media player service rejected request to listen for remote display '%s'.",
iface.c_str());
return 0;
}
NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);
return reinterpret_cast<jint>(wrapper);
}
这里采用了Binder通信机制,BpMediaPlayerService继承 BpInterface<IMediaPlayerService>作为代理端,采用Bridge模式调用 listenForRemoteDisplay()接口函数将上层的监听接口以及实例化的NativeRemoteDisplayClient代理对象传 递至服务端BnMediaPlayerService进行处理。
/frameworks/av/media/libmedia/IMediaPlayerService.cpp
[cpp] view plaincopy
class BpMediaPlayerService: public BpInterface<IMediaPlayerService>
{
public:
…
virtual sp<IRemoteDisplay> listenForRemoteDisplay(const sp<IRemoteDisplayClient>& client,
const String8& iface)
{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
data.writeStrongBinder(client->asBinder());
data.writeString8(iface);
remote()->transact(LISTEN_FOR_REMOTE_DISPLAY, data, &reply); //向服务端BnMediaPlayerService发送LISTEN_FOR_REMOTE_DISPLAY 处理命令
return interface_cast<IRemoteDisplay>(reply.readStrongBinder());
}
};
进 一步可以看到,NativeRemoteDisplayClient继承于BnRemoteDisplayClient,其实这是 IRemoteDisplayClient接口的服务端实现。该类提供了三个接口函数onDisplayConnected()、 onDisplayDisconnected()、onDisplayError()是frameworks/base/media/java /android/media/RemoteDisplay.java中RemoteDisplay.Listener{}的三个监听函数在JNI层的实 现,特别的,对于onDisplayConnected()函数而言,调用 android_view_Surface_createFromISurfaceTexture()函数创建surfaceObj并将其向 RemoteDisplay中注册的监听线程传递并进行回调。
frameworks/base/core/jni/android_media_RemoteDisplay.cpp
[cpp] view plaincopy
virtual void onDisplayConnected(const sp<ISurfaceTexture>& surfaceTexture,
uint32_t width, uint32_t height, uint32_t flags) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jobject surfaceObj = android_view_Surface_createFromISurfaceTexture(env, surfaceTexture);
//跟据当前获取的media server的surface texture来创建Surface对象
if (surfaceObj == NULL) {
...
return;
}
env->CallVoidMethod(mRemoteDisplayObjGlobal,
gRemoteDisplayClassInfo.notifyDisplayConnected,
surfaceObj, width, height, flags); //将Suface对象作为参数传递至notifyDisplayConnected函数用于监听函数的回调
env->DeleteLocalRef(surfaceObj);
checkAndClearExceptionFromCallback(env, "notifyDisplayConnected");
}
接下来,我们继续来看服务端BnMediaPlayerService的实现,其中onTransact函数用于接收来自 BpMediaPlayerService发送的命令,如果命令为LISTEN_FOR_REMOTE_DISPLAY,则会读取相应数据并作为参数进行 传递。这里的listenForRemoteDisplay()函数是纯虚函数,其实现是由派生类MediaPlayerService来完成的。
[cpp] view plaincopy
status_t BnMediaPlayerService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
…
case LISTEN_FOR_REMOTE_DISPLAY: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
sp<IRemoteDisplayClient> client(
interface_cast<IRemoteDisplayClient>(data.readStrongBinder()));
String8 iface(data.readString8());
sp<IRemoteDisplay> display(listenForRemoteDisplay(client, iface));//调用纯虚函数接口,运行时实际调用派生类MediaPlayerService的函数实现
reply->writeStrongBinder(display->asBinder());
return NO_ERROR;
} break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
最后,来看一看该函数的实际实现,
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
[cpp] view plaincopy
sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
const sp<IRemoteDisplayClient>& client, const String8& iface) {
if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
//检查是否有WIFI Display权限
return NULL;
}
return new RemoteDisplay(client, iface.string()); //直接调用 RemoteDisplay构造函数来开启Wifi display source端
}
其中,RemoteDisplay继承于BnRemoteDisplay,也采取了Binder通信机制,代理端BpRemoteDisplay与服务端 BnRemoteDisplay的接口实现详见frameworks/av/media/libmedia/IRemoteDisplay.cpp。这 里,值得一提的是,函数listenForRemoteDisplay()假设在同一时刻连接到指定网络端口iface的remotedisplay设备 最多只有一个。换句话说,在同一时刻只有一个设备能作为WifiDisplay source端设备进行播放。
最后,我们来看一看开启Wifidisplay source端的这个构造函数,
frameworks/av/media/libmediaplayerservice/RemoteDisplay.cpp
[cpp] view plaincopy
RemoteDisplay::RemoteDisplay(
const sp<IRemoteDisplayClient> &client, const char *iface)
: mLooper(new ALooper),
mNetSession(new ANetworkSession),
mSource(new WifiDisplaySource(mNetSession, client)) {
mLooper->setName("wfd_looper");
mLooper->registerHandler(mSource); //注册了Wifi display 处理线程
mNetSession->start(); //初始化数据管道,启动NetworkThread线程,进入threadLoop中监听数据流变化等待处理
mLooper->start(); //开启消息处理管理线程
mSource->start(iface); //将网络端口作为消息载体进行传递处理,并等待响应结果,完成与Wifi Display source端开启播放的相关工作
}
其中mLooper,mNetSession, mSource分别为sp<ALooper>mLooper,sp<ANetworkSession>mNetSession以 及sp<WifiDisplaySource>mSource等三个强指针,对强指针概念不清的请见此。 此处是利用构造函数的初始化列表将这三个强指针指向这三个new出来的对象。之后便是利用这三个指针,调用类中的方法以开启Wifidisplay source端进行播放。这里,ALooper是关于线程以及消息队列等待处理管理相关的一个类。ANetworkSessions是管理所有与数据报文 和数据流相关socket的一个单线程帮助类。在此处,该类负责管理与WifiDisplay播放相关的socket,其中相关的数据传递和消息返回通过 AMessage类对象和方法进行。WifiDisplaySource光看命名就知道,其主要负责WifiDisplaysource端的开启关闭,以 及与其相关的建立Rtsp服务器,管理所有支持的协议连接、数据流传递以及各个状态之间转换处理等内容。此外,该类还定义了关闭WifiDisplay source端,停止相关线程、关闭socket以及释放资源等内容。
至此,有关WifiDisplay设备连接和建立数据流的流程已经交代清楚了,可以看到应用层建立的连接是与source端相关的。Sink端的主程序在 frameworks/av/media/libstagefright/wifi-display/wfd.cpp中,与sink端实现相关的程序在 frameworks/av/media/libstagefright/wifi-display/sink目录下面。关于source如何建立 rtsp连接,开始通信,各个状态之间的转换以及与sink端的交互将在下回介绍。