深入研究 DisplayManager.requestDisplayModes 开启日志 DisplayManagerShellCommand 中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class DisplayManagerShellCommand extends ShellCommand { private int getMatchContentFrameRateUserPreference () { final Context context = mService.getContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); getOutPrintWriter().println("Match content frame rate type: " + dm.getMatchContentFrameRateUserPreference()); return 0 ; } private int setMatchContentFrameRateUserPreference () { final String matchContentFrameRatePrefText = getNextArg(); if (matchContentFrameRatePrefText == null ) { getErrPrintWriter().println("Error: no matchContentFrameRatePref specified" ); return 1 ; } final int matchContentFrameRatePreference; try { matchContentFrameRatePreference = Integer.parseInt(matchContentFrameRatePrefText); } catch (NumberFormatException e) { getErrPrintWriter().println("Error: invalid format of matchContentFrameRatePreference" ); return 1 ; } if (matchContentFrameRatePreference < 0 ) { getErrPrintWriter().println("Error: invalid value of matchContentFrameRatePreference" ); return 1 ; } final Context context = mService.getContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); final int refreshRateSwitchingType = toRefreshRateSwitchingType(matchContentFrameRatePreference); dm.setRefreshRateSwitchingType(refreshRateSwitchingType); return 0 ; } private int setDisplayModeDirectorLoggingEnabled (boolean enabled) { mService.setDisplayModeDirectorLoggingEnabled(enabled); return 0 ; } }
也就是说 adb shell cmd display 这 getMatchContentFrameRateUserPreference 和 setMatchContentFrameRateUserPreference 这类都是调用的 DisplayManager
但部分也是直接调用 DisplayManagerService 的方法,比如 setDisplayModeDirectorLoggingEnabled
那就有个问题,它明明都持有了 DisplayManagerService 的引用,为啥还要去调用 context.getSystemService(DisplayManager.class) 去获取 DisplayManager 呢?
DisplayManager 都是往 DisplayManagerGlobal 里面调用的,DisplayManagerGlobal 再调用 IDisplayManager(IBinder),拿到的 IBinder 也是去和 DisplayManagerService 通信,饶了一大圈,为啥不直接调用,也许是我有没理解到的地方
Claude Sonnet 4.5:
1 2 3 4 5 6 7 8 9 DisplayManagerShellCommand 的混用策略是有意为之 厨师要尝味道: 直接从锅里舀一勺 (直接调用 Service) ↓ 快速、简单、不需要走流程 厨师要测试上菜流程: 按铃叫服务员,走完整流程 (通过 Manager) ↓ 确保整个流程没问题
根据 DisplayManagerShellCommand 代码 dmd-logging-enable 命令可以开启日志,也就是
1 adb shell cmd display dmd-logging-enable
调用链路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────┐ │ DisplayManagerShellCommand │ │ setDisplayModeDirector │ │ LoggingEnabled() │ └──────────────┬───────────────┘ │ 直接调用 ▼ ┌──────────────────────────────┐ │ DisplayManagerService │ │ setDisplayModeDirector │ │ LoggingEnabled() │ └──────────────┬───────────────┘ │ ▼ ┌──────────────────────────────┐ │ DisplayModeDirector │ │ setLoggingEnabled() │ └──────────────────────────────┘
通过命令也可以查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ➜ ~ adb shell cmd display Display manager commands: help Print this help text. show-notification NOTIFICATION_TYPE Show notification for one of the following types: on-hotplug-error, on-link-training-failure, on-cable-dp-incapable cancel-notifications Cancel notifications. set-brightness BRIGHTNESS Sets the current brightness to BRIGHTNESS (a number between 0 and 1). reset-brightness-configuration Reset the brightness to its default configuration. ab-logging-enable Enable auto-brightness logging. ab-logging-disable Disable auto-brightness logging. dwb-logging-enable Enable display white-balance logging. dwb-logging-disable Disable display white-balance logging. dmd-logging-enable Enable display mode director logging.
但是还有部分日志可以打开
1 2 3 4 5 6 7 8 public final class DisplayManagerService extends SystemService { } class DisplayDeviceRepository implements DisplayAdapter .Listener { }
我们一并打开观察日志
调用链路分析 DisplayManager.requestDisplayModes DisplayManagerGlobal.requestDisplayModes 这两个都加了 @RequiresPermission(“android.permission.RESTRICT_DISPLAY_MODES”)
这个是系统级的权限,SHELL 下都没有,所以整个调用,我都是在 root 的设备下进行的
DisplayManagerService 中的定义如下
1 2 3 4 5 6 7 @EnforcePermission(RESTRICT_DISPLAY_MODES) @Override public void requestDisplayModes (IBinder token, int displayId, @Nullable int [] modeIds) { requestDisplayModes_enforcePermission(); DisplayManagerService.this .mDisplayModeDirector.requestDisplayModes( token, displayId, modeIds); }
其中的 token 是 DisplayManagerGlobal 中的 mToken
requestDisplayModes 调用链路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 应用进程 system_server 进程 ┌──────────────────────────┐ ┌──────────────────────────┐ │ DisplayManager │ │ DisplayManagerService │ │ requestDisplayModes() │ Binder │ requestDisplayModes() │ └────────────┬─────────────┘ IPC └────────────┬─────────────┘ │ ←──────→ │ ▼ ▼ ┌──────────────────────────┐ ┌──────────────────────────┐ │ DisplayManagerGlobal │ │ DisplayModeDirector │ │ requestDisplayModes() │ │ requestDisplayModes() │ └────────────┬─────────────┘ └──────────────────────────┘ │ ▼ ┌──────────────────────────┐ │ IDisplayManager │ │ (Binder Proxy) │ └──────────────────────────┘
其中 IDisplayManager 与 DisplayManagerService 通过 IBinder 进行通信,DisplayManagerService 和 DisplayModeDirector 都是在系统框架代码里面,具体路径是 /system/framework/services.jar
直接看 DisplayModeDirector.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void requestDisplayModes (IBinder token, int displayId, int [] modeIds) { if (mSystemRequestObserver != null ) { boolean vrrSupported; synchronized (mLock) { vrrSupported = isVrrSupportedLocked(displayId); } if (vrrSupported) { mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds); } } }
这里判断了一个 vrr,目前我拿不到这个东西是 true 还是 false,false 后续的逻辑都是不会走的
我直接在 SHELL 权限下,搞到这个 DisplayManagerService,直接调用 mDisplayModeDirector.requestDisplayModes 用这种方式能不能绕过权限?也可能会受到 SELinux 的限制
mSystemRequestObserver 在DisplayModeDirector构造函数创建的时候创建的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class DisplayModeDirector { public DisplayModeDirector (@NonNull Context context, @NonNull Handler handler, @NonNull Injector injector, @NonNull DisplayManagerFlags displayManagerFlags, @NonNull DisplayDeviceConfigProvider displayDeviceConfigProvider) { mVotesStorage = new VotesStorage (this ::notifyDesiredDisplayModeSpecsChangedLocked, mVotesStatsReporter); mSystemRequestObserver = new SystemRequestObserver (mVotesStorage); } private void notifyDesiredDisplayModeSpecsChangedLocked () { if (mDesiredDisplayModeSpecsListener != null && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) { Message msg = mHandler.obtainMessage( MSG_REFRESH_RATE_RANGE_CHANGED, mDesiredDisplayModeSpecsListener); msg.sendToTarget(); } } }
VotesStorage 持有 DisplayModeDirector.notifyDesiredDisplayModeSpecsChangedLocked
进入 SystemRequestObserver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class SystemRequestObserver { void requestDisplayModes (IBinder token, int displayId, @Nullable int [] modeIds) { if (modeIds == null ) { removeSystemRequestedVote(token, displayId); } else { addSystemRequestedVote(token, displayId, modeIds); } } private void addSystemRequestedVote (IBinder token, int displayId, @NonNull int [] modeIds) { try { boolean needLinkToDeath = false ; List<Integer> modeIdsList = new ArrayList <>(); for (int mode: modeIds) { modeIdsList.add(mode); } synchronized (mLock) { SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); if (modesByDisplay == null ) { needLinkToDeath = true ; modesByDisplay = new SparseArray <>(); mDisplaysRestrictions.put(token, modesByDisplay); } modesByDisplay.put(displayId, modeIdsList); updateStorageLocked(displayId); } if (needLinkToDeath) { Slog.d(TAG, "binder linking to death: " + token); token.linkToDeath(mDeathRecipient, 0 ); } } catch (RemoteException re) { Slog.d(TAG, "linking to death failed: " + token, re); removeSystemRequestedVotes(token); } } @GuardedBy("mLock") private void updateStorageLocked (int displayId) { List<Integer> modeIds = new ArrayList <>(); boolean [] modesFound = new boolean [1 ]; mDisplaysRestrictions.forEach((key, value) -> { List<Integer> modesForDisplay = value.get(displayId); if (modesForDisplay != null ) { if (!modesFound[0 ]) { modeIds.addAll(modesForDisplay); modesFound[0 ] = true ; } else { modeIds.retainAll(modesForDisplay); } } }); mVotesStorage.updateVote(displayId, Vote.PRIORITY_SYSTEM_REQUESTED_MODES, modesFound[0 ] ? Vote.forSupportedModes(modeIds) : null ); } }
总结链路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌──────────────────────┐ │ DisplayModeDirector │ │ requestDisplayModes()│ └─────────┬────────────┘ │ ▼ ┌──────────────────────┐ │ SystemRequestObserver│ │addSystemRequestedVote│ └─────────┬────────────┘ │ ▼ ┌──────────────────────┐ │ SystemRequestObserver│ │ updateStorageLocked()│ └─────────┬────────────┘ │ ▼ ┌──────────────────────┐ │ VotesStorage │ │ updateVote() │ └──────────────────────┘
这里 VotesStorage. 等只是表达这个函数在哪个类里面,不是它的静态方法
从 DisplayModeDirector 的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class DisplayModeDirector { public DisplayModeDirector (@NonNull Context context, @NonNull Handler handler, @NonNull Injector injector, @NonNull DisplayManagerFlags displayManagerFlags, @NonNull DisplayDeviceConfigProvider displayDeviceConfigProvider) { mVotesStorage = new VotesStorage (this ::notifyDesiredDisplayModeSpecsChangedLocked, mVotesStatsReporter); } } class VotesStorage { VotesStorage(@NonNull Listener listener, @Nullable VotesStatsReporter votesStatsReporter) { mListener = listener; mVotesStatsReporter = votesStatsReporter; } }
再结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class VotesStorage { void updateVote (int displayId, @Vote .Priority int priority, @Nullable Vote vote) { if (mLoggingEnabled) { Slog.i(TAG, "updateVoteLocked(displayId=" + displayId + ", priority=" + Vote.priorityToString(priority) + ", vote=" + vote + ")" ); } if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) { Slog.w(TAG, "Received a vote with an invalid priority, ignoring:" + " priority=" + Vote.priorityToString(priority) + ", vote=" + vote); return ; } boolean changed = false ; SparseArray<Vote> votes; synchronized (mStorageLock) { if (mVotesByDisplay.contains(displayId)) { votes = mVotesByDisplay.get(displayId); } else { votes = new SparseArray <>(); mVotesByDisplay.put(displayId, votes); } var currentVote = votes.get(priority); if (vote != null && !vote.equals(currentVote)) { votes.put(priority, vote); changed = true ; } else if (vote == null && currentVote != null ) { votes.remove(priority); changed = true ; } } if (mLoggingEnabled) { Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes); } if (changed) { if (mVotesStatsReporter != null ) { mVotesStatsReporter.reportVoteChanged(displayId, priority, vote); } mListener.onChanged(); } } }
关键行 mListener.onChanged();
mListener 的值也就是 DisplayModeDirector.notifyDesiredDisplayModeSpecsChangedLocked
看到这里,很难不感叹,为啥 java 就是没有回调函数,类似于其他语言的闭包,所有的回调,都需要依赖接口
最终 mVotesStorage.updateVote 调用到的是 DisplayModeDirector.notifyDesiredDisplayModeSpecsChangedLocked
整理链路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ┌──────────────────────────────────────────────┐ │ SystemRequestObserver │ │ requestDisplayModes() │ └────────────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ SystemRequestObserver │ │ addSystemRequestedVote() │ └────────────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ SystemRequestObserver │ │ updateStorageLocked() │ └────────────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ VotesStorage │ │ updateVote() │ └────────────────────────┬─────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ DisplayModeDirector │ │ notifyDesiredDisplayModeSpecsChangedLocked() │ └──────────────────────────────────────────────┘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Step 1: 接收请求 SystemRequestObserver.requestDisplayModes(token, displayId, modeIds) │ └─→ 判断是添加还是移除投票 Step 2: 添加投票 addSystemRequestedVote() │ 保存请求信息到 mDisplaysRestrictions │ Map<IBinder, SparseArray<List<Integer>>> └─→ linkToDeath() 监听调用进程 Step 3: 更新存储 updateStorageLocked() │ 遍历所有 token 的请求 │ 对同一 displayId 的 modeIds 求交集 └─→ 得到最终支持的 modes Step 4: 投票机制 VotesStorage.updateVote() │ 优先级: PRIORITY_SYSTEM_REQUESTED_MODES │ 比较新旧投票是否变化 └─→ changed = true 时触发回调 Step 5: 异步通知 mListener.onChanged() │ 实际调用 DisplayModeDirector.notify...Locked() │ 投递 MSG_REFRESH_RATE_RANGE_CHANGED 到 Handler └─→ 避免在持有锁时回调外部代码(防止死锁)
而 DisplayModeDirector
1 2 3 4 5 6 7 8 9 10 11 12 private void notifyDesiredDisplayModeSpecsChangedLocked () { if (mDesiredDisplayModeSpecsListener != null && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) { Message msg = mHandler.obtainMessage( MSG_REFRESH_RATE_RANGE_CHANGED, mDesiredDisplayModeSpecsListener); msg.sendToTarget(); } }
交给 Handler 处理,并且传递的事件类型就是刷新率变化,也就是默认了 Mode 改变,就是刷新率变化
Handler 处理比较简单,直接回调传过来的 DesiredDisplayModeSpecsListener,但是自己持有回调的情况下,为啥还要交给 Handler 去处理嘞
学习了一下
Claude Sonnet 4.5 Start
根据代码中的注释,这个问题有明确的答案:
1 2 3 4 5 6 7 8 9 10 11 12 private void notifyDesiredDisplayModeSpecsChangedLocked () { if (mDesiredDisplayModeSpecsListener != null && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) { Message msg = mHandler.obtainMessage( MSG_REFRESH_RATE_RANGE_CHANGED, mDesiredDisplayModeSpecsListener); msg.sendToTarget(); } }
原因解析
主要目的:避免死锁
持有锁的问题 : 当前方法名 notifyDesiredDisplayModeSpecsChangedLocked() 表明调用时持有锁 (mLock)
回调风险 : 如果直接在持有锁的情况下调用监听器回调:
监听器可能会调用其他方法
这些方法可能需要获取其他锁
无法保证锁的获取顺序一致
容易造成死锁
Handler 的作用 :
将回调投递到 Handler 的消息队列
在 Handler 线程处理时,当前持有的锁已经释放
避免了在持有锁时调用外部代码的风险
类比说明 这是一个经典的异步解耦 模式:
1 持有锁 -> 需要通知外部 -> 不能直接调用(会死锁) -> 投递消息 -> 释放锁 -> Handler处理消息
类似于你在一个房间里拿着钥匙,需要叫外面的人进来,但如果你拿着钥匙直接开门让人进来,可能会撞到门。更安全的做法是先把钥匙放下,再开门让人进来。
Claude Sonnet 4.5 End
比喻手法不错
再来看 DisplayModeDirector.DisplayModeDirectorHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private final class DisplayModeDirectorHandler extends Handler { DisplayModeDirectorHandler(Looper looper) { super (looper, null , true ); } @Override public void handleMessage (Message msg) { switch (msg.what) { case MSG_REFRESH_RATE_RANGE_CHANGED: DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener = (DesiredDisplayModeSpecsListener) msg.obj; desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged(); break ; } } }
那这个 DesiredDisplayModeSpecsListener 又是哪里设置的呢?在函数构造的时候并没有初始化,而是通过DisplayModeDirector 中
1 2 3 4 5 6 public void setDesiredDisplayModeSpecsListener ( @Nullable DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener) { synchronized (mLock) { mDesiredDisplayModeSpecsListener = desiredDisplayModeSpecsListener; } }
由 DisplayManagerService 在 systemReady 方法中设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public final class DisplayManagerService extends SystemService { public void systemReady (boolean safeMode) { mDisplayModeDirector.setDesiredDisplayModeSpecsListener( new DesiredDisplayModeSpecsObserver ()); } class DesiredDisplayModeSpecsObserver implements DisplayModeDirector .DesiredDisplayModeSpecsListener { private final Consumer<LogicalDisplay> mSpecsChangedConsumer = display -> { int displayId = display.getDisplayIdLocked(); DisplayModeDirector.DesiredDisplayModeSpecs desiredDisplayModeSpecs = mDisplayModeDirector.getDesiredDisplayModeSpecs(displayId); DisplayModeDirector.DesiredDisplayModeSpecs existingDesiredDisplayModeSpecs = display.getDesiredDisplayModeSpecsLocked(); if (DEBUG) { Slog.i(TAG, "Comparing display specs: " + desiredDisplayModeSpecs + ", existing: " + existingDesiredDisplayModeSpecs); } if (!desiredDisplayModeSpecs.equals(existingDesiredDisplayModeSpecs)) { display.setDesiredDisplayModeSpecsLocked(desiredDisplayModeSpecs); mChanged = true ; } }; @GuardedBy("mSyncRoot") private boolean mChanged = false ; public void onDesiredDisplayModeSpecsChanged () { synchronized (mSyncRoot) { mChanged = false ; mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer, false ); if (mChanged) { scheduleTraversalLocked(false ); mChanged = false ; } } } } }
好好好,又回到 DisplayManagerService 了 再总结链路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 应用进程 system_server 进程 ┌──────────────────────────┐ ┌──────────────────────────┐ │ DisplayManager │ │ DisplayManagerService │ │ requestDisplayModes() │ Binder │ requestDisplayModes() │ └────────────┬─────────────┘ IPC └────────────┬─────────────┘ │ ←──────────→ │ ▼ ▼ ┌──────────────────────────┐ ┌──────────────────────────┐ │ DisplayManagerGlobal │ │ DisplayModeDirector │ │ requestDisplayModes() │ │ requestDisplayModes() │ └────────────┬─────────────┘ └────────────┬─────────────┘ │ │ ▼ ▼ ┌──────────────────────────┐ ┌──────────────────────────┐ │ IDisplayManager │ │ SystemRequestObserver │ │ (Binder Proxy) │ │ requestDisplayModes() │ └──────────────────────────┘ └────────────┬─────────────┘ │ ▼ ┌──────────────────────────┐ │ VotesStorage │ │ updateVote() │ └────────────┬─────────────┘ │ 回调 ▼ ┌──────────────────────────┐ │ DisplayModeDirector │ │ notifyDesiredDisplayMode │ │ SpecsChangedLocked() │ └────────────┬─────────────┘ │ Handler 异步 ▼ ┌───────────────────────────────────────┐ │ DisplayManagerService │ │ DesiredDisplayModeSpecsObserver. │ │ onDesiredDisplayModeSpecsChanged() │ └───────────────────────────────────────┘
不支持闭包的痛苦,虽然面相接口编程是好事,让整体架构可以变得清晰,但四面八方都是接口,这时候带来的收益就不好说了
关键调用
1 display.setDesiredDisplayModeSpecsLocked(desiredDisplayModeSpecs);
1 2 3 4 5 6 final class LogicalDisplay { public void setDesiredDisplayModeSpecsLocked ( DisplayModeDirector.DesiredDisplayModeSpecs specs) { mDesiredDisplayModeSpecs = specs; } }
好像,饶了这么长一圈,最后给一个变量赋值,当然,其中应该有很多额外的逻辑,有每个类分别处理
并没有任何和 native code 交互的代码,例如调用 SurfaceControl 等
经过一些猜测,我怀疑这个特么就是给默认显示器用的,告知当前应该用多少刷新率渲染
和应用用 SurfaceView.setFrameRate,好像是一样?
死马当死马医
我还是尝试调用一下,看下会出什么日志
Cannot get MTK settings - java.lang.ClassNotFoundException: com.mediatek.provider.MtkSettingsExt$System
一加两个设备都报错
tag:DisplayManagerService | tag:DisplayDeviceRepository | tag:DisplayModeDirector | tag:SystemRequestObserver