Android状态栏禁止下拉异常分析

       最近做一个项目,需要在进入极致省电模式的时候,禁止状态栏的下拉,退出极致省电模式时,恢复状态栏的下拉,功能很容易就实现了,但是却发现在极致省电状态栏出现异常后,状态栏仍然处于禁止下拉状态,此时调用恢复下拉的代码,仍然不能恢复状态栏下拉,在此记录一下我的解决过程。

     1.添加权限

<!-- <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />  -->
<uses-permission android:name="android.permission.STATUS_BAR" />
 

        注:(1)我看有的文章,用的第一个权限,但是我实际用的第二个权限,这里把两个都写上,大家选一个合适的吧。

              (2)这个权限在之前的Android版本中,是可以直接获取的,但是6.0以后就需要系统权限才可以获得这个权限了

     2.禁止通知栏下拉      

      StatusBarManager mStatusBarManager = (StatusBarManager) getSystemService("statusbar");
      mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);

     如果想禁止多个选项,比如禁止下拉以及隐藏虚拟按键的recent键,可用按位或的方式:

      StatusBarManager mStatusBarManager = (StatusBarManager) getSystemService("statusbar");
      mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND|StatusBarManager.DISABLE_RECENT);
    如果此处不用按位或,而调用两次disable,则只会有最后一次的disable生效。

3.恢复通知栏下拉      

      StatusBarManager mStatusBarManager = (StatusBarManager) getSystemService("statusbar");
      mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);

    4.在禁止下拉状态发生异常崩溃,不能恢复下拉原因分析

       (1)根据代码,查看StatusBarManager.java             

…………
import android.os.ServiceManager;
import android.view.View;
import com.android.internal.statusbar.IStatusBarService;

public class StatusBarManager {

    public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND;
    private IStatusBarService mService;
    private IBinder mToken = new Binder();
    private static final String TAG = "StatusBarManager";
…………

    private synchronized IStatusBarService getService() {
        if (mService == null) {
            mService = IStatusBarService.Stub.asInterface(
                    ServiceManager.getService(Context.STATUS_BAR_SERVICE));
            if (mService == null) {
                Slog.w("StatusBarManager", "warning: no STATUS_BAR_SERVICE");
            }
        }
        return mService;
    }

    /**
     * Disable some features in the status bar.  Pass the bitwise-or of the DISABLE_* flags.
     * To re-enable everything, pass {@link #DISABLE_NONE}.
     */
    public void disable(int what) {
        try {
            final IStatusBarService svc = getService();
            if (svc != null) {
                if ((what & DISABLE_EXPAND) != 0 ) {
                    Slog.d("StatusBarManager", "disable status bar , call from" , new RuntimeException("disable"));
                }
                svc.disable(what, mToken, mContext.getPackageName());
            }
        } catch (RemoteException ex) {
            // system process is dead anyway.
            throw new RuntimeException(ex);
        }
    }
 …………
  在disable方法中,先获取service,然后调用service的disable方法。

     其中有一段代码:   

      if (svc != null) {
                if ((what & DISABLE_EXPAND) != 0 ) {
                    Slog.d("StatusBarManager", "disable status bar , call from" , new RuntimeException("disable"));
                }
                svc.disable(what, mToken, mContext.getPackageName());
            }
 如果
what & DISABLE_EXPAND) != 0
   其中what为我们输入的禁止或者下拉的int型参数,在禁止下拉的时候是0x00010000,恢复下拉的时候是0x00000000;DISABLE_EXPAND是View.STATUS_BAR_DISABLE_EXPAND,为0x00010000。

    因此,在正常情况下,禁止下拉时会有debug的log信息,在恢复下拉的时候没有。

     注:本文手机使用的是YunOS系统,其log的tag与Android稍有不同,但极致未变,不影响分析

    于是我们分析log信息,此处省略繁杂的系统log,在正常情况下,禁止下拉时,出现log: 

10-09 11:02:01.318: D/StatusBarManager(20724): disable status bar , call from
10-09 11:02:01.318: D/StatusBarManager(20724): java.lang.RuntimeException: disable
10-09 11:02:01.318: D/StatusBarManager(20724):   at android.app.StatusBarManager.disable(StatusBarManager.java:109)
10-09 11:02:01.318: D/StatusBarManager(20724):   at com.changhong.batteryaidl.BatteryService$AIDLServerBinder.disableStatusBar(BatteryService.java:326)
10-09 11:02:01.318: D/StatusBarManager(20724):   at com.changhong.batteryaidl.IBatteryService$Stub.onTransact(IBatteryService.java:118)
10-09 11:02:01.318: D/StatusBarManager(20724):   at android.os.Binder.execTransact(Binder.java:451)
10-09 11:02:01.319: D/StatusBarManagerService(794): disable statusbar calling PID = 20724
10-09 11:02:01.320: D/SystemUI_PhoneStatusBar(1097): disable: 0x00000000 -> 0x00010000 (diff: 0x00010000)
10-09 11:02:01.320: D/SystemUI_PhoneStatusBar(1097): disable: < EXPAND* icons alerts ticker system_info back home recent clock >
10-09 11:02:01.320: D/tianPanelView(1097): setExpandedHeightInternal() h=0.0  mLeftRightEffect=false
10-09 11:02:01.321: E/BatteryService(20724): disableStatusBar state: 65536
     再查看发生崩溃后:

10-09 11:16:01.983: D/StatusBarManager(20724): disable status bar , call from
10-09 11:16:01.983: D/StatusBarManager(20724): java.lang.RuntimeException: disable
10-09 11:16:01.983: D/StatusBarManager(20724): 	at android.app.StatusBarManager.disable(StatusBarManager.java:109)
10-09 11:16:01.983: D/StatusBarManager(20724): 	at com.changhong.batteryaidl.BatteryService$AIDLServerBinder.disableStatusBar(BatteryService.java:326)
10-09 11:16:01.983: D/StatusBarManager(20724): 	at com.changhong.batteryaidl.IBatteryService$Stub.onTransact(IBatteryService.java:118)
10-09 11:16:01.983: D/StatusBarManager(20724): 	at android.os.Binder.execTransact(Binder.java:451)
10-09 11:16:01.983: E/BatteryService(20724): disableStatusBar state: 65536
  从log可以看出,发生异常后,依然可以获取到service,但是在调用service的disable方法时出现了问题,参见异常后,缺少下面log:

10-09 11:02:01.319: D/StatusBarManagerService(794): disable statusbar calling PID = 20724
10-09 11:02:01.320: D/SystemUI_PhoneStatusBar(1097): disable: 0x00000000 -> 0x00010000 (diff: 0x00010000)
10-09 11:02:01.320: D/SystemUI_PhoneStatusBar(1097): disable: < EXPAND* icons alerts ticker system_info back home recent clock >

从log可以看出,这段log部分的代码正是执行状态栏禁止或恢复下拉的代码。

 追踪语句

     svc.disable(what, mToken, mContext.getPackageName());

     我们找到StatusBarManagerService.java,该类disable方法的源码为:         

    public void disable(int what, IBinder token, String pkg) {
        disableInternal(mCurrentUserId, what, token, pkg);
    }
     mCurrentUserId是由方法设置的,根据名字推测是应用的ID

  →继续 (第一层方法)  

    private void disableInternal(int userId, int what, IBinder token, String pkg) {
        enforceStatusBar();

        synchronized (mLock) {
            disableLocked(userId, what, token, pkg);
        }
    }
       其中enforceStatusBar方法与获取权限相关,并未仔细分析

   →看disableLocked   (第二层方法)    

    private void disableLocked(int userId, int what, IBinder token, String pkg) {
        // It's important that the the callback and the call to mBar get done
        // in the same order when multiple threads are calling this function
        // so they are paired correctly.  The messages on the handler will be
        // handled in the order they were enqueued, but will be outside the lock.
        manageDisableListLocked(userId, what, token, pkg);

        // Ensure state for the current user is applied, even if passed a non-current user.
        final int net = gatherDisableActionsLocked(mCurrentUserId);
        if (net != mDisabled) {
            mDisabled = net;
            mHandler.post(new Runnable() {
                    public void run() {
                        mNotificationDelegate.onSetDisabled(net);
                    }
                });
            if (mBar != null) {
                try {
                    /// M:[ALPS01673960] Fix User cannot drag down the notification bar.
                    if (true) Slog.d(TAG, "disable statusbar calling PID = " + Binder.getCallingPid());
                    mBar.disable(net);
                } catch (RemoteException ex) {
                }
            }
        }
    }
翻译前面几句注释:

      当多个线程调用这个函数时,回调函数和调用mbar在同一顺序里是很重要的,以便它们的配对正确。在handler中的信息将在其排队的队列中管理,但处于lock的外面。

    注:纯直译,关于lock方面的知识很单薄,如果有问题,欢迎指正。

→看manageDisableListLocked(第二层方法→第三层方法1)

    void manageDisableListLocked(int userId, int what, IBinder token, String pkg) {
        if (SPEW) {
            Slog.d(TAG, "manageDisableList userId=" + userId
                    + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
        }
        // update the list
        final int N = mDisableRecords.size();
        DisableRecord tok = null;
        int i;
        for (i=0; i<N; i++) {
            DisableRecord t = mDisableRecords.get(i);
            if (t.token == token && t.userId == userId) {
                tok = t;
                break;
            }
        }
        if (what == 0 || !token.isBinderAlive()) {
            if (tok != null) {
                mDisableRecords.remove(i);
                tok.token.unlinkToDeath(tok, 0);
            }
        } else {
            if (tok == null) {
                tok = new DisableRecord();
                tok.userId = userId;
                try {
                    token.linkToDeath(tok, 0);
                }
                catch (RemoteException ex) {
                    return; // give up
                }
                mDisableRecords.add(tok);
            }
            tok.what = what;
            tok.token = token;
            tok.pkg = pkg;
        }
    }
    该方法主要是更新mDisableRecords,如果token和userId在mDisableRecords中找到了匹配的记录,则赋值给tok,对于异常发生后,token和userId均与之前不同,因此tok为空。正常情况下,当恢复通知栏下拉的时候,如果tok不为空,则会清除mDisableRecords中该条记录,并调用unlinkToDeath清除一个之前注册的死亡标识信息,很显然,如果发生异常,不会执行该操作。

 →看gatherDisableActionsLocked方法(第二层方法→第三层方法2)

    // lock on mDisableRecords
    int gatherDisableActionsLocked(int userId) {
        final int N = mDisableRecords.size();
        // gather the new net flags
        int net = 0;
        for (int i=0; i<N; i++) {
            final DisableRecord rec = mDisableRecords.get(i);
            if (rec.userId == userId) {
                net |= rec.what;
            }
        }
        return net;
    }
    该方法主要是获取需要执行的操作,即what值,异常发生后,net值为0

→看 mBar.disable方法即PhoneStatusBar.disable(第二层方法2→第三层方法3)

    public void disable(int state) {
        final int old = mDisabled;
        final int diff = state ^ old;
        mDisabled = state;

        if (DEBUG) {
            Slog.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)", old, state, diff));
        }

        StringBuilder flagdbg = new StringBuilder();
        flagdbg.append("disable: < ");
        flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand");
        flagdbg.append(((diff & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS"
                : "icons");
        flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS"
                : "alerts");
        flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "TICKER"
                : "ticker");
        flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO"
                : "system_info");
        flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
        flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home");
        flagdbg.append(((diff & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent");
        flagdbg.append(((diff & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " ");
        flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock");
        flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " ");
        flagdbg.append(">");
        Slog.d(TAG, flagdbg.toString());

        if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
            boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
            // showClock(show);
        }
        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
                animateCollapsePanels();
            }
        }

        /*
         * if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if
         * ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
         * setNotificationIconVisibility(false,
         * com.android.internal.R.anim.fade_out); } }
         */


        if ((diff & (StatusBarManager.DISABLE_HOME
                | StatusBarManager.DISABLE_RECENT
                | StatusBarManager.DISABLE_BACK
                | StatusBarManager.DISABLE_SEARCH)) != 0) {
            // the nav bar will take care of these
            if (mNavigationBarView != null)
                mNavigationBarView.setDisabledFlags(state);

            if ((state & StatusBarManager.DISABLE_RECENT) != 0) {
                // close recents if it's visible
                mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
                mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
            }
        }

        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
                if (mTicking) {
                    mTicker.halt();
                } else {
                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
                }
            } else {
                if (!mExpandedVisible) {
                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
                }
            }
        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
            if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
                mTicker.halt();
            }
        }
    }
      可以发现,是此处输出的 

10-09 11:02:01.320: D/SystemUI_PhoneStatusBar(1097): disable: 0x00000000 -> 0x00010000 (diff: 0x00010000)
10-09 11:02:01.320: D/SystemUI_PhoneStatusBar(1097): disable: < EXPAND* icons alerts ticker system_info back home recent clock >
      在发生异常后,不再输出该段log,因此可以确定,发生异常后,并没有进入该方法。 但在调用该方法前,输出了一个log:

     10-09 11:02:01.319: D/StatusBarManagerService(794): disable statusbar calling PID = 20724

在试验中发现,发生异常仍然是输出了该句log的,也就是说逻辑应该没有问题。同时,当我们每次在代码中调用disable时都新建一个binder时,当成功禁止通知栏下拉后,并不能恢复通知栏下拉,因为binder改变了。此分析,发生异常后不能恢复通知栏下拉与binder、lock有关,这也就是disableLocked方法中注释说明的情况。

(2)此处需要说明一下:因为涉及到系统权限级别,我把对通知栏操作的方法放在了有系统权限的AIDLService中,然后在app中建了一个AIDLClient与其通信。

         发生异常后,如果重新安装AIDLClient所在的app A,状态栏仍然无法恢复下拉,但是如果重新安装一次AIDLService所在的app B,在安装完成的时候,状态栏自行恢复下拉。

        查看log,发现出现了下面log  :

10-09 17:56:37.372: D/DisplayManagerService(793): Display listener for pid 22946 died.
10-09 17:56:37.373: E/BatteryClient(20551): AIDLClient.onServiceDisconnected()...
10-09 17:56:37.372: I/StatusBarManagerService(793): binder died for pkg=com.changhong.batteryaidl
10-09 17:56:37.373: E/BatteryClient(20551): AIDLClient.onServiceDisconnected()...
10-09 17:56:37.373: D/StatusBarManagerService(793): disable statusbar calling PID = 793
   分析log,第三句log处是使状态栏恢复下拉的关键,查看源码:  

    private class DisableRecord implements IBinder.DeathRecipient {
        int userId;
        String pkg;
        int what;
        IBinder token;

        public void binderDied() {
            Slog.i(TAG, "binder died for pkg=" + pkg);
            disableInternal(userId, 0, token, pkg);
            token.unlinkToDeath(this, 0);
        }
    }

     这是因为binger前面调用了linkToDeath方法,因此当binder死亡的时候会调用该方法,而该方法中,disableInternal方法正是上文分析的对状态栏进行设置的方法,因此可以推测,如果我们在发生异常AIDLService与其Client联系中断的时候,调用恢复通知栏下拉的代码,其binder未改变,也许可以正常恢复通知栏下拉。

    在服务中断的时候,远程的AIDLservice将会调用onUnbind方法,以及一系列销毁service的方法,因此我们只需要在这些操作中执行一次恢复状态栏下拉即可,本文将该部分操作放入onUnbind中,

	public boolean onUnbind(Intent intent) {
	StatusBarManager mStatusBarManager = (StatusBarManager) getSystemService("statusbar");
        mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
        return super.onUnbind(intent);
    }

    至此,当再次发生程序崩溃,导致AIDLService与其Client连接中断时,通知栏能够自行恢复下拉,问题得到解决。

 

    本人小菜鸟一枚,Android和JAVA很多知识是硬伤,文章中设计到的lock和binder更是我的短板,会在后续的工作中深入研究它们。如果本文有问题,欢迎指正!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值