DreamService 和 DreamManagerService 问题分析详解手机开发

问题背景

Android官方提供了一个DreamService类以实现屏保的功能,内部提供了两个回调:

  1. onAttachToWindow()
  2. onDetachToWindow()

昨天碰到了一个系统性的问题:

在启动DreamService屏保之后,系统的其他App直接startActivity()并不会退出屏保,也就是DreamService并不会收到onDetachToWindow()回调?

分析问题

1. DreamService 怎么实现屏保

论如何实现屏保可以参考《基于DreamService的屏保》

DreamService实现屏保功能很简单,只需要两个步骤

  • 实现DreamService的onAttachedToWindow()和onDetachToWindow()
  • 声明权限 android:permission="android.permission.BIND_DREAM_SERVICE

如果你熟悉NotificationManagerService和NotificationListenerService,你会发现DreamService跟他们的实现是一致的。

DreamService 就是利用窗口Window,来显示屏保功能,如果你是做系统应用的,那你应该很熟悉了。

// DreamService.java 
private final void attach(IBinder windowToken, boolean canDoze, IRemoteCallback started) {
    
    ... 
    mWindow = new PhoneWindow(this); 
    mWindow.setCallback(this); 
     
    getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes()); 
    ... 
     
    mHandler.post(new Runnable() {
    
        @Override 
        public void run() {
    
            ... 
            onDreamingStarted(); 
            ...  
        } 
    }); 
} 
 
// 设定布局 
public void setContentView(@LayoutRes int layoutResID) {
    
    getWindow().setContentView(layoutResID); 
} 

DreamService在attach的私有方法内部创建了PhoneWindow,跟Activity创建很像。

在WIndow创建成功以后会回调到onAttachToWindow(),我们就可以直接在onAttachToWindow()添加我们的自定义布局setContentView()

2. DreamManagerService 怎么启动 DreamService

上面问题1,大概简述了DreamService怎么利用Window创建自己的屏保,但是attach()方式是哪里调用的呢?

DreamService的onBind()创建了DreamServiceWrapper,DreamServiceWrapper实现了IDreamService的binder接口,内部对外提供了attach(),detach()和wakeUp()接口。DreamService作为了binder的服务端。

其中,wakeUp()就是唤醒,attach()屏保启动,detach()结束屏保服务

其实是DreamManagerService绑定服务DreamService的binder。

 
 private final class DreamServiceWrapper extends IDreamService.Stub {
    
        @Override 
        public void attach(final IBinder windowToken, final boolean canDoze, 
                IRemoteCallback started) {
    
            mHandler.post(new Runnable() {
    
                @Override 
                public void run() {
    
                    DreamService.this.attach(windowToken, canDoze, started); 
                } 
            }); 
        } 
 
        @Override 
        public void detach() {
    
            mHandler.post(new Runnable() {
    
                @Override 
                public void run() {
    
                    DreamService.this.detach(); 
                } 
            }); 
        } 
 
        @Override 
        public void wakeUp() {
    
            mHandler.post(new Runnable() {
    
                @Override 
                public void run() {
    
                    DreamService.this.wakeUp(true /*fromSystem*/); 
                } 
            }); 
        } 
    } 

DreamManagerService是系统实现的一个SystemService,他怎么会去bind一个用户实现的DreamService呢?

如上面所说,这部分跟NotificationManagerService如何发现并绑定NotificationListenerService原理是一样的。

DreamManagerService 利用权限发现自定义 DreamService

DreamManagerService 继承了SystemService,类似AMS或者NMS。他们的启动和注册方式也是一样的

// DreamManagerService 
 @Override 
 public void onStart() {
    
    publishBinderService(DreamService.DREAM_SERVICE, new BinderService()); 
    publishLocalService(DreamManagerInternal.class, new LocalService()); 
 } 

来看如何发现和绑定 DreamService?

// DreamManagerService 
    private final class LocalService extends DreamManagerInternal {
    
        @Override 
        public void startDream(boolean doze) {
    
            startDreamInternal(doze); 
        } 
		... 
    } 
 
//DreamManagerService 
private void startDreamInternal(boolean doze) {
    
    final int userId = ActivityManager.getCurrentUser(); 
    final ComponentName dream = chooseDreamForUser(doze, userId); 
    if (dream != null) {
    
        synchronized (mLock) {
    
            startDreamLocked(dream, false /*isTest*/, doze, userId); 
        } 
    } 
} 

重点来了,chooseDreamForUser()就是发现DreamService,最后调用到getDreamComponentsForUser()

    private ComponentName[] getDreamComponentsForUser(int userId) {
    
        String names = Settings.Secure.getStringForUser(mContext.getContentResolver(), 
                Settings.Secure.SCREENSAVER_COMPONENTS, 
                userId); 
        ComponentName[] components = componentsFromString(names); 
 
        // 验证 
        List<ComponentName> validComponents = new ArrayList<ComponentName>(); 
        if (components != null) {
    
            for (ComponentName component : components) {
    
                if (validateDream(component)) {
    
                    validComponents.add(component); 
                } 
            } 
        } 
        return validComponents.toArray(new ComponentName[validComponents.size()]); 
    } 

可见,DreamManagerService利用Settings.Secure权限发现DreamService,当有多个DreamService时候,会选择第一个启动。

DreamManagerService 绑定 DreamService

上面利用了权限选择自定义的DreamService,那如何绑定就很简单了

private void startDreamLocked(final ComponentName name, 
        final boolean isTest, final boolean canDoze, final int userId) {
    
    // 结束屏保 
    stopDreamLocked(true /*immediate*/); 
    .... 
    // 启动不熄屏 
    PowerManager.WakeLock wakeLock = mPowerManager 
            .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); 
    mHandler.post(wakeLock.wrap( 
        // 启动屏保 
        () -> mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock))); 
} 

如何你跟下去会走到DreamController

// DreamController 
public void startDream(Binder token, ComponentName name, 
    boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
    
    Intent intent = new Intent(DreamService.SERVICE_INTERFACE); 
    intent.setComponent(name); 
    ... 
    if (!mContext.bindServiceAsUser(intent, mCurrentDream, 
       Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 
       new UserHandle(userId))) {
    
       stopDream(true /*immediate*/); 
       return; 
    } 
} 

可见,最后DreamManagerService借助DreamController去bind对应的DreamService

3. 有什么方法停止屏保

(1) DreamService Input事件

在DreamService的keyEvent接收所有输入事件,当收到key事件时候,直接退出屏保

    public boolean dispatchKeyEvent(KeyEvent event) {
    
        if (!mInteractive) {
    
            wakeUp(); 
            return true; 
        } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    
            wakeUp(); 
            return true; 
        } 
        return mWindow.superDispatchKeyEvent(event); 
    } 
 
    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
    
        if (!mInteractive) {
    
            wakeUp(); 
            return true; 
        } 
        return mWindow.superDispatchKeyShortcutEvent(event); 
    } 
 
    public boolean dispatchTouchEvent(MotionEvent event) {
    
        if (!mInteractive) {
    
            wakeUp(); 
            return true; 
        } 
        return mWindow.superDispatchTouchEvent(event); 
    } 

很直接,在激活状态下,只要收到点击、key事件就退出了屏保。

(2) PowerManagerService的 ACTION_DREAMING_STOPPED广播

利用DreamManagerService的binder接口,去wakeUp()屏保。但是呢!

哎!有个很可惜的地方,DreamManagerService并不在SystemServiceRegistry,意味着我们不能直接通过 Context.getSystemService来拿到DreamManagerService来停止屏保动作。

最后我发现DreamManagerService跟PowerManagerService是有关联性的

// PowerManagerService     
    filter = new IntentFilter(); 
    filter.addAction(Intent.ACTION_DREAMING_STARTED); 
    filter.addAction(Intent.ACTION_DREAMING_STOPPED); 
    mContext.registerReceiver(new DreamReceiver(), filter, null, mHandler); 

PowerManagerService接收了两个广播:开始屏保和结束屏保。

个人找了好久才找到

    private final class DreamReceiver extends BroadcastReceiver {
    
        @Override 
        public void onReceive(Context context, Intent intent) {
    
            synchronized (mLock) {
    
                scheduleSandmanLocked(); 
            } 
        } 
    } 
 
    private void scheduleSandmanLocked() {
    
        if (!mSandmanScheduled) {
    
            mSandmanScheduled = true; 
            Message msg = mHandler.obtainMessage(MSG_SANDMAN); 
            msg.setAsynchronous(true); 
            mHandler.sendMessage(msg); 
        } 
    } 

最后,看下如何处理MSG_SANDMAN,就是handleSandman()方法

private void handleSandman() {
    // runs on handler thread 
    ··· 
    // Stop dream. 
    if (isDreaming) {
    
        mDreamManager.stopDream(false /*immediate*/); 
    } 
} 

但是,这里的结束屏保是异步的动作。

总结

自做系统应用以来,养成了一个很好的习惯就是习惯性阅读源码,很多时候都是可以从源码寻找解决之道。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6264.html

(0)
上一篇 2021年7月17日 00:45
下一篇 2021年7月17日 00:45

相关推荐

发表回复

登录后才能评论