全部內容都是本身的分析,如今是簡單羅列代碼位置及總體結構,細節的東西會慢慢充實,歡迎討論糾正,我會及時更改。html
1、簡單背景
java
簡單背景:隨着無線互聯的深刻,不論是藍牙、WIFI或者各類基於此的規範不論是UPNP仍是DLNA都隨着用戶的需求獲得了很大的發展,google 自從android 4.0引入wifi direct後,又在11月份公佈的android 4.2中引入了Miracast無線顯示共享,其協議在此能夠下載。具體的協議部份內容比較多,本人因爲水平有限,就不在這裏羅列協議的內容了,只附上一份架構圖供你們對其有個大體的印象。android
英文縮寫對應以下:
git
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 Setupapi
2、應用層簡介
數組
好了,接下來首先來看一看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 設備鏈接歷史信息的方法。
3、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端的交互將在下回介紹。