Android 4.2 Wifi Display 之 Settings 源码分析

System Server是Android系统的核心,他在Dalvik虚拟机启动后立即开始初始化和运行

http://blog.csdn.net/sadamoo/article/details/27665149

最近在学习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方法:

[java] view plaincopy

  1. public DisplayManagerService(Context context, Handler mainHandler) {
  2. mContext = context;
  3. mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
  4. mHandler = new DisplayManagerHandler(mainHandler.getLooper());
  5. mUiHandler = UiThread.getHandler();
  6. mDisplayAdapterListener = new DisplayAdapterListener();
  7. mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
  8. mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
  9. }
  10. public void systemReady(boolean safeMode, boolean onlyCore) {
  11. synchronized (mSyncRoot) {
  12. mSafeMode = safeMode;
  13. mOnlyCore = onlyCore;
  14. }
  15. mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
  16. }

在DisplayManagerService的构造函数中,首先获取SYSTEM_HEADLESS属性,用于表明系统是否支持headless模式,默认为0。然后创建一个DisplayManagerHandler用于处理DisplayManagerService中的消息,mSigleDisplayDemoMode用于开发模式中。然后给自己发送MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER,我们到DisplayManagerHandler看如何处理这个消息:

[java] view plaincopy

  1. private final class DisplayManagerHandler extends Handler {
  2. public DisplayManagerHandler(Looper looper) {
  3. super(looper, null, true /*async*/);
  4. }
  5. @Override
  6. public void handleMessage(Message msg) {
  7. switch (msg.what) {
  8. case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER:
  9. registerDefaultDisplayAdapter();
  10. break;
  11. case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS:
  12. registerAdditionalDisplayAdapters();
  13. break;

处理MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER消息就是调用registerDefaultDisplayAdapter来注册一个默认的DiaplayAdapter,DisplayManagerService维护一组DiaplayAdapter,用于管理这些显示设备。默认的DiaplayAdapter就是系统的物理屏幕,通过Surface flinger来控制输出。

[java] view plaincopy

  1. private void registerDefaultDisplayAdapter() {
  2. // Register default display adapter.
  3. synchronized (mSyncRoot) {
  4. if (mHeadless) {
  5. registerDisplayAdapterLocked(new HeadlessDisplayAdapter(
  6. mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
  7. } else {
  8. registerDisplayAdapterLocked(new LocalDisplayAdapter(
  9. mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
  10. }
  11. }
  12. }
  13. private void registerDisplayAdapterLocked(DisplayAdapter adapter) {
  14. mDisplayAdapters.add(adapter);
  15. adapter.registerLocked();
  16. }

管理surface finger的知识就不讲解了。接着来看systemReady函数中会发送MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS,这里就会调用registerAdditionalDisplayAdapters来注册其它的显示设备:

[java] view plaincopy

  1. private void registerAdditionalDisplayAdapters() {
  2. synchronized (mSyncRoot) {
  3. if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
  4. registerOverlayDisplayAdapterLocked();
  5. registerWifiDisplayAdapterLocked();
  6. registerVirtualDisplayAdapterLocked();
  7. }
  8. }
  9. }

这里主要注册三种DisplayAdapter,一种是OverlayDiaplayAdapter用于开发模式用;一种是WifiDisplayAdapter用于wifi display,也是我们接下来要讲的;还有一种是虚拟显示。接下来只看registerWifiDisplayAdapterLocked:

[java] view plaincopy

  1. private void registerWifiDisplayAdapterLocked() {
  2. if (mContext.getResources().getBoolean(
  3. com.android.internal.R.bool.config_enableWifiDisplay)
  4. || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
  5. mWifiDisplayAdapter = new WifiDisplayAdapter(
  6. mSyncRoot, mContext, mHandler, mDisplayAdapterListener,
  7. mPersistentDataStore);
  8. registerDisplayAdapterLocked(mWifiDisplayAdapter);
  9. }
  10. }

这里会创建WifiDisplayAdapter对象,我们到它的构造函数中去分析,并调用registerDisplayAdapterLocked添加到mDisplayAdapter中,这里会回调WifiDisplayAdapter的registerLocked方法:

[java] view plaincopy

  1. public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
  2. Context context, Handler handler, Listener listener,
  3. PersistentDataStore persistentDataStore) {
  4. super(syncRoot, context, handler, listener, TAG);
  5. mHandler = new WifiDisplayHandler(handler.getLooper());
  6. mPersistentDataStore = persistentDataStore;
  7. mSupportsProtectedBuffers = context.getResources().getBoolean(
  8. com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);
  9. mNotificationManager = (NotificationManager)context.getSystemService(
  10. Context.NOTIFICATION_SERVICE);
  11. }
  12. public void registerLocked() {
  13. super.registerLocked();
  14. updateRememberedDisplaysLocked();
  15. getHandler().post(new Runnable() {
  16. @Override
  17. public void run() {
  18. mDisplayController = new WifiDisplayController(
  19. getContext(), getHandler(), mWifiDisplayListener);
  20. getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
  21. new IntentFilter(ACTION_DISCONNECT), null, mHandler);
  22. }
  23. });
  24. }

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连接/断开等。

[java] view plaincopy

  1. public WifiDisplayController(Context context, Handler handler, Listener listener) {
  2. mContext = context;
  3. mHandler = handler;
  4. mListener = listener;
  5. mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
  6. mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
  7. IntentFilter intentFilter = new IntentFilter();
  8. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
  9. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
  10. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
  11. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
  12. context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
  13. ContentObserver settingsObserver = new ContentObserver(mHandler) {
  14. @Override
  15. public void onChange(boolean selfChange, Uri uri) {
  16. updateSettings();
  17. }
  18. };
  19. final ContentResolver resolver = mContext.getContentResolver();
  20. resolver.registerContentObserver(Settings.Global.getUriFor(
  21. Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
  22. resolver.registerContentObserver(Settings.Global.getUriFor(
  23. Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver);
  24. resolver.registerContentObserver(Settings.Global.getUriFor(
  25. Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, settingsObserver);
  26. updateSettings();
  27. }

这里主要注册WifiP2pReceiver用于接收处理WIFI_P2P_STATE_CHANGED_ACTION、WIFI_P2P_PEERS_CHANGED_ACTION、WIFI_P2P_CONNECTION_CHANGED_ACTION、WIFI_P2P_THIS_DEVICE_CHANGED_ACTION消息,然后注册ContentObserver来监控Settings.Global这个数据库里面的WIFI_DISPLAY_ON、WIFI_DISPLAY_CERTIFICATION_ON和WIFI_DISPLAY_WPS_CONFIG,这里比较重要,我们后面会看到在WifiDisplaySettings里面enable wifi display的时候,就会走到这个地方来。接着调用updateSettings来处理默认是否打开Wifi display,这里默认是关闭的,我们后面再来分析这一块。

接着来看MediaRouterService和MediaRouter,MediaRouter通过AIDL调用MediaRouterService的实现来完成一些工作。在SystemServer启动MediaRouterService的时候,主要创建一个MediaRouterService,然后调用它的systemRunning方法,代码如下:

[java] view plaincopy

  1. public MediaRouterService(Context context) {
  2. mContext = context;
  3. Watchdog.getInstance().addMonitor(this);
  4. }
  5. public void systemRunning() {
  6. IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
  7. mContext.registerReceiver(new BroadcastReceiver() {
  8. @Override
  9. public void onReceive(Context context, Intent intent) {
  10. if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
  11. switchUser();
  12. }
  13. }
  14. }, filter);
  15. switchUser();
  16. }

上面的方法比较简单,主要就是接收ACTION_USER_SWITCHED,这是关于多用户切换的操作。MediaRouterService的工作比较少,主要都是MediaRouter通过AIDL调用完成,接下来去看MediaRouter的部分,在Android官方文档中有说明MediaRouter的调用方法:

A MediaRouter is retrieved through Context.getSystemService() of a Context.MEDIA_ROUTER_SERVICE. 这样系统是实例化一个MediaRouter对象并返回,下面来看它的构造函数:

[java] view plaincopy

  1. public MediaRouter(Context context) {
  2. synchronized (Static.class) {
  3. if (sStatic == null) {
  4. final Context appContext = context.getApplicationContext();
  5. sStatic = new Static(appContext);
  6. sStatic.startMonitoringRoutes(appContext);
  7. }
  8. }
  9. }
  10. Static(Context appContext) {
  11. mAppContext = appContext;
  12. mResources = Resources.getSystem();
  13. mHandler = new Handler(appContext.getMainLooper());
  14. IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
  15. mAudioService = IAudioService.Stub.asInterface(b);
  16. mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
  17. mMediaRouterService = IMediaRouterService.Stub.asInterface(
  18. ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
  19. mSystemCategory = new RouteCategory(
  20. com.android.internal.R.string.default_audio_route_category_name,
  21. ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
  22. mSystemCategory.mIsSystem = true;
  23. mCanConfigureWifiDisplays = appContext.checkPermission(
  24. Manifest.permission.CONFIGURE_WIFI_DISPLAY,
  25. Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
  26. }

MediaRouter中主要通过Static对象来实现其大多数的方法,Static就是一个单例模式,先看Static的构造函数,也可以通过上面的图看到,MediaRouter包含DisplayManager对象和MediaRouterService的BpBinder引用,MediaRouter还持有AudioService的BpBind,用于控制audio数据的输出设备,例如可以用于蓝牙A2DP中使用。接着看Static的startMonitoringRoutes方法:

[java] view plaincopy

  1. void startMonitoringRoutes(Context appContext) {
  2. mDefaultAudioVideo = new RouteInfo(mSystemCategory);
  3. mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
  4. mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
  5. mDefaultAudioVideo.updatePresentationDisplay();
  6. addRouteStatic(mDefaultAudioVideo);
  7. // This will select the active wifi display route if there is one.
  8. updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
  9. appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
  10. new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
  11. appContext.registerReceiver(new VolumeChangeReceiver(),
  12. new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
  13. mDisplayService.registerDisplayListener(this, mHandler);
  14. // Bind to the media router service.
  15. rebindAsUser(UserHandle.myUserId());
  16. // Select the default route if the above didn‘t sync us up
  17. // appropriately with relevant system state.
  18. if (mSelectedRoute == null) {
  19. selectDefaultRouteStatic();
  20. }
  21. }

首先注册系统中默认的AudioVideo输出设备,如果有处于活动状态的wifi display连接,就记录下当前处于活动连接的设备,默认为空。上面会注册两个broadcastReceiver,一个用于接收ACTION_WIFI_DISPLAY_STATUS_CHANGED,另一个接收VOLUME_CHANGED_ACTION,我们主要看前面接收ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,如下:

[java] view plaincopy

  1. static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
  2. @Override
  3. public void onReceive(Context context, Intent intent) {
  4. if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
  5. updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
  6. DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
  7. }
  8. }

上面接收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:

[java] view plaincopy

  1. void rebindAsUser(int userId) {
  2. if (mCurrentUserId != userId || userId < 0 || mClient == null) {
  3. mCurrentUserId = userId;
  4. try {
  5. Client client = new Client();
  6. mMediaRouterService.registerClientAsUser(client,
  7. mAppContext.getPackageName(), userId);
  8. mClient = client;
  9. } catch (RemoteException ex) {
  10. Log.e(TAG, "Unable to register media router client.", ex);
  11. }
  12. publishClientDiscoveryRequest();
  13. publishClientSelectedRoute(false);
  14. updateClientState();
  15. }
  16. }

Enable WifiDisplay

当用户进入WifiDisplaySettings界面,会调用其对应的onCreate和onStart方法:

[java] view plaincopy

  1. public void onCreate(Bundle icicle) {
  2. super.onCreate(icicle);
  3. final Context context = getActivity();
  4. mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
  5. mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
  6. mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
  7. mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null);
  8. addPreferencesFromResource(R.xml.wifi_display_settings);
  9. setHasOptionsMenu(true);
  10. }
  11. public void onStart() {
  12. super.onStart();
  13. mStarted = true;
  14. final Context context = getActivity();
  15. IntentFilter filter = new IntentFilter();
  16. filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
  17. context.registerReceiver(mReceiver, filter);
  18. getContentResolver().registerContentObserver(Settings.Global.getUriFor(
  19. Settings.Global.WIFI_DISPLAY_ON), false, mSettingsObserver);
  20. getContentResolver().registerContentObserver(Settings.Global.getUriFor(
  21. Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, mSettingsObserver);
  22. getContentResolver().registerContentObserver(Settings.Global.getUriFor(
  23. Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, mSettingsObserver);
  24. mRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mRouterCallback,
  25. MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
  26. update(CHANGE_ALL);
  27. }

首先注册对ACTION_WIFI_DISPLAY_STATUS_CHANGED的receiver,这个broadcast会在WifiDisplayAdapter里面当wifi display的状态发送改变时发送,包括扫描到新的设备、开始连接、连接成功、断开等消息都会被这个receiver接收到,后面我们会来分析这个receiver干了什么,然后在onStart中想MediaRouter对象注册一个callback函数,用于获取系统中remote display的相关回调信息。然后类似WifiDisplayController一样,注册一些对数据库改变的ContentObserver。接着来看MediaRouter.addCallback的实现:

[java] view plaincopy

  1. public void addCallback(int types, Callback cb, int flags) {
  2. CallbackInfo info;
  3. int index = findCallbackInfo(cb);
  4. if (index >= 0) {
  5. info = sStatic.mCallbacks.get(index);
  6. info.type |= types;
  7. info.flags |= flags;
  8. } else {
  9. info = new CallbackInfo(cb, types, flags, this);
  10. sStatic.mCallbacks.add(info);
  11. }
  12. sStatic.updateDiscoveryRequest();
  13. }

Static的mCallbacks是一个CopyOnWriteArrayList数组,记录所有注册到MediaRouter中的回调函数。如果已经向MediaRouter注册过这个callback,则更新相关的type和flag;如果没有注册,则新建一个CallbackInfo对象并添加到mCallbacks数组中。然后调用Static的updateDiscoveryRequest去更新是否需要发送Discovery request请求:

[java] view plaincopy

  1. void updateDiscoveryRequest() {
  2. final int count = mCallbacks.size();
  3. for (int i = 0; i < count; i++) {
  4. CallbackInfo cbi = mCallbacks.get(i);
  5. if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
  6. | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {
  7. // Discovery explicitly requested.
  8. routeTypes |= cbi.type;
  9. } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {
  10. // Discovery only passively requested.
  11. passiveRouteTypes |= cbi.type;
  12. } else {
  13. // Legacy case since applications don‘t specify the discovery flag.
  14. // Unfortunately we just have to assume they always need discovery
  15. // whenever they have a callback registered.
  16. routeTypes |= cbi.type;
  17. }
  18. if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
  19. activeScan = true;
  20. if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
  21. activeScanWifiDisplay = true;
  22. }
  23. }
  24. }
  25. if (routeTypes != 0 || activeScan) {
  26. // If someone else requests discovery then enable the passive listeners.
  27. // This is used by the MediaRouteButton and MediaRouteActionProvider since
  28. // they don‘t receive lifecycle callbacks from the Activity.
  29. routeTypes |= passiveRouteTypes;
  30. }
  31. // Update wifi display scanning.
  32. // TODO: All of this should be managed by the media router service.
  33. if (mCanConfigureWifiDisplays) {
  34. if (mSelectedRoute != null
  35. && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {
  36. // Don‘t scan while already connected to a remote display since
  37. // it may interfere with the ongoing transmission.
  38. activeScanWifiDisplay = false;
  39. }
  40. if (activeScanWifiDisplay) {
  41. if (!mActivelyScanningWifiDisplays) {
  42. mActivelyScanningWifiDisplays = true;
  43. mDisplayService.startWifiDisplayScan();
  44. }
  45. } else {
  46. if (mActivelyScanningWifiDisplays) {
  47. mActivelyScanningWifiDisplays = false;
  48. mDisplayService.stopWifiDisplayScan();
  49. }
  50. }
  51. }
  52. }

这个函数体比较长,主要通过注册的一系列的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中去分析:

[java] view plaincopy

  1. private void updateScanState() {
  2. if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
  3. if (!mDiscoverPeersInProgress) {
  4. Slog.i(TAG, "Starting Wifi display scan.");
  5. mDiscoverPeersInProgress = true;
  6. handleScanStarted();
  7. tryDiscoverPeers();
  8. }
  9. } else {
  10. if (mDiscoverPeersInProgress) {
  11. // Cancel automatic retry right away.
  12. mHandler.removeCallbacks(mDiscoverPeers);
  13. if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
  14. Slog.i(TAG, "Stopping Wifi display scan.");
  15. mDiscoverPeersInProgress = false;
  16. stopPeerDiscovery();
  17. handleScanFinished();
  18. }
  19. }
  20. }
  21. }

当初次进入到WifiDisplaySettings中,并没有去optionMenu中enable wifi display时,上面code中的mWfdEnabled为false,所以会跳出前面的if语句;后面的else语句中mDiscoverPeersInProgress也为false,因为这个变量只有在scan时才会被置为true。

接着来分析当用户点击了optionMenu中enable wifi display后的流程,先看WifiDisplaySettings的代码:

[java] view plaincopy

  1. public boolean onOptionsItemSelected(MenuItem item) {
  2. switch (item.getItemId()) {
  3. case MENU_ID_ENABLE_WIFI_DISPLAY:
  4. mWifiDisplayOnSetting = !item.isChecked();
  5. item.setChecked(mWifiDisplayOnSetting);
  6. Settings.Global.putInt(getContentResolver(),
  7. 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的代码:

[java] view plaincopy

  1. private void updateSettings() {
  2. final ContentResolver resolver = mContext.getContentResolver();
  3. mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
  4. Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
  5. mWifiDisplayCertMode = Settings.Global.getInt(resolver,
  6. Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
  7. mWifiDisplayWpsConfig = WpsInfo.INVALID;
  8. if (mWifiDisplayCertMode) {
  9. mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,
  10. Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
  11. }
  12. updateWfdEnableState();
  13. }

这里主要置mWifiDisplayOnSetting为true,然后就调用updateWfdEnableState去更新wfd的状态:

[java] view plaincopy

  1. private void updateWfdEnableState() {
  2. if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
  3. // WFD should be enabled.
  4. if (!mWfdEnabled && !mWfdEnabling) {
  5. mWfdEnabling = true;
  6. WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
  7. wfdInfo.setWfdEnabled(true);
  8. wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
  9. wfdInfo.setSessionAvailable(true);
  10. wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
  11. wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
  12. mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
  13. @Override
  14. public void onSuccess() {
  15. if (DEBUG) {
  16. Slog.d(TAG, "Successfully set WFD info.");
  17. }
  18. if (mWfdEnabling) {
  19. mWfdEnabling = false;
  20. mWfdEnabled = true;
  21. reportFeatureState();
  22. updateScanState();
  23. }
  24. }
  25. @Override
  26. public void onFailure(int reason) {
  27. if (DEBUG) {
  28. Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
  29. }
  30. mWfdEnabling = false;
  31. }
  32. });
  33. }

首先调用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的实现:

[java] view plaincopy

  1. private void reportFeatureState() {
  2. final int featureState = computeFeatureState();
  3. mHandler.post(new Runnable() {
  4. @Override
  5. public void run() {
  6. mListener.onFeatureStateChanged(featureState);
  7. }
  8. });
  9. }
  10. private int computeFeatureState() {
  11. if (!mWifiP2pEnabled) {
  12. return WifiDisplayStatus.FEATURE_STATE_DISABLED;
  13. }
  14. return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
  15. WifiDisplayStatus.FEATURE_STATE_OFF;
  16. }

直接回调WifiDisplayListener的onFeatureStateChanged,从上面的图我们可以看着WifiDisplayListener会由WifiDisplayAdapter注册的,去看这部分的实现:

[java] view plaincopy

  1. public void onFeatureStateChanged(int featureState) {
  2. synchronized (getSyncRoot()) {
  3. if (mFeatureState != featureState) {
  4. mFeatureState = featureState;
  5. scheduleStatusChangedBroadcastLocked();
  6. }
  7. }
  8. }
  9. private void scheduleStatusChangedBroadcastLocked() {
  10. mCurrentStatus = null;
  11. if (!mPendingStatusChangeBroadcast) {
  12. mPendingStatusChangeBroadcast = true;
  13. mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
  14. }
  15. }

这里最后通过WifiDisplayHandler的sendEmptyMessage的方法实现,目的是不要卡住了WifiDisplayController后面代码的执行,来看WifiDisplayHandler如何处理MSG_SEND_STATUS_CHANGE_BROADCAST:

[java] view plaincopy

  1. public void handleMessage(Message msg) {
  2. switch (msg.what) {
  3. case MSG_SEND_STATUS_CHANGE_BROADCAST:
  4. handleSendStatusChangeBroadcast();
  5. break;
  6. case MSG_UPDATE_NOTIFICATION:
  7. handleUpdateNotification();
  8. break;
  9. }
  10. ate void handleSendStatusChangeBroadcast() {
  11. final Intent intent;
  12. synchronized (getSyncRoot()) {
  13. if (!mPendingStatusChangeBroadcast) {
  14. return;
  15. }
  16. mPendingStatusChangeBroadcast = false;
  17. intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
  18. intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
  19. intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
  20. getWifiDisplayStatusLocked());
  21. }
  22. // Send protected broadcast about wifi display status to registered receivers.
  23. 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设备:

[java] view plaincopy

  1. private void updateScanState() {
  2. if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
  3. if (!mDiscoverPeersInProgress) {
  4. Slog.i(TAG, "Starting Wifi display scan.");
  5. mDiscoverPeersInProgress = true;
  6. handleScanStarted();
  7. tryDiscoverPeers();
  8. }
  9. }

handleScanStarted用于通知WifiDisplayAdapter扫描开始了,当然WifiDisplayAdapter也会发broadcast给MediaRouter。接着会调用tryDiscoverPeers:

[java] view plaincopy

  1. private void tryDiscoverPeers() {
  2. mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
  3. @Override
  4. public void onSuccess() {
  5. if (DEBUG) {
  6. Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
  7. }
  8. if (mDiscoverPeersInProgress) {
  9. requestPeers();
  10. }
  11. }
  12. mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
  13. }

这里调用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去获取所有扫描到的设备列表,下面来看这个函数的实现:

[java] view plaincopy

  1. private void requestPeers() {
  2. mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
  3. @Override
  4. public void onPeersAvailable(WifiP2pDeviceList peers) {
  5. if (DEBUG) {
  6. Slog.d(TAG, "Received list of peers.");
  7. }
  8. mAvailableWifiDisplayPeers.clear();
  9. for (WifiP2pDevice device : peers.getDeviceList()) {
  10. if (DEBUG) {
  11. Slog.d(TAG, "  " + describeWifiP2pDevice(device));
  12. }
  13. if (isWifiDisplay(device)) {
  14. mAvailableWifiDisplayPeers.add(device);
  15. }
  16. }
  17. if (mDiscoverPeersInProgress) {
  18. handleScanResults();
  19. }
  20. }
  21. });
  22. }

首先从扫描的设备列表中过滤掉不能做wifi display的设备,主要从三个方面过滤,一是纯粹的P2P设备,不会待用WfdInfo;第二是带有WfdInfo,但是暂时没有被enable;三是只能是PrimarySinkDevice,看起来Android还不支持SecondSink。并将过滤掉剩下的设备加入到mAvailableWifiDisplayPeers列表中,接着调用handleScanResults来组装WifiDisplay列表数组并notify给WifiDisplayAdapter:

[java] view plaincopy

  1. private void handleScanResults() {
  2. final int count = mAvailableWifiDisplayPeers.size();
  3. final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
  4. for (int i = 0; i < count; i++) {
  5. WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
  6. displays[i] = createWifiDisplay(device);
  7. updateDesiredDevice(device);
  8. }
  9. mHandler.post(new Runnable() {
  10. @Override
  11. public void run() {
  12. mListener.onScanResults(displays);
  13. }
  14. });
  15. }

这里首先根据mAvailableWifiDisplayPeers的数目创建一个WifiDisplay数组,然后一个个构造WifiDisplay对象,WifiDiplay对象包含以下几个变量:

mDeviceAddress 设备的Mac地址
mDeviceName 设备的名字
mDeviceAlias 设备的别名,一般为NULL
mIsAvailable 是否可用状态
mCanConnect WfdInfo中的SessionAvailable是否为1
mIsRemembered 是否被记录的

接着调用updateDesiredDevice用于判断扫描到的这个设备是否是现在正在连接或者连接上的设备,如果是,则更新它的一些信息,以后在连接Wifi display的时候再来分析这一块。接着就会向WifiDisplayAdapter回调onScanResults,回调函数中带有已经扫描到的wifi display设备列表(如果有):

[java] view plaincopy

  1. public void onScanResults(WifiDisplay[] availableDisplays) {
  2. synchronized (getSyncRoot()) {
  3. availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
  4. availableDisplays);
  5. boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
  6. // Check whether any of the available displays changed canConnect status.
  7. for (int i = 0; !changed && i<availableDisplays.length; i++) {
  8. changed = availableDisplays[i].canConnect()
  9. != mAvailableDisplays[i].canConnect();
  10. }
  11. if (changed) {
  12. mAvailableDisplays = availableDisplays;
  13. fixRememberedDisplayNamesFromAvailableDisplaysLocked();
  14. updateDisplaysLocked();
  15. scheduleStatusChangedBroadcastLocked();
  16. }
  17. }
  18. }

这里首先调用PersistentDateStore的applyWifiDisplayAliases方法去判断扫描到的设备中有没有以前连接过并记录下来的wifi display设备,比较方法是比较两者的MAC地址,如果在PersistentDateStore中找到,再比较两者的别名(Alias),如果不相同则更新results列表,细节的代码可以看applyWifiDisplayAlias中的实现。

[java] view plaincopy

  1. public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
  2. WifiDisplay[] results = displays;
  3. if (results != null) {
  4. int count = displays.length;
  5. for (int i = 0; i < count; i++) {
  6. WifiDisplay result = applyWifiDisplayAlias(displays[i]);
  7. if (result != displays[i]) {
  8. if (results == displays) {
  9. results = new WifiDisplay[count];
  10. System.arraycopy(displays, 0, results, 0, count);
  11. }
  12. results[i] = result;
  13. }
  14. }
  15. }
  16. return results;
  17. }

回到上面的onScanResults中,接着判断刚扫描到的设备列表(availableDisplays)和之前存储的设备列表(mAvailableDisplays)之间有没有变化,可以数组内容以及是否可连两个方面检查。如果有变化,则把刚扫描到的设备列表(availableDisplays)赋值给存储的设备列表(mAvailableDisplays)。接下来调用fixRememberedDisplayNamesFromAvailableDisplaysLocked来更新PersistentDateStore中存储的已经连接过的wifi display设备,更新的条件是设备的MAC地址一样,但设备的DeviceName和DeviceAlias有变化,这是就要更新到PersistentDateStore中,代码如下:

[java] view plaincopy

  1. private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() {
  2. boolean changed = false;
  3. for (int i = 0; i < mRememberedDisplays.length; i++) {
  4. WifiDisplay rememberedDisplay = mRememberedDisplays[i];
  5. WifiDisplay availableDisplay = findAvailableDisplayLocked(
  6. rememberedDisplay.getDeviceAddress());
  7. if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) {
  8. mRememberedDisplays[i] = availableDisplay;
  9. changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay);
  10. }
  11. }
  12. if (changed) {
  13. mPersistentDataStore.saveIfNeeded();
  14. }
  15. }

如果扫描到的设备列表中有wifi display设备的名字或者别名发生了变化,就会调用到PersistentDataStore.saveIfNeeded方法把数据写到/data/system/display-manager-state.xml中。

回到onScanResults中,接下来会调用updateDisplaysLocked来更新返回给MediaRouter的设备列表信息,在这里会把扫描到的设备以及之前存储下来的设备做一次合并,共同保存到mDisplays数组中,后面在发送broadcast的时候,就会把mDisplays保存到WifiDisplayStatus对象中,并在broadcast带上这个对象。

[java] view plaincopy

  1. private void updateDisplaysLocked() {
  2. List<WifiDisplay> displays = new ArrayList<WifiDisplay>(
  3. mAvailableDisplays.length + mRememberedDisplays.length);
  4. boolean[] remembered = new boolean[mAvailableDisplays.length];
  5. for (WifiDisplay d : mRememberedDisplays) {
  6. boolean available = false;
  7. for (int i = 0; i < mAvailableDisplays.length; i++) {
  8. if (d.equals(mAvailableDisplays[i])) {
  9. remembered[i] = available = true;
  10. break;
  11. }
  12. }
  13. if (!available) {
  14. displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
  15. d.getDeviceAlias(), false, false, true));
  16. }
  17. }
  18. for (int i = 0; i < mAvailableDisplays.length; i++) {
  19. WifiDisplay d = mAvailableDisplays[i];
  20. displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
  21. d.getDeviceAlias(), true, d.canConnect(), remembered[i]));
  22. }
  23. mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY);
  24. }

上面的实现中先从mRememberedDisplays逐个添加wifi display设备到displays数组中,如果在mAvailableDisplays有相同的设备,则不添加到displays数组;后面再把mAvailableDisplays所有元素添加到displays数组,并全部赋值给mDisplays数组。

再回到onScanResults中,就会调用scheduleStatusChangedBroadcastLocked向WifiDisplayHandler发送MSG_SEND_STATUS_CHANGE_BROADCAST消息,这个我们在前面已经讲过了,然后会发送broadcast,并带上一个WifiDisplayStatus对象。现在我们再到MediaRouter和WifiDisplaySettings中看如何处理这个broadcast,先来看MediaRouter如何解析WifiDisplayStatus对象。updateWifiDisplayStatus的实现如下:

[java] view plaincopy

  1. static void updateWifiDisplayStatus(WifiDisplayStatus status) {
  2. WifiDisplay[] displays;
  3. WifiDisplay activeDisplay;
  4. if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
  5. displays = status.getDisplays();
  6. activeDisplay = status.getActiveDisplay();
  7. } else {
  8. displays = WifiDisplay.EMPTY_ARRAY;
  9. activeDisplay = null;
  10. }
  11. String activeDisplayAddress = activeDisplay != null ?
  12. activeDisplay.getDeviceAddress() : null;
  13. // Add or update routes.
  14. for (int i = 0; i < displays.length; i++) {
  15. final WifiDisplay d = displays[i];
  16. if (shouldShowWifiDisplay(d, activeDisplay)) {
  17. RouteInfo route = findWifiDisplayRoute(d);
  18. if (route == null) {
  19. route = makeWifiDisplayRoute(d, status);
  20. addRouteStatic(route);
  21. } else {
  22. String address = d.getDeviceAddress();
  23. boolean disconnected = !address.equals(activeDisplayAddress)
  24. && address.equals(sStatic.mPreviousActiveWifiDisplayAddress);
  25. updateWifiDisplayRoute(route, d, status, disconnected);
  26. }
  27. if (d.equals(activeDisplay)) {
  28. selectRouteStatic(route.getSupportedTypes(), route, false);
  29. }
  30. }
  31. }
  32. // Remove stale routes.
  33. for (int i = sStatic.mRoutes.size(); i-- > 0; ) {
  34. RouteInfo route = sStatic.mRoutes.get(i);
  35. if (route.mDeviceAddress != null) {
  36. WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress);
  37. if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) {
  38. removeRouteStatic(route);
  39. }
  40. }
  41. }
  42. sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;
  43. }

上面的代码中,首先从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设备。

我的一些简单理解:MediaRouter只保存已经配对上的remote display设备,包括Wifi diplay、蓝牙A2DP设备、chromecast设备等,用于提供给其它应用程序使用,比如youtube可以直接chromecast,当我们前面有成功和一个chromecast设备配对过后,youtube应用就可以从MediaRouter对象中获取到当前已经配对的chromecast设备信息,并可以把youtube的视频推送到chromecast上面播放;再举个例子,百度视频应用可以访问MediaRouter中的wifi display设备,当我们设备中有已经连接或已经保存的wifi display设备时,就可以很方便的从直接百度视频上面直接开始wifi display,而不需要用户再去Settings里面扫描连接。

再来看WifiDisplaySettings中如何处理MSG_SEND_STATUS_CHANGE_BROADCAST:

[java] view plaincopy

  1. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  2. @Override
  3. public void onReceive(Context context, Intent intent) {
  4. String action = intent.getAction();
  5. if (action.equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
  6. scheduleUpdate(CHANGE_WIFI_DISPLAY_STATUS);
  7. }
  8. }
  9. };

从MediaRouter中的callback消息也会进入到scheduleUpdate中,只是后面的参数不一样,通过callback进来的参数是CHANGE_ROUTES,而broadcast进来的参数是CHANGE_WIFI_DISPLAY_STATUS,来看scheduleUpdate,最终实现是mUpdateRunnable中:

[java] view plaincopy

  1. private void update(int changes) {
  2. boolean invalidateOptions = false;
  3. // Update wifi display state.
  4. if ((changes & CHANGE_WIFI_DISPLAY_STATUS) != 0) {
  5. mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();
  6. // The wifi display feature state may have changed.
  7. invalidateOptions = true;
  8. }
  9. // Rebuild the routes.
  10. final PreferenceScreen preferenceScreen = getPreferenceScreen();
  11. preferenceScreen.removeAll();
  12. // Add all known remote display routes.
  13. final int routeCount = mRouter.getRouteCount();
  14. for (int i = 0; i < routeCount; i++) {
  15. MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
  16. if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) {
  17. preferenceScreen.addPreference(createRoutePreference(route));
  18. }
  19. }
  20. // Additional features for wifi display routes.
  21. if (mWifiDisplayStatus != null
  22. && mWifiDisplayStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
  23. // Add all unpaired wifi displays.
  24. for (WifiDisplay display : mWifiDisplayStatus.getDisplays()) {
  25. if (!display.isRemembered() && display.isAvailable()
  26. && !display.equals(mWifiDisplayStatus.getActiveDisplay())) {
  27. preferenceScreen.addPreference(new UnpairedWifiDisplayPreference(
  28. getActivity(), display));
  29. }
  30. }
  31. }
  32. }

上面的代码比较简单,一个是从MediaRouter中获取mRoutes数组中存着的remote display设备;一个是从broadcast中的WifiDisplayStatus对象中获取mDisplay数组,两者相互合并构建整个listview展现给用户。至此,wifi display的扫描流程就介绍完了,下面是整体的流程图:

时间: 2024-08-04 18:14:40

Android 4.2 Wifi Display 之 Settings 源码分析的相关文章

Android 4.2 Wifi Display 之 Settings 源码分析(一)

一.简单背景 简单背景:随着无线互联的深入,不管是蓝牙.WIFI或者各种基于此的规范不管是UPNP还是DLNA都随着用户的需求得到了很大的发展,google 自从android 4.0引入wifi direct后,又在11月份公布的android 4.2中引入了Miracast无线显示共享,其协议在此可以下载.具体的协议部分内容比较多,本人由于水平有限,就不在这里罗列协议的内容了,只附上一份架构图供大家对其有个大致的印象. 英文缩写对应如下: HIDC: Human Interface Devi

ym——Android仿网易新闻导航栏PagerSlidingTabStrip源码分析

转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! 前言 最近工作比较忙,所以现在才更新博文,对不住大家了~!言归正传,我们来说说这个PagerSlidingTabStrip,它是配合ViewPager使用的导航栏,网易新闻就是用的这个导航,我们仔细观察这个导航栏不仅他是跟着ViewPager滑动而滑动,而且指示器还会随着标题的长度而动态的变化长度,还可以改变多种样式哦~! · 下载地址: Github:https://github.

Android异步消息处理机制详解及源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 最近相对来说比较闲,加上养病,所以没事干就撸些自己之前的知识点为博客,方便自己也方便别人. 1 背景 之所以选择这个知识点来分析有以下几个原因: 逛GitHub时发现关注的isuss中有人不停的在讨论Android中的Looper , Handler , Me

Android5.1源码分析系列(一)Settings源码分析

最近在看android源码,现在想做一个系列,专门对源码进行简单直接的分析.本人道行尚浅,希望大家能够进行批评,本人感激不尽. Android5.1 Settints源码分析 1 概要 本文分析的文件目录: /packages/apps/Settings/AndroidManifest.xml /packages/apps/settings/src/com/android/settings/Settings.java /packages/apps/settings/src/com/android

Android异步消息处理 Handler Looper Message关系源码分析

# 标签: 读博客 对于Handler Looper Message 之前一直只是知道理论,知其然不知所以然,看了hongyang大神的源码分析,写个总结帖. 一.概念.. Handler . Looper .Message 这三者都与Android异步消息处理线程相关的概念. 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环.若消息队列为空,线程则会阻塞等待. 说了这一堆,那么和Handle

Django的settings源码分析

首先需要在settings文件中导入,然后查看settings源码 from django.conf import global_settings,settings 点进去LazySettings我们会发现它是一个类,所以settings是类实例化出来的对象,这里利用了单例模式. 然后我们点入manage.py文件中看到 最后点入Settings类 原文地址:https://www.cnblogs.com/baohanblog/p/12164257.html

Android实现多个倒计时优化与源码分析

因为之前有个项目需求是需要时时刻去更新UI倒计时,之前想到的,这简单嘛,用计时或者Handler就可以搞定,而且性能也不错,但是需求要ListView,什么,?大量的View都需要,那Handle处理不好会挂的啊,那轮训呢,?太消耗内存和Cpu,突然之前只有想到用Handle去处理,但是Item太多如何管理呢.?带着这样的问题,思考着纠结着,今天无意中看到一个源码还不错, 这个类是Google原生提供的数字时钟,可以实现时时刻刻的更新,我想里面肯定封装了一些实现的逻辑就跟着开始研究学习,下面是该

Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析

ActiveAndroid 项目地址在https://github.com/pardom/ActiveAndroid 关于他的详细介绍和使用步骤 可以看下面两篇文章: https://github.com/pardom/ActiveAndroid/wiki http://www.future-processing.pl/blog/persist-your-data-activeandroid-and-parse/ 请确保你在阅读本文下面的内容之前 熟读上面的2篇文章.我不会再讲一遍如何使用这个框

Android cocos2dx游戏开发——示例程序HelloCpp源码分析

本文通过分析cocos2dx提供的示例程序HelloCpp来分析cocos2dx的启动过程. 我们从HelloCpp.java开始: [java] view plaincopyprint? package org.cocos2dx.hellocpp; import org.cocos2dx.lib.Cocos2dxActivity; import android.os.Bundle; public class HelloCpp extends Cocos2dxActivity{ protecte