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

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 listfinal 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 mDisableRecordsint gatherDisableActionsLocked(int userId) {final int N = mDisableRecords.size();// gather the new net flagsint 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 theseif (mNavigationBarView != null)mNavigationBarView.setDisabledFlags(state);if ((state & StatusBarManager.DISABLE_RECENT) != 0) {// close recents if it's visiblemHandler.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更是我的短板,会在后续的工作中深入研究它们。如果本文有问题,欢迎指正!

Android状态栏禁止下拉异常分析相关推荐

  1. Android 4.2 禁止下拉状态栏

    最近在做一个界面需要禁止下拉状态栏,于是整理了一下,以后备用. import android.app.StatusBarManager;//首先导入包 StatusBarManager mStatus ...

  2. Android 12.0 锁屏页面禁止下拉状态栏

    目录 1.概述 2.锁屏页面禁止下拉状态栏的核心类 3.锁屏页面禁止下拉状态栏的核心功能分析和实现

  3. android10 禁止下拉状态栏

    需求:android10 禁止下拉状态栏,也就是禁止下拉如下图的快速设置面板( Quick settings panel,也叫QS面板) 修改后:怎么拉都拉不下来,包括锁屏页面和正常桌面都无法下拉状态 ...

  4. android禁止下拉刷新,Android开发之无痕过渡下拉刷新控件的实现思路详解

    相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下 ...

  5. android 4.4 禁止下拉,Android开发中禁止下拉式的实现技巧

    我们开发项目的时候,经常会看到禁止的情况,而Android开发中并没有直接调用的接口,下面是爱站技术频道小编就给大家介绍的Android开发中禁止下拉式的实现技巧,希望网友们喜欢! 分享给大家供大家参 ...

  6. Android 禁止下拉菜单栏

    Android 禁止下拉菜单栏 如下图,有时候我们需要禁止用户下拉出菜单栏. 在解决这个问题之前,我们需要知道,下拉菜单栏总共有两种,一种是锁屏下的下拉菜单,一种是非锁屏下的下拉菜单.因此需要两种不同 ...

  7. android自带下拉阻尼动画,android 有阻尼下拉刷新列表的实现方法

    本文将会介绍有阻尼下拉刷新列表的实现,先来看看效果预览: 这是下拉状态: 这是下拉松开手指后listView回滚到刷新状态时的样子: 1. 如何调用 虽然效果图看起来样子不太好看,主要是因为那个蓝色的 ...

  8. Android实现系统下拉栏的消息提示——Notification

    Android实现系统下拉栏的消息提示--Notification 系统默认样式 默认通知(通用) 效果图 按钮 <Button android:layout_width="match ...

  9. android加载时二级联动点击二级联动,Android实现联动下拉框二级地市联动下拉框功能...

    日常使用软件中,为了方便且规范输入,会使用到下拉框进行输入,如注册时生日选项,购物时的地址输入,都会用到下拉框,今日笔者为了巩固已学的知识,实现了二级联动下拉框用作回顾及分享给求知的新手. 思路/步骤 ...

  10. iOSTableview 禁止下拉,允许上拉

    1 回弹机制:bounces alwaysBounceHorizontal alwaysBounceVertical bounces:描述的当scrollview的显示超过内容区域的边缘以及返回时,是 ...

最新文章

  1. 蓝牙L2CAP剖析(一)
  2. flex中dispatchEvent的用法(自定义事件) .
  3. bootstrap tabale 点击_jquery+bootstrap实现tab切换, 每次切换时都请求数据, 点击提交分别向不同的地址提交数据...
  4. 【转】Delphi实现自动发贴和识别验证码 王泽宾
  5. [摘抄] 资深软件项目经理/产品经理“扯皮技巧”汇总(新手入坑必读,不定期更新)...
  6. 应用华云对象存储服务实现网站存储的平滑迁移实践
  7. 又看了半天的pdf格式的js方面的书,感觉受益匪浅啊,只会一点操作的我,要学好理论...
  8. linux进程管理试题,Linux 考试试题
  9. 阿里云香港服务器解析后域名无法访问
  10. 地球围绕着太阳的概念和计算
  11. 【基于贪心的树型动态规划】【NOI2007】追捕盗贼
  12. 使用RecyclerView实现瀑布流
  13. Java代码审计——WebGoat CSRF (上)
  14. Google搜索引擎使用技巧大全
  15. 「营业日志 2020.12.10」Jiangly 的排列数数题
  16. Linux 之 快捷键,命令总结 --- 三剑客**
  17. spring data jpa使用的几种方式
  18. C4D致富经典入门到精通(三)
  19. PostgreSQL中使用的SQL语法
  20. zblog host php,ZblogPHP幻灯片调用代码

热门文章

  1. python 中chr_python中chr
  2. mysql中chr_Chr()和chrb()的含义
  3. uni-app生成pdf,依赖html2canvas和jspdf
  4. Ubuntu18.04安装搜狗输入法无法使用
  5. win10应用商店不见了
  6. Flutter(十七) 实现国际化
  7. 炫酷的个人主页要怎么制作 ? |GitCode
  8. 构建虚拟Web主机——基于IP地址的虚拟主机
  9. [原创]UMail for linux邮件服务器备份/还原邮件数据与数据库
  10. 微博视频自动投稿视频社区大师软件下载