Evan-Lam's Blog

持之以恒,契而不舍


  • 首页

  • 归档

  • 标签

  • 分类

  • 关于

  • 搜索

六月

发表于 2018-06-01 | 分类于 随笔 | 阅读次数:

新的6月,愿你别爱的太满,别睡的太晚。
当你开始爱自己,全世界都会爱你。

崭新的6月,
愿你,
不再为过去苦恼,
也别再为明天担忧;

令人希冀的6月,
愿你,
远离一切不健康的东西,
不论是人还是饮食,
不论是事物还是环境;

无限可能的6月,
愿你,
只做有趣和快乐的事,
余生很长,不必慌张。

多分辨率适配总结

发表于 2018-01-07 | 分类于 Android | 阅读次数:

sw360dp :
sw表示smallest screen width, 设备使用比设备本身小最接近的文件夹。比如一个设备最小的屏幕宽度是380dp,现在有sw360dp文件夹和sw411dp文件夹,那么会使用哪个文件夹的资源?会使用sw360dp的,因为sw411dp要求最小屏幕宽度是411dp,你的设备最小屏幕宽度是360dp,那么大于360dp并且小于411dp这部分的布局就不够空间显示。

xhdpi:
如果设备是xhdpi,现在有hdpi文件夹和xxhdpi的资源,它会使用哪个文件夹的资源呢?会使用xxhdpi的,因为使用更好分辨率的资源,可以把它按比例压缩到小分辨率的资源而不会失真,如果使用hdpi的资源,需要把它进行按比例拉伸到合适的大小才能使用。

最清晰的Android多屏幕适配方案
http://blog.csdn.net/shimiso/article/details/52061376#t0

  1. 使用dp单位

  2. 在需要适配的地方使用引用,在不同的最小屏幕和像素密度的文件夹里面创建一份对应的尺寸。例如: mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi, sw320dp-hdpi, sw320dp-xxhdpi, sw360dp-hdpi, sw411dp-xxhdpi, w360dp, h360dp, values-480x320, values-854x480, values-1920x1080, values-v11, values-v14, values-24等等。

  3. selector, layer-list, shape, vector, animated-vector, path标签等xml文件放在drawable文件夹。可以使用这些创建的图形,最好不用图片资源。
    selector: http://blog.csdn.net/jiankeufo/article/details/73845750?from=singlemessage
    shape: https://www.cnblogs.com/popfisher/p/6238119.html

  4. 图片资源创建一套放在drawable-xxhdpi,Android系统会根据设备的分辨率来自动缩放适配,不用为不同分辨率的设备创建多套图片资源,以达到减少apk大小。

  5. 使用.9文件(nine-patch)图片,可以满足特殊拉伸图片的情况。这样就不用为每种分辨率创建一套图片资源。

  6. 在代码中动态需要使用像素px单位的地方,可以通过设备密度来计算 px = (int)(density * dp) + 0.5 来计算出来。tip:这里加0.5是为了四舍五入的作用。

  7. 对于字体大小使用sp, dp的区别:

    • 当修改系统字体大小时,字体大小以dp为单位时,大小不变;
    • 当修改系统字体大小时,字体大小以sp为单位时,大小跟随变化;
      dp:
      dp是一种密度无关像素,对应于 160dpi 下像素的物理尺寸
      sp:
      sp是和dp相同的基本单位,但它会按用户首选的文本尺寸进行缩放(属于缩放无关像素)

      从源代码注释可以看到:
      scaledDensity会收到用户配置的影响,而density不会.
      这也就是为什么设置sp单位时字体大小会受到用户配置系统字体的影响.

  8. Android中dp,px,sp概念梳理以及如何做到屏幕适配
    http://blog.csdn.net/jiangwei0910410003/article/details/40509571
    https://www.jianshu.com/p/23324a57352d

  9. | 密度类型 | 代表的分辨率(px) | 屏幕像素密度(dpi)|
    | - | :-: | -: |
    | 低密度(ldpi) | 240x320 | 120 |
    | 中密度(mdpi) | 320x480 | 160 |
    | 高密度(hdpi) | 480x800 | 240|
    | 超高密度(xhdpi) | 720x1280 | 320|
    | 超超高密度(xxhdpi) | 1080x1920 | 480 |

    屏幕尺寸、分辨率、像素密度三者关系

    一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是:

    Android 屏幕适配:最全面的解决方案:
    https://www.jianshu.com/p/ec5a1a30694b

  10. 使得布局元素自适应屏幕尺寸
    使用相对布局(RelativeLayout),禁用绝对布局(AbsoluteLayout)
    开发中,我们使用的布局一般有:
    线性布局(Linearlayout)
    相对布局(RelativeLayout)
    帧布局(FrameLayout)
    绝对布局(AbsoluteLayout)
    用”wrap_content”、”match_parent”和”weight“来控制视图组件的宽度和高度

  11. 常用图标规范尺寸:
    L DPI ( Low Density Screen,120 DPI ),其图标大小为 36 x 36 px
    M DPI ( Medium Density Screen, 160 DPI ),其图标大小为 48 x 48 px
    H DPI ( High Density Screen, 240 DPI ),其图标大小为 72 x 72 px
    XH DPI ( Extra-high density screen, 320 DPI ),其图标大小为 96 x 96 px
    XXH DPI( xx-high density screen, 480 DPI ),其图标大小为144 x 144 px
    XXXH DPI( xxx-high density screen, 640 DPI ),其图标大小为192 x 192 px
    http://blog.csdn.net/ys743276112/article/details/51910874

  12. 注意小数点强制转换后的精确度降低导致的显示问题。例如: 前面几个布局的高度都四舍五入相加后,总高度的和可能会少了两三个像素px单位。

Git常用命令

发表于 2017-05-08 | 分类于 Git | 阅读次数:

Git常用命令

全局设置

a. 设置用户信息

$ git config --global user.name "Your Name"  
$ git config --global user.email "email@example.com"

b. 设置编辑器

$ git config --global core.editor emacs

c. 设置差异比较器

$ git config --global merge.tool vimdiff

d. 设置显示不同的颜色

$ git config --global color.ui true

e. 查看全局设置信息

$ git config --global -l

SSH Key设置

a. 生成SSH key

$ ssh-keygen -t rsa -C "youremail@example.com"

b. 复制id_rsa.pub内容到粘贴板中

$ cat ~/.ssh/id_rsa.pub

c. 登陆Github > 设置 > SSH and GPG Keys > new SSH key

d. 复制粘贴板中的内容到key文本编辑框中

e. 点击Add SHH key按钮

f. 查看是否设置SSH成功

$ ssh [-p port] git@github.com

创建仓库

a. 进入需要创建仓库的目录

$ mkdir studygit
$ cd studygit
$ pwd
/Users/username/studygit

b. 在当前目录创建仓库

$ git init

添加远程库

a. 把一个已有的本地仓库与远程仓库关联

$ git remote add origin git@github.com:liangyunfeng/git.study.git

b. 本地库的所有内容推送到远程库上

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

$ git push -u origin master

c. 之后推送本地库到远程库上使用

$ git push origin master

把文件添加到暂存区

$ git add readme.txt

把文件添加到版本库

$ git commit -m "wrote a readme file"

查看仓库的当前状态

$ git status

查看修改内容

$ git diff readme.txt 

查看提交历史

$ git log

查看命令历史

$ git reflog

版本回退

$ git reset --hard commit_id 
$ git reset --hard HEAD <filename>
$ git reset --hard HEAD^^
$ git reset --hard HEAD~2

撤销修改

$ git checkout -- file

命令git checkout – file意思就是,把file文件在工作区的修改全部撤销,这里有两种情况:
一种是file自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是filet已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

把暂存区的修改撤销掉(unstage),重新放回工作区

$ git reset HEAD <file>

场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout – file。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD ,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退git reset –hard commit_id,不过前提是没有推送到远程库。

删除文件

$ git rm test.txt
$ git commit -m "description"

工作区误删文件,恢复到版本库最新版本

$ git checkout -- file

关联远程库

$ git remote add origin git@server-name:path/repo-name.git

关联后,第一次推送master分支的所有内容

$ git push -u origin master

此后,提交到远程库

$ git push origin master

从远程库克隆

$ git clone git@server-name:path/repo-name.git

创建分支

$ git branch <name>

切换分支

$ git checkout <name>

查看分支

$ git branch

创建+切换分支

$ git checkout -b <name>

合并某分支到当前分支

$ git merge <name>

删除分支

$ git branch -d <name>

解决冲突

当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。

查看分支合并图

$ git log --graph

普通模式合并某分支到当前分支

$ git merge --no-ff -m "merge with no-ff" <name>

合并分支时,加上–no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

工作现场隐藏

$ git stash

查看工作现场

$ git stash list

工作现场恢复但不删除工作现场

$ git stash apply [stash@{0}]

工作现场恢复并删除工作现场

$ git stash pop

查看远程库信息

$ git remote

查看远程库详细信息

$ git remote -v

从本地推送分支

$ git push origin branch-name

从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;

在本地创建和远程分支对应的分支

$ git checkout -b branch-name origin/branch-name

建立本地分支和远程分支的关联

$ git branch --set-upstream branch-name origin/branch-name

从远程抓取分支

$ git pull

从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

关联远程库

$ git remote add <name> git@github.com:liangyunfeng/git.study.git

远程库重命名

$ git remote rename <oldname> <newname>

删除远程库

$ git remote rm <name>

重新设置基线

$ git rebase

原理很简单:rebase需要基于一个分支来设置你当前的分支的基线,这基线就是当前分支的开始时间轴向后移动到最新的跟踪分支的最后面,这样你的当前分支就 是最新的跟踪分支。这里的操作是基于文件事务处理的,所以你不用怕中间失败会影响文件的一致性。在中间的过程中你可以随时取消rebase 事务。git rebase –abort

git rebase其实可以把它理解成是“重新设置基线”,将你的当前分支重新设置开始点。这个时候才能知道你当前分支于你需要比较的分支之间的差异。

创建标签

$ git tag <tagname> [commitid]

可以指定commitid,默认为HEAD

git tag -a <tagname> -m "decription" [commitid]

创建带有说明的标签,用-a指定标签名,-m指定说明文字

查看标签

$ git tag

看标签信息

$ git show <tagname>

删除标签

$ git tag -d <tagname>

推送一个本地标签

$ git push origin <tagname>

推送全部未推送过的本地标签

$ git push origin --tags

删除一个远程标签

$ git push origin :refs/tags/<tagname>

忽略文件

忽略某些文件时,需要编写.gitignore;

gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!

如果你确实想添加该文件,可以用-f强制添加到Git:
$ git add -f App.class
$ git check-ignore -v App.class

配置别名

$ $ git config --global alias.<name> 'option...'

$ git config –global alias.unstage ‘reset HEAD’
当你敲入命令:
$ git unstage file
实际上Git执行的是:
$ git reset HEAD file

搭建Git服务器

a. 安装git

$ sudo apt-get install git

b. 创建一个git用户,用来运行git服务:

$ sudo adduser git

c. 创建证书登录:

收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。

d. 初始化Git仓库:

先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:

$ sudo git init --bare sample.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。然后,把owner改为git:

$ sudo chown -R git:git sample.git

e. 禁用shell登录:

出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。

f. 克隆远程仓库:

现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行:

$ git clone git@server:/srv/sample.git

git push

$ git push origin master

origin指定了你要push到哪个remote
master其实是一个“refspec”,正常的“refspec”的形式为”+:”,冒号前表示local branch的名字,冒号后表示remote repository下 branch的名字。注意,如果你省略了,git就认为你想push到remote repository下和local branch相同名字的branch。听起来有点拗口,再解释下,push是怎么个push法,就是把本地branch指向的commit push到remote repository下的branch,比如:

$ git push origin master:master                (在local repository中找到名字为master的branch,使用它去更新remote repository下名字为master的branch,如果remote repository下不存在名字是master的branch,那么新建一个)
$ git push origin master                    (省略了<dst>,等价于“git push origin)master:master”)
$ git push origin master:refs/for/mybranch    (在local repository中找到名字为master的branch,用他去更新remote repository下面名字为mybranch的branch)
$ git push origin HEAD:refs/for/mybranch    (HEAD指向当前工作的branch,master不一定指向当前工作的branch,所以我觉得用HEAD还比master好些)
$ git push origin :mybranch                    (再origin repository里面查找mybranch,删除它。用一个空的去更新它,就相当于删除了)

git fetch 和 git pull 的区别

git fetch:相当于是从远程获取最新版本到本地,不会自动merge

$ git fetch origin master
$ git log -p master ../origin/master
$ git merge origin/master

以上命令的含义:
首先从远程的origin的master主分支下载最新的版本到origin/master分支上
然后比较本地的master分支和origin/master分支的差别
最后进行合并

git pull:相当于是从远程获取最新版本并merge到本地

$ git pull origin master

上述命令其实相当于git fetch 和 git merge
在实际使用中,git fetch更安全一些
因为在merge前,我们可以查看更新情况,然后再决定是否合并

git log

git log [<options>] [<since>..<until>] [[--] <path>...]

这条命令有很多参数选项

一、不带参数
    1.如果不带任何参数,它会列出所有历史记录,最近的排在最上方,显示提交对象的哈希值,作者、提交日期、和提交说明
    2.如果记录过多,则按Page Up、Page Down、↓、↑来控制显示
    3.按q退出历史记录列表

二、显示参数
    1.-p:按补丁显示每个更新间的差异,比下一条- -stat命令信息更全
    2.--stat:显示每次更新的修改文件的统计信息,每个提交都列出了修改过的文件,以及其中添加和移除的行数,并在最后列出所有增减行数小计
    3.--shortstat:只显示--stat中最后的行数添加修改删除统计
    4.--name-only:尽在已修改的提交信息后显示文件清单
    5.--name-status:显示新增、修改和删除的文件清单
    6.--abbrev-commit:仅显示SHA-1的前几个字符,而非所有的40个字符
    7.--relative-date:使用较短的相对时间显示(例如:"two weeks ago")
    8.--graph:显示ASCII图形表示的分支合并历史
    9.—pretty=:使用其他格式显示历史提交信息,可选项有:oneline,short,medium,full,fuller,email,raw以及format:<string>,默认为medium,如:
        1.--pretty=oneline:一行显示,只显示哈希值和提交说明(--online本身也可以作为单独的属性)
        2.--pretty=format:” ":控制显示的记录格式,如:
    10.--date= (relative|local|default|iso|rfc|short|raw):定制后边如果出现%ad或%cd时的日期格式

三、筛选参数:
    1.按数量
    2.按日期
    3.按作者
    4.按commit描述
    5.按文件
    6.按分支
    7.按内容
    8.按范围
    9.过滤掉merge commit
    10.按标签tag  
    11.按commit

git help

$ git help
$ git clone -help
$ git init -help
$ git add -help
$ git tag -help
$ git pull -help
$ git push -help
$ git status -help
$ git commit -help
$ git checkout -help
$ git merge -help
$ git stash -help

Notification框架简介

发表于 2017-03-19 | 分类于 Android | 阅读次数:

目录

  • Notification介绍
  • Notification框架原理
  • Notification框架服务端启动过程
  • SystemUI进程启动和绑定NotificationManagerService服务端过程
  • Notification调用过程
  • Notification通知提示音响起过程
  • 总结

Notification介绍

功能作用

  1. 显示接收到短息、即时消息等信息 (如QQ、微信、新浪、短信)
  2. 显示客户端的推送消息(如有新版本发布,广告,推荐新闻等)
  3. 显示正在进行的事务(例如:后台运行的程序)(如音乐播放器、版本更新时候的下载进度等)

通知的基本组成

1. 标题
2. 大图标
3. 内容文字 
4. 内容信息
5. 小图标
6. 通知的时间(Timestamp,默认为系统发出通知的时间,也可通过setWhen()来设置)

Notification框架原理

通知栏框架(Notificaiton),它适用于Android系统中交互事件的通知。它主要由三部分组成:系统服务端NotificationManagerService,通知显示端SystemUI,还有创建和更新通知的App端。
NotificationManagerService作为框架的服务端,在系统启动时就会启动并在后台运行,显示端SystemUI作为系统UI进程,也是Notification框架的客户端,在系统启动时也会启动并一直运行。
其它模块需调用Notification时,只需要调用NotificationManager.notify(int,Notification)就可以发出通知。

根据Notification框架的原理,我们就分别按以下几点来分析:

1. Notification框架服务端启动过程
2. SystemUI进程启动和绑定NotificationManagerService服务端过程
3. Notification调用过程
4. Notification通知提示音响起过程

Notification框架相关的包

android/frameworks/base/services/java/com/android/server/SystemServer.java        //系统服务类启动的地方
android/frameworks/base/core/java/com/android/server/LocalServices.java            //系统服务类通信的辅助类
android/frameworks/base/core/java/android/service/notification/                    //Notification服务的接口类和监听类
android/frameworks/base/services/core/java/com/android/server/notification/        //NotificationManagerService服务相关的类
android/frameworks/base/core/java/android/app/        //NotificationManager、Notification类和INotificationManager.aidl的包
android/frameworks/base/packages/SystemUI/                                        //Notification显示的UI进程

Notification框架的关系类图

Notification框架服务端启动过程

SystemServer启动的Notification管理服务类是NotificationManagerService,保存到SystemServiceManager的是NotificationManagerService服务对象中的INotificationManager.Stub(),但是绑定到ServiceManager中Context.NOTIFICATION_ SERVICE的服务类是NotificationManager,所有开发者通过Context.getSystemService(Context.NOTIFICATION_SERVICE)获取回来的服务类不是NotificationManagerServiced服务对象,而是NotificationManager对象,需要再通过NotificationManager对象中的getService()方法,获取SystemServiceManager系统服务管理对象中保存的INotificationManager.Stub()对象。这样NotificationManager就能通过INotificationManager.Stub()对象和NotificationManagerService服务对象进行远程通信了

系统启动时,SystemServiceRegistry类中会把NotificationManager注册为系统服务提供给其它服务或者应用获取系统服务使用,

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
/android/frameworks/base/core/java/android/app/SystemServiceRegistry.java 

private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();

static {
//...
registerService(Context.NOTIFICATION_SERVICE, NotificationManager.class,
new CachedServiceFetcher<NotificationManager>() {
@Override
public NotificationManager createService(ContextImpl ctx) {
final Context outerContext = ctx.getOuterContext();
return new NotificationManager(
new ContextThemeWrapper(outerContext,
Resources.selectSystemTheme(0,
outerContext.getApplicationInfo().targetSdkVersion,
com.android.internal.R.style.Theme_Dialog,
com.android.internal.R.style.Theme_Holo_Dialog,
com.android.internal.R.style.Theme_DeviceDefault_Dialog,
com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
ctx.mMainThread.getHandler());
}});
//...
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); //把注册的服务保存到列表对象中
}

public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}

在Activity或者Service中,可以直接通过Context.getSystemService(Context.NOTIFICATION_SERVICE)就可以获取系统服务使用。ActivityThread.performLaunchActivity()方法中创建ContextImpl对象并通过activity.attach()传递给Activity对象,再通过attachBaseContext()方法赋值给父类ContextWrapper中Context mBase对象,在Activity或者Service中调用getSystemService()方法,最终是调用ContextImpl中的getSystemService()方法。

1
2
3
4
5
6
7
8
//android/frameworks/base/core/java/android/app/ContextImpl.java 

@Override
public Object getSystemService(String name) { //从注册到系统的服务列表中获取对应的服务
//...
Object registrySystemService = SystemServiceRegistry.getSystemService(this, name);
return registrySystemService;
}

SystemServiceRegistry中注册的NotificationManager对象,其实不是真正的Notification服务,它只是一个调用接口对象,需要通过远程调用来实现和NotificationManagerService服务对象进行通信,真正实现相应的操作。以下是NotificationManagerService服务的启动流程。系统启动时会调用SystemServer来启动相应的系统服务对象。

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
60
61
62
63
64
65
66
67
68
/android/frameworks/base/services/java/com/android/server/SystemServer.java 

private SystemServiceManager mSystemServiceManager; //系统服务管理类

private void run() {
//...
//创建系统服务管理对象
mSystemServiceManager = new SystemServiceManager(mSystemContext);
//把系统服务管理对象保存到本地服务列表对象中,这样在系统进程中就可以通过LocalServices.getService(Class<T> type)
//直接返回本地服务列表中的服务对象进行使用
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
//...
}

private void startOtherServices() {
//...
INotificationManager notification = null;
//...
mSystemServiceManager.startService(NotificationManagerService.class); //启动NotificationManagerService服务对象
//获取NotificationManagerService服务对象的桩,用于进行远程调用
notification = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
//...
}


/android/frameworks/base/services/core/java/com/android/server/SystemServiceManager.java //系统服务管理类

public <T extends SystemService> T startService(Class<T> serviceClass) {
final String name = serviceClass.getName();
final T service;

Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext); //实例化服务

// Register it.
mServices.add(service); //注册服务

// Start it.
service.onStart(); //启动服务

return service;
}


//系统服务类,NotificationManagerService的父类
/android/frameworks/base/services/core/java/com/android/server/SystemService.java

public abstract void onStart(); //抽象方法,在子类中实现

protected final void publishBinderService(String name, IBinder service) {
publishBinderService(name, service, false);
}

protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated) {
ServiceManager.addService(name, service, allowIsolated);
}

//绑定和发布key为Context.NOTIFICATION_SERVICE的NotificationManager系统服务类
protected final <T> void publishLocalService(Class<T> type, T service) {
LocalServices.addService(type, service);
}

//添加到内部系统服务类的集合类LocalServices中
private SystemServiceManager getManager() {
return LocalServices.getService(SystemServiceManager.class);
}

SystemServer中调用NotificationManagerService的onStart()方法来启动NotificationManagerService服务。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/android/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 

public class NotificationManagerService extends SystemService {

@Override
public void onStart() {

mListeners = new NotificationListeners();

//绑定和发布key为Context.NOTIFICATION_SERVICE的NotificationManager系统服务类
publishBinderService(Context.NOTIFICATION_SERVICE, mService);
//添加到内部系统服务类的集合类LocalServices中
publishLocalService(NotificationManagerInternal.class, mInternalService);
}

//只开放给系统内部调用的API
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
@Override
public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idReceived, int userId) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
idReceived, userId);
}

@Override
public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
int userId) {
checkCallerIsSystem();
synchronized (mNotificationList) {
int i = indexOfNotificationLocked(pkg, null, notificationId, userId);
if (i < 0) {
return;
}
NotificationRecord r = mNotificationList.get(i);
StatusBarNotification sbn = r.sbn;
sbn.getNotification().flags = (r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
mRankingHelper.sort(mNotificationList);
mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);
}
}
};

//INotificationManager.Stub用于与NotificationManager类和SystemUI进程进行远程通信的桩 (或者其它模块)
private final IBinder mService = new INotificationManager.Stub() {

@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId); //添加或更新Notification
}

@Override
public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId, REASON_NOMAN_CANCEL,
null); //删除Notification
}

@Override
public void cancelAllNotifications(String pkg, int userId) {
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
REASON_NOMAN_CANCEL_ALL, null); //删除所有Notification
}

@Override
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
enforceSystemOrSystemUI("INotificationManager.registerListener");
//把NotificationListenerService对象注册到ManagedServices服务管理子类NotificationListeners对象中
mListeners.registerService(listener, component, userid);
}

@Override
public void unregisterListener(INotificationListener listener, int userid) {
//把NotificationListenerService对象从ManagedServices服务管理子类NotificationListeners对象中解除
mListeners.unregisterService(listener, userid);
}
};

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
//...
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
//...
//调用服务管理对象mListeners来更新所有注册到mListeners中的NotificationListenerService对象
mListeners.notifyPostedLocked(n, oldSbn);
//实现播放notification的铃声,使led灯亮起来或者震动等操作。buzz:嗡嗡叫,beep: 嘟嘟响,blink: 闪烁
buzzBeepBlinkLocked(r);
}
}
});
idOut[0] = id;
}

//ManagedServices服务管理类,就是用于一类服务的管理对象,例如:如需要管理几个同一类的服务对象NotificationListenerService
//只需要把相关的NotificationListenerService对象注册到ManagedServices服务管理对象中,需要更新的时候,只需要调用
//ManagedServices服务管理对象对注册的NotificationListenerService对象进行更新即可
public class NotificationListeners extends ManagedServices {

public NotificationListeners() {
super(getContext(), mHandler, mNotificationList, mUserProfiles);
}

@Override
protected IInterface asInterface(IBinder binder) {
return INotificationListener.Stub.asInterface(binder);
}

//新添加的方法,调用这个方法,就会更新所有注册进来的NotificationListenerService对象来更新
//调用服务管理对象mListeners来更新所有注册到mListeners中的NotificationListenerService对象
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
StatusBarNotification sbnClone = null;
StatusBarNotification sbnCloneLight = null;

for (final ManagedServiceInfo info : mServices) {
//...
if (trim == TRIM_LIGHT && sbnCloneLight == null) {
sbnCloneLight = sbn.cloneLight();
} else if (trim == TRIM_FULL) {
sbnClone = sbn.clone();
}
final StatusBarNotification sbnToPost =
(trim == TRIM_FULL) ? sbnClone : sbnCloneLight;

mHandler.post(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, update); //调用更新通知方法
}
});
}
}

public void notifyRemovedLocked(StatusBarNotification sbn) {
final StatusBarNotification sbnLight = sbn.cloneLight();
for (final ManagedServiceInfo info : mServices) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
mHandler.post(new Runnable() {
@Override
public void run() {
notifyRemoved(info, sbnLight, update);
}
});
}
}
//调用更新通知方法
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener)info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
//回调NotificationListenerService对象中的方法onNotificationPosted(),在SystemUI中显示Notification
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}

private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
NotificationRankingUpdate rankingUpdate) {
if (!info.enabledAndUserMatches(sbn.getUserId())) {
return;
}
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationRemoved(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
//...
}
}

//系统服务类,NotificationManagerService的父类
/android/frameworks/base/services/core/java/com/android/server/notification/ManagedServices.java

abstract protected IInterface asInterface(IBinder binder);

protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();

abstract public class ManagedServices {
abstract protected void onServiceAdded(ManagedServiceInfo info);
protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }

//把Service对象从ManagedServices服务管理类对象中删除
public void unregisterService(IInterface service, int userid) {
checkNotNull(service);
unregisterServiceImpl(service, userid);
}

//把Service对象注册到ManagedServices服务管理类对象中
public void registerService(IInterface service, ComponentName component, int userid) {
checkNotNull(service);
ManagedServiceInfo info = registerServiceImpl(service, component, userid);
if (info != null) {
onServiceAdded(info);
}
}

private ManagedServiceInfo registerServiceImpl(final IInterface service,
final ComponentName component, final int userid) {
synchronized (mMutex) {
try {
ManagedServiceInfo info = newServiceInfo(service, component, userid,
true /*isSystem*/, null, Build.VERSION_CODES.LOLLIPOP);
service.asBinder().linkToDeath(info, 0);
mServices.add(info);
return info;
} catch (RemoteException e) {
}
}
return null;
}

private void unregisterServiceImpl(IInterface service, int userid) {
ManagedServiceInfo info = removeServiceImpl(service, userid);
if (info != null && info.connection != null) {
mContext.unbindService(info.connection);
}
}
}

SystemUI进程启动和绑定NotificationManagerService服务端过程:

至此,Notification框架的服务端就已经启动完毕,NotificationManagerService类只是管理Notification的逻辑,显示端是在SystemUI进程中实现的,那么NotificationManagerService服务对象和SystemUI进程间是怎么通信的呢?两个不同进程间通信,很多同学可能就会想到Android的远程过程调用(Remote Procedure Call,RPC)方式来实现,这种猜测是合理的,而且这里也的确是这么实现的。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。所以我们先来看下NotificationManagerService服务和SystemUI进程通信的服务接口文件:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/android/frameworks/base/core/java/android/app/INotificationManager.aidl 

interface INotificationManager
{
void cancelAllNotifications(String pkg, int userId);

void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
in Notification notification, inout int[] idReceived, int userId); //发出通知的方法
void cancelNotificationWithTag(String pkg, String tag, int id, int userId); //取消通知的方法

void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled); //Settings中关闭应用通知
boolean areNotificationsEnabledForPackage(String pkg, int uid); //判断应用是否可发通知

void setPackagePriority(String pkg, int uid, int priority);
int getPackagePriority(String pkg, int uid);

void setPackagePeekable(String pkg, int uid, boolean peekable);
boolean getPackagePeekable(String pkg, int uid);

void setPackageVisibilityOverride(String pkg, int uid, int visibility);
int getPackageVisibilityOverride(String pkg, int uid);

// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
StatusBarNotification[] getActiveNotifications(String callingPkg);
StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);

//注册INotificationListener服务的方法
void registerListener(in INotificationListener listener, in ComponentName component, int userid);
//解除INotificationListener服务的方法
void unregisterListener(in INotificationListener listener, int userid);

void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
void cancelNotificationsFromListener(in INotificationListener token, in String[] keys);

void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);

ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim);
void requestHintsFromListener(in INotificationListener token, int hints);
int getHintsFromListener(in INotificationListener token);
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
void setInterruptionFilter(String pkg, int interruptionFilter);

ComponentName getEffectsSuppressor();
boolean matchesCallFilter(in Bundle extras);
boolean matchesMessageFilter(in Bundle extras);
boolean isSystemConditionProviderEnabled(String path);

int getZenMode();
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config, String reason);
oneway void setZenMode(int mode, in Uri conditionId, String reason);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
String[] getPackagesRequestingNotificationPolicyAccess();
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);

byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);

ParceledListSlice getAppActiveNotifications(String callingPkg, int userId);

//***************************************************************//
// ++ @noti.sysui [START] NotificationManager APIs for SEC ONLY //
//***************************************************************//
boolean isNotificationInterruptable(String pkg, String opPkg, String tag, int id,
in Notification notification, in long time, int userId);
void cancelSummaryNotificationWithTag(String pkg, String tag, int id, int userId);
//***************************************************************///
// -- @noti.sysui [START] NotificationManager APIs for SEC ONLY //
//***************************************************************//

//WTL_EDM_START
void clearAllNotificationsAsUser(int userId);
//WTL_EDM_END

void enqueueEdgeNotification(String pkg, String opPkg, int id, in Bundle extras, int userId); //发侧屏通知的方法
void removeEdgeNotification(String pkg, int id, in Bundle extras, int userId); //删除侧屏通知的方法
}


/android/frameworks/base/core/java/android/service/notification/INotificationListener.aidl

oneway interface INotificationListener
{
void onListenerConnected(in NotificationRankingUpdate update);
void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder,
in NotificationRankingUpdate update);
void onNotificationRemoved(in IStatusBarNotificationHolder notificationHolder,
in NotificationRankingUpdate update);
void onNotificationRankingUpdate(in NotificationRankingUpdate update);
void onListenerHintsChanged(int hints);
void onInterruptionFilterChanged(int interruptionFilter);

void onEdgeNotificationPosted(String pkg, int id, in Bundle extra);
void onEdgeNotificationRemoved(String pkg, int id, in Bundle extra);
}

这个接口类的服务端就是NotificationManagerService服务对象中的INotificationManager.Stub对象mService

private final IBinder mService = new INotificationManager.Stub(){};

客户端可以通过以下方式来获取和服务端通信的桩对象:

IBinder b = ServiceManager.getService(Context.NOTIFICATION_SERVICE);
INotificationManager service = INotificationManager.Stub.asInterface(b);

SystemUI进程在初始化过程中,会创建一个NotificationListenerService服务类,服务对象中创建一个INotificationListener对象并通过远程过程调用把这个INotificationListener对象注册到NotificationManagerService服务对象的服务管理类子类NotificationListeners对象mListeners中

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/android/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java 

private final NotificationListenerService mNotificationListener =
new NotificationListenerService() {
//...
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
String key = sbn.getKey();
boolean isUpdate = mNotificationData.get(key) != null;
if (isUpdate) {
updateNotification(sbn, rankingMap); //更新Notification
} else {
addNotification(sbn, rankingMap, null /* oldEntry */); //添加Notification
}
}
});
}
}

@Override
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null) {
final String key = sbn.getKey();
mHandler.post(new Runnable() {
@Override
public void run() {
removeNotification(key, rankingMap); //删除Notification
}
});
}
}
//...
};

public void start() {
//...
mNotificationListener.registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL); //把NotificationListenerService对象注册为系统服务并通过和NotificationManagerService服务远程通信
//...
}


/android/frameworks/base/core/java/android/service/notification/NotificationListenerService.java

public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
mSystemContext = context;
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
INotificationManager noMan = getNotificationInterface();
//通过远程过程把INotificationListener注册到NotificationManagerService服务对象中,
//这样NotificationManagerService对象就可以通过INotificationListener通信对象
//直接回调SystemUI进程中的NotificationListenerService对象来操作显示UI
noMan.registerListener(mWrapper, componentName, currentUser);
mCurrentUser = currentUser;
}

private final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE)); //这里就是上面客户端可以获取和服务端通信的桩对象的过程
}
return mNoMan;
}

public void onNotificationPosted(StatusBarNotification sbn) {
// optional //在SystemUI中BaseStatusBar的NotificationListenerService重写了这个方法
}

private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
return;
}
synchronized (mWrapper) {
applyUpdate(update);
try {
if (sbn != null) {
NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
} else {
NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
}
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationPosted", t);
}
}
}

@Override
public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,NotificationRankingUpdate update) {}

@Override
public void onListenerConnected(NotificationRankingUpdate update) {}

@Override
public void onNotificationRankingUpdate(NotificationRankingUpdate update) throws RemoteException {}

@Override
public void onListenerHintsChanged(int hints) throws RemoteException {}

@Override
public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {}

@Override
public void onEdgeNotificationPosted(String pkg, int id, Bundle extra) {}

@Override
public void onEdgeNotificationRemoved(String pkg, int id, Bundle extra) {}
}

Notification调用过程

Notification调用过程可以从应用开始,通过NotificationManager.notify()来发出通知,NotificationManager通过和NotificationManagerService服务对象通信,NotificationManagerService服务对象再利用通过NotificationListeners中监听的服务列表与SystemUI进程启动的系统服务NotificationListenerService中的INotificationListener对象通信,就可以调用SystemUI进程进行显示。

调用过程如下图所示:

对应的代码,如以下所示:

应用程序要发通知或取消通知,只需要获取系统的通知管理服务,调用notify或者cancel来操作通知即可。

1
2
3
4
5
6
7
NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);	//获取通知管理服务
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.drawable.smallicon)
.setContentTitle("This is Title")
.setContentText("This is Content");
Notification n = b.build(); //创建Notification
nm.notify(ID, n); //发通知

NotificationManager是管理通知的服务类,它负责与NotificationManagerService服务对象通信,并通过调用NotificationManagerService服务对象添加、更新、删除通知等等,所支持的功能可以参照远程通信服务接口INotificationManager.aidl中公开的方法。

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
/android/frameworks/base/core/java/android/app/NotificationManager.java

private static INotificationManager sService;

static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification"); //获取系统服务的桩对象
sService = INotificationManager.Stub.asInterface(b); //把桩对象转化成远程通信对象
return sService;
}

public void notify(int id, Notification notification)
{
notify(null, id, notification);
}

public void notify(String tag, int id, Notification notification)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
//...
Notification stripped = notification.clone();
Builder.stripForDelivery(stripped);
try {
//调用远程服务对象的enqueueNotificationWithTag()方法来调用NotificationManagerService对象发出通知
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
} catch (RemoteException e) {
}
}

NotificationManagerService服务对象只是处理逻辑,显示的逻辑是放在系统UI的进程SystemUI中去的,所以NotificationManagerService服务对象处理完逻辑后,还需要远程调用SystemUI进程去更新显示。所以SystemUI进程需要把INotificationListener服务对象注册到NotificationManagerService服务对象中来,当需要更新UI是,就可以通过INotificationListener服务对象回调SystemUI进程中的方法来更新。

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
/android/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 

@Override
public void onStart() {
mListeners = new NotificationListeners();
}

private final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId); //添加或更新Notification
}
}

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
//...
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
//...
//调用服务管理对象mListeners来更新所有注册到mListeners中的NotificationListenerService对象
mListeners.notifyPostedLocked(n, oldSbn);
//实现播放notification的铃声,使led灯亮起来或者震动等操作。buzz:嗡嗡叫,beep: 嘟嘟响,blink: 闪烁
buzzBeepBlinkLocked(r);
}
}
});
idOut[0] = id;
}

public class NotificationListeners extends ManagedServices {

public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
for (final ManagedServiceInfo info : mServices) {
mHandler.post(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, update); //调用更新通知方法
}
});
}
}

private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener)info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
//回调NotificationListenerService对象中的方法onNotificationPosted(),在SystemUI中显示Notification
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}

INotificationListener服务对象是从BaseStatusBar对象中启动的系统服务BNotificationListenerService注册到NotificationManagerService对象中的,当NotificationManagerService服务对象通过INotificationListener服务对象回调SystemUI进程中的方法时,就可以调用BaseStatusBar对象中的方法来更新UI显示了。

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
/android/frameworks/base/core/java/android/service/notification/NotificationListenerService.java 	

private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
return;
}
synchronized (mWrapper) {
applyUpdate(update);
try {
if (sbn != null) {
NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
} else {
NotificationListenerService.this.onNotificationRankingUpdate(mRankingMap);
}
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationPosted", t);
}
}
}
}

/android/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java

private final NotificationListenerService mNotificationListener =
new NotificationListenerService() {
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (sbn != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
String key = sbn.getKey();
boolean isUpdate = mNotificationData.get(key) != null;
if (isUpdate) {
updateNotification(sbn, rankingMap); //更新Notification,
} else {
addNotification(sbn, rankingMap, null /* oldEntry */); //添加Notification
}
}
});
}
}
};

PhoneStatusBar是BaseStatusBar的子类,实现了BaseStatusBar中的相关方法,addNotification()就是其中之一,这个方法是用来添加Notification和状态栏通知图标的。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/android/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java 

public void addNotification(StatusBarNotification notification, RankingMap ranking,
Entry oldEntry) {
//...
Entry shadeEntry = createNotificationViews(notification); //创建状态栏通知图标和通知列表行布局
//...
//把创建的状态栏通知图标和通知列表行布局分别添加到状态栏通知栏和通知列表NotificatioStackScrollLayout中
addNotificationViews(shadeEntry, ranking);
setAreThereNotifications();
//...
}

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) { //创建状态栏通知图标和通知列表行布局
final StatusBarIconView iconView = createIcon(sbn); //创建状态栏通知图标
if (iconView == null) {
return null;
}

NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
if (!inflateViews(entry, mStackScroller)) { //创建展开通知布局列表中的通知一行的布局
handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
return null;
}
return entry;
}

protected StatusBarIconView createIcon(StatusBarNotification sbn) { //创建状态栏通知图标
Notification n = sbn.getNotification();
final StatusBarIconView iconView = new StatusBarIconView(mContext,
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n); //创建状态栏通知图标布局View
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);

final Icon smallIcon = n.getSmallIcon();
if (smallIcon == null) {
handleNotificationError(sbn,
"No small icon in notification from " + sbn.getPackageName());
return null;
}
final StatusBarIcon ic = new StatusBarIcon(
sbn.getUser(),
sbn.getPackageName(),
smallIcon,
n.iconLevel,
n.number,
n.tickerText);
if (!iconView.set(ic)) { //把StatusBarIcon传到状态栏通知图标布局View中
handleNotificationError(sbn, "Couldn't create icon: " + ic);
return null;
}
return iconView;
}

protected boolean inflateViews(Entry entry, ViewGroup parent) { //创建展开通知布局列表中的通知一行的布局
final StatusBarNotification sbn = entry.notification;

RemoteViews contentView = sbn.getNotification().contentView; //通知布局的contentView布局
RemoteViews bigContentView = sbn.getNotification().bigContentView; //通知布局的bigContentView布局
RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView;//通知布局的headsUpContentView布局

Notification publicNotification = sbn.getNotification().publicVersion;

ExpandableNotificationRow row; //创建通知布局列表中的一行的布局
if (entry.row != null) {
} else {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
parent, false);
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
}

NotificationContentView contentContainer = row.getPrivateLayout();
NotificationContentView contentContainerPublic = row.getPublicLayout();
NotificationContentView expandedKnox = row.getKnoxLayout();

mNotificationClicker.register(row, sbn);

View contentViewLocal = null;
View bigContentViewLocal = null;
View headsUpContentViewLocal = null;
try {
contentViewLocal = contentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
mOnClickHandler); //把contentView添加到通知布局列表中通知行容器中
if (bigContentView != null) {
bigContentViewLocal = bigContentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
mOnClickHandler); //把bigContentView添加到通知布局列表中通知行容器中
}
if (headsUpContentView != null) {
headsUpContentViewLocal = headsUpContentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
mOnClickHandler); //把headsUpContentView添加到通知布局列表中通知行容器中
}
} catch (RuntimeException e) {
return false;
}

View publicViewLocal = null;
if (publicNotification != null) {
try {
publicViewLocal = publicNotification.contentView.apply(
sbn.getPackageContext(mContext),
contentContainerPublic, mOnClickHandler);
} catch (RuntimeException e) {
publicViewLocal = null;
}
}
//...
return true;
}

//把创建的状态栏通知图标和通知列表行布局分别添加到状态栏通知栏和通知列表NotificatioStackScrollLayout中
protected void addNotificationViews(Entry entry, RankingMap ranking) {
if (entry == null) {
return;
}
mNotificationData.add(entry, ranking); //先把通知添加到NotificationData中去
updateNotifications();//根据更新后的NotificationData数据更新状态栏通知图标和通知列表NotificationStackScrollLayout布局
}

@Override
protected void updateNotifications() {
mNotificationData.filterAndSort(); //过滤和排序通知的顺序

updateNotificationShade(); //添加或者更新NotificationStackScrollLayout中的Notification
mIconController.updateNotificationIcons(mNotificationData); //添加或者更新状态栏左上角通知栏图标

mNotificationPanel.updateCarrierAndClearLayout(); //更新通信类型和清除布局
}

private void updateNotificationShade() {
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());

//把所有需要显示的Notification添加到toShow列表中
final int N = activeNotifications.size();
for (int i=0; i<N; i++) {
Entry ent = activeNotifications.get(i);
toShow.add(ent.row);
}

//通过判断mStackScroller中各个child是否在toShow列表;不在的话,就添加toRemove列表中,待会一起删除
ArrayList<View> toRemove = new ArrayList<>();
for (int i=0; i< mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {//child是否在toShow列表
toRemove.add(child);
}
}

//把toRemove列表中需要删除的child,都删除掉
for (View remove : toRemove) {
mStackScroller.removeView(remove);
}

//判断toShow列表中,有哪些是新添加的通知(可以通过通知View是否有父容器来判断),新的通知就添加到mStackScroller中
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) { //这里通过通知View是否有父容器来判断这条通知是否是新的
mStackScroller.addView(v); //新通知就添加到mStackScroller中
}
}

//以上已经把需要显示或者删除的通知都处理完了,但是还需要重新调整顺序
//从mStackScroller中顺序判断每个child的顺序是否与toShow列表中的顺序一样
//不一样的,就把顺序调整下
int j = 0;
for (int i = 0; i < mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
ExpandableNotificationRow targetChild = toShow.get(j);
if (child != targetChild) { //顺序不对
mStackScroller.changeViewPosition(targetChild, i); //调整顺序
}
j++;
}

updateNotificationShadeForChildren(); //更新每个Group Notification中的child Notification
updateRowStates(); //更新Notification布局中的Item展开情况、dim情况和锁屏状态下的通知布局情况,很重要的一个方法
updateClearAll(); //更新清楚所有按钮布局
updateEmptyShadeView(); //隐藏"No Notification"
updateQsExpansionEnabled(); //关闭QS功能
mShadeUpdates.check();
}


/android/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java

public void filterAndSort() {
mSortedAndFiltered.clear(); //清除mSortedAndFiltered列表

final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
StatusBarNotification sbn = entry.notification;

if (shouldFilterOut(sbn)) { //判断是否需要过滤掉
continue;
}

mSortedAndFiltered.add(entry); //添加到mSortedAndFiltered列表中
}

Collections.sort(mSortedAndFiltered, mRankingComparator); //重新排序mSortedAndFiltered中的选项
}

StatusBarIconController是状态栏图标控制的类,用来控制状态栏通知图标显示和系统图标显示等等。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/android/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java 

private IconMerger mNotificationIcons; //状态栏通知图标容器对象

public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
PhoneStatusBar phoneStatusBar) {
//...
mNotificationIcons = (IconMerger) statusBar.findViewById(R.id.notificationIcons); //获取状态栏图标的容器类
//...
}

public void updateNotificationIcons(NotificationData notificationData) {
int iconSize = mContext.getResources().getDimensionPixelSize(R.dimen.notification_icon_view_width); //图标宽度
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
iconSize + 2*mIconHPadding, mPhoneStatusBar.getStatusBarHeight()); //状态栏图标的布局参数(宽度x高度)

ArrayList<NotificationData.Entry> activeNotifications =
notificationData.getActiveNotifications();
final int N = activeNotifications.size();
ArrayList<StatusBarIconView> toShow = new ArrayList<>(N);

//把所有需要显示的Notification添加到toShow列表中
for (int i = 0; i < N; i++) {
NotificationData.Entry ent = activeNotifications.get(i);
//过滤环境通知,例如:插USB或者充电线时的通知,是不需要显示状态栏中的图标的
if (notificationData.isAmbient(ent.key)
&& !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
continue;
}
if (!PhoneStatusBar.isTopLevelChild(ent)) { //过滤分组的组图标
continue;
}
if ((ent.notification.getNotification().secFlags
& Notification.SEC_FLAG_HIDE_NOTIFICATION_ICON) !=0) { //过滤掉设置了隐藏状态栏图标的通知
continue;
}
if(!mPhoneStatusBar.shouldShowOnIndicator(ent.notification.getKey())) { //过滤设置了隐藏图标的包的通知
continue;
}
toShow.add(ent.icon);
}

//通过判断mNotificationIcons中各个child是否在toShow列表;不在的话,就添加toRemove列表中,待会一起删除
ArrayList<View> toRemove = new ArrayList<>();
for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
View child = mNotificationIcons.getChildAt(i);
if (!toShow.contains(child)) { //child是否在toShow列表
toRemove.add(child);
}
}

//把toRemove列表中需要删除的child,都删除掉
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
mNotificationIcons.removeView(toRemove.get(i));
}

//判断toShow列表中,有哪些是新添加的通知(可以通过通知View是否有父容器来判断),新的通知就添加到mNotificationIcons中
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) { //这里通过通知View是否有父容器来判断这条通知是否是新的
mNotificationIcons.addView(v, i, params); //新通知就添加到mStackScroller中
}
}

//以上已经把需要显示或者删除的通知都处理完了,但是还需要重新调整顺序
//从mNotificationIcons中顺序判断每个child的顺序是否与toShow列表中的顺序一样
//不一样的,就把顺序调整下
final int childCount = mNotificationIcons.getChildCount();
for (int i = 0; i < childCount; i++) {
View actual = mNotificationIcons.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) { //顺序正确的就不处理
continue;
}
mNotificationIcons.removeView(expected); //把顺序错误的View先删除
mNotificationIcons.addView(expected, i); //再添加到正确的顺序位置上
}

applyNotificationIconsTint(); //更新状态栏图标的颜色
}


/android/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java

public class IconMerger extends LinearLayout{ //状态栏通知图标容器类

}

把状态栏通知图标和通知行布局都分别添加到相应的容器类后,显示部分的逻辑也就完成了。

Notification通知提示音响起过程

SystemUI的RingtonePlayer服务通过IAudioService.setRingtonePlayer(IRingtonePlayer)把IRingtonePlayer实现的回调接口对象注册到AudioService服务中,第三方App调用NotificationManager.notify()时,会调用NotificationManagerService中的enqueueNotificationInternal方法中的buzzBeepBlinkLocked()方法,这个方法会通过IAudioService.getRingtonePlayer()获取AudioServoce中的IRingtonePlayer对象,并调用回调方法来调用SystenUI.RingtonePlayer.mCallback的playAsync()来实现播放notification铃声、震动、闪烁灯功能。

调用过程如下图所示:

对应的代码,如以下所示:

启动AudioService服务过程,

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
/android/frameworks/base/services/java/com/android/server/SystemServer.java 

private void startOtherServices() {
AudioService audioService = null;
audioService = new AudioService(context);
ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
}

/android/frameworks/base/services/core/java/com/android/server/audio/AudioService.java

public class AudioService extends IAudioService.Stub {

private volatile IRingtonePlayer mRingtonePlayer;

@Override
public void setRingtonePlayer(IRingtonePlayer player) {
mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
mRingtonePlayer = player;
}

@Override
public IRingtonePlayer getRingtonePlayer() {
return mRingtonePlayer;
}
}

启动SystemUI的RingtonePlayer服务过程,

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
/android/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java 

private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class, //RingtinePlayer服务
com.android.systemui.keyboard.KeyboardUI.class,
};

public void startServicesIfNeeded() {
//...
final int N = SERVICES.length;
for (int i=0; i<N; i++) {
Class<?> cl = SERVICES[i];
mServices[i] = (SystemUI)cl.newInstance();
mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
mServices[i].start(); //启动RingtinePlayer服务
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
//...
}

RingtonePlayer是运行在SystemUI进程的服务,RingtonePlayer服务会获取AudioService服务对象,并把IRingtonePlayer对象传给AudioService服务对象中去,其它模块通过AudioService.getRingtonePlayer()来控制RingtonePlayer服务播放提示音的功能。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
@Override
public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
throws RemoteException {
Client client;
synchronized (mClients) {
client = mClients.get(token);
if (client == null) {
final UserHandle user = Binder.getCallingUserHandle();
client = new Client(token, uri, user, aa);
token.linkToDeath(client, 0);
mClients.put(token, client);
}
}
client.mRingtone.setLooping(looping);
client.mRingtone.setVolume(volume);
client.mRingtone.play(); //同步播放提示音
}

@Override
public void stop(IBinder token) {
Client client;
synchronized (mClients) {
client = mClients.remove(token);
}
if (client != null) {
client.mToken.unlinkToDeath(client, 0);
client.mRingtone.stop();
}
}

@Override
public boolean isPlaying(IBinder token) {
Client client;
synchronized (mClients) {
client = mClients.get(token);
}
if (client != null) {
return client.mRingtone.isPlaying();
} else {
return false;
}
}

@Override
public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
Client client;
synchronized (mClients) {
client = mClients.get(token);
}
if (client != null) {
client.mRingtone.setVolume(volume);
client.mRingtone.setLooping(looping);
}
}

@Override
public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Async playback only available from system UID.");
}
mAsyncPlayer.play(getContextForUser(user), uri, looping, aa); //把播放任务放到异步队列中
}

@Override
public void stopAsync() {
if (LOGD) Log.d(TAG, "stopAsync()");
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Async playback only available from system UID.");
}
mAsyncPlayer.stop();
}

@Override
public String getTitle(Uri uri) {
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false /*followSettingsUri*/, false /*allowRemote*/);
}

@Override
public IBinder setOnCompletionListener(INotificationPlayerOnCompletionListener l) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException(
"setOnCompletionListener only available from system UID.");
}
return mAsyncPlayer.setOnCompletionListener(l);
}
};

如果需要调用异步方式来播放提示音,就需要用到NotificationPlayer这个类,它会把播放任务保存到队列中,通过线程一个一个为队列中每个提示音播放任务创建一个播放线程并执行。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/android/frameworks/base/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java 

private static final class Command { //一个异步任务对应一个Command对象
int code;
Context context;
Uri uri;
boolean looping;
AudioAttributes attributes;
long requestTime;
}

private LinkedList<Command> mCmdQueue = new LinkedList(); //异步任务队列对象

public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
Command cmd = new Command();
cmd.requestTime = SystemClock.uptimeMillis();
cmd.code = PLAY;
cmd.context = context;
cmd.uri = uri;
cmd.looping = looping;
cmd.attributes = attributes;
synchronized (mCmdQueue) {
enqueueLocked(cmd); //把异步任务加入队列中
mState = PLAY;
}
}

private void enqueueLocked(Command cmd) {
mCmdQueue.add(cmd); //把异步任务加入队列对象mCmdQueue中
if (mThread == null) { //如果执行任务线程已经停止,创建线程并开始执行
acquireWakeLock();
mThread = new CmdThread(); //创建执行任务的线程
mThread.start(); //启动线程
}
}

private final class CmdThread extends java.lang.Thread {
CmdThread() {
super("NotificationPlayer-" + mTag);
}

public void run() {
while (true) {
Command cmd = null;

synchronized (mCmdQueue) {
cmd = mCmdQueue.removeFirst(); //取出队列中第一个任务
}

switch (cmd.code) { //任务类型
case PLAY: //播放提示音任务
startSound(cmd); //播放提示音
break;
case STOP: //停止提示音任务
if (mPlayer != null) {
long delay = SystemClock.uptimeMillis() - cmd.requestTime;
if (delay > 1000) { //如果异步时间超过1s,打印出来,方便调试
Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
}
mPlayer.stop();
mPlayer.release();
mPlayer = null;
synchronized(mQueueAudioFocusLock) {
if (mAudioManagerWithAudioFocus != null) {
mAudioManagerWithAudioFocus.abandonAudioFocus(null);
mAudioManagerWithAudioFocus = null;
}
}
if((mLooper != null)
&& (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
mLooper.quit();
}
} else {
Log.w(mTag, "STOP command without a player");
}
break;
}

synchronized (mCmdQueue) {
if (mCmdQueue.size() == 0) {
mThread = null;
releaseWakeLock();
return;
}
}
}
}
}

private void startSound(Command cmd) {
try {
synchronized(mCompletionHandlingLock) {
if((mLooper != null)
&& (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
mLooper.quit();
}
//为每个播放任务创建一个播放提示音线程
mCompletionThread = new CreationAndCompletionThread(cmd);
synchronized(mCompletionThread) {
mCompletionThread.start(); //开始执行线程
mCompletionThread.wait(); //等待线程执行完
}
}
long delay = SystemClock.uptimeMillis() - cmd.requestTime;
if (delay > 1000) { //如果异步时间超过1s,打印出来,方便调试
Log.w(mTag, "Notification sound delayed by " + delay + "msecs");
}
}
catch (Exception e) {
Log.w(mTag, "error loading sound for " + cmd.uri, e);
}
}

private final class CreationAndCompletionThread extends Thread {
public Command mCmd;
public CreationAndCompletionThread(Command cmd) {
super();
mCmd = cmd;
}
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();
synchronized(this) {
AudioManager audioManager =
(AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
try {
MediaPlayer player = new MediaPlayer(); //创建一个MediaPlayer对象
player.setAudioAttributes(mCmd.attributes);
player.setDataSource(mCmd.context, mCmd.uri);
player.setLooping(mCmd.looping);
player.prepare();
if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
&& (mCmd.uri.getEncodedPath().length() > 0)) {
if (!audioManager.isMusicActiveRemotely()) {
synchronized(mQueueAudioFocusLock) {
if (mAudioManagerWithAudioFocus == null) {
if (mDebug) Log.d(mTag, "requesting AudioFocus");
if (mCmd.looping) { //获取长时间音频焦点
audioManager.requestAudioFocus(null,
AudioAttributes.toLegacyStreamType(mCmd.attributes),
AudioManager.AUDIOFOCUS_GAIN);
} else { //获取临时音频焦点
audioManager.requestAudioFocus(null,
AudioAttributes.toLegacyStreamType(mCmd.attributes),
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
}
mAudioManagerWithAudioFocus = audioManager;
} else {
if (mDebug) Log.d(mTag, "AudioFocus was previously requested");
}
}
}
}
player.setOnCompletionListener(NotificationPlayer.this);
player.setOnErrorListener(NotificationPlayer.this);
player.start(); //开始播放提示音
if (mPlayer != null) {
mPlayer.release();
}
mPlayer = player;
}
catch (Exception e) {
Log.w(mTag, "error loading sound for " + mCmd.uri, e);
}
this.notify();
}
Looper.loop();
}
};

应用或者服务通过NotificationManager调用notify()发出通过,NotificationManager通过和NotificationManagerService服务通信,调用发出通知,并调用buzzBeepBlinkLocked()方法来触发通知提示音、震动或者led闪烁。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/android/frameworks/base/core/java/android/app/NotificationManager.java

public void notify(int id, Notification notification)
{
notify(null, id, notification);
}

public void notify(String tag, int id, Notification notification)
{
INotificationManager service = getService();
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
}


/android/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

private final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {

enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId);
}
}

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, callingUid, callingPid, score, notification, user);
NotificationRecord r = new NotificationRecord(n, score);
NotificationRecord old = mNotificationsByKey.get(n.getKey());
//调用服务管理对象mListeners来更新所有注册到mListeners中的NotificationListenerService对象
mListeners.notifyPostedLocked(n, oldSbn);
//实现播放notification的提示音,使led灯亮起来或者震动等操作。buzz:嗡嗡叫,beep: 嘟嘟响,blink: 闪烁
buzzBeepBlinkLocked(r);
}
}
});
}

//实现播放notification的提示音,使led灯亮起来或者震动等操作。buzz:嗡嗡叫,beep: 嘟嘟响,blink: 闪烁
private void buzzBeepBlinkLocked(NotificationRecord record) {
boolean buzz = false; //震动
boolean beep = false; //提示音
boolean blink = false; //闪烁
final Notification notification = record.sbn.getNotification();
// Should this notification make noise, vibe, or use the LED?
final boolean aboveThreshold = record.score >= SCORE_INTERRUPTION_THRESHOLD;
final boolean canInterrupt = aboveThreshold && !record.isIntercepted();
// If we're not supposed to beep, vibrate, etc. then don't.
final String disableEffects = disableNotificationEffects(record);
if (disableEffects != null) {
ZenLog.traceDisableEffects(record, disableEffects);
}
boolean smsRingtone = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_sms_ringtone_incall);
if ((disableEffects == null || (smsRingtone && mInCall)) //通话期间来短信铃声
&& (!(record.isUpdate
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& (record.getUserId() == UserHandle.USER_ALL ||
record.getUserId() == currentUser ||
mUserProfiles.isCurrentProfile(record.getUserId()))
&& canInterrupt
&& mSystemReady
&& mAudioManager != null) { //判断是否需要提示音或者震动

//这里会发出一个Notification的AccessibilityEvent,这样在辅助服务中才能收到这个事件,
//微信抢红包的功能就是通过这个辅助事件才得以实现的
sendAccessibilityEvent(notification, record.sbn.getPackageName());

//提示音相关
if(!isPrayModeNotiOn(mContext)) { //判断是否是在祈祷模式下
//判断Notification是否设置了使用默认提示音或者Notification设置的提示音文件刚好是默认提示音文件
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
Settings.System.DEFAULT_NOTIFICATION_URI
.equals(notification.sound);
Uri soundUri = null;
boolean hasValidSound = false;
if (useDefaultSound) { //判断是否使用默认Notification提示音
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; //获取默认提示音Uri
ContentResolver resolver = getContext().getContentResolver();
hasValidSound = Settings.System.getString(resolver,
Settings.System.NOTIFICATION_SOUND) != null; //默认提示音文件是否有效
} else if (notification.sound != null) {
soundUri = notification.sound; //获取Notification的提示音Uri
hasValidSound = (soundUri != null); //提示音文件是否有效
}

if (hasValidSound) { //判断是否是有效提示音
boolean looping =
(notification.flags & Notification.FLAG_INSISTENT) != 0; //是否设置了循环
AudioAttributes audioAttributes;
if (notification.audioAttributes != null) {
audioAttributes = notification.audioAttributes;
} else {
audioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
}
mSoundNotificationKey = record.getKey();
//Notification提示音音量是否为0和音频焦点是否可用
if ((mAudioManager.getStreamVolume(
AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
&& !mAudioManager.isAudioFocusExclusive()) {
final long identity = Binder.clearCallingIdentity();
try {
//-----这里或通过AudioService来获取IRingtonePlayer对象,最终会调用SystemUI进程中的RingtonePlayer来播放提示音
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
player.playAsync(soundUri, record.sbn.getUser(), looping,
audioAttributes); //异步方式播放提示音
beep = true;
mIsPlaying = true;

if (mMethodRingtonePlayer != null) { //翻转停止播放提示音功能的相关逻辑
if (mOverTurnPlayer != null && mOverTurnPlayer.isEnable() && !mOverTurnPlayer.isRegister()) {
mOverTurnPlayer.register();
//... //翻转停止播放提示音功能的相关逻辑,这里先不赘述
}
}
}
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}

//震动相关
final boolean hasCustomVibrate = notification.vibrate != null; //Notification是否设置震动
final boolean convertSoundToVibration =
!hasCustomVibrate
&& hasValidSound
&& (mAudioManager.getRingerMode()
== AudioManager.RINGER_MODE_VIBRATE); //震动模式下,需要把通知提示音变成震动

final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0; //Notification是否设置了默认震动

final boolean useHaptic = doesItUseHaptic(notification.haptic); //Notification是否设置了触屏反馈

if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate ||useHaptic)
&& !(mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT)) { //判断是否需要震动
mVibrateNotificationKey = record.getKey();
buzz = true;
doVibrate((useDefaultVibrate || convertSoundToVibration), useHaptic, notification); //执行震动
}
}

if(beep || buzz) {
AccessibilityManager accManager = AccessibilityManager.getInstance(getContext());
accManager.onFlashNotification(record.getNotification().category);
}
}

//led灯相关
boolean wasShowLights = mLights.remove(record.getKey());
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) {
mLights.add(record.getKey());
updateLightsLocked(); //更新led灯闪烁
if (mUseAttentionLight) {
mAttentionLight.pulse();
}
blink = true;
} else if (wasShowLights) {
updateLightsLocked(); //更新led灯闪烁
}

if ((buzz || beep || blink) && !isPrayModeNotiOn(mContext)) {
EventLogTags.writeNotificationAlert(record.getKey(),
buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
mHandler.post(mBuzzBeepBlinked);
}
}

private void doVibrate(boolean useDefaultVibrate, boolean useHaptic, Notification n) {
if (useHaptic) {
mVibrator.vibrate(n.haptic, -1, null,
Vibrator.MagnitudeTypes.NotificationMagnitude); //执行震动
} else if (useDefaultVibrate) {
long identity = Binder.clearCallingIdentity();
try {
mVibrator.vibrate(HapticFeedbackConstants.VIBE_NOTIFICATION, -1, null,
Vibrator.MagnitudeTypes.NotificationMagnitude); //执行震动
} finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity2 = Binder.clearCallingIdentity();
try {
mVibrator.vibrate(n.vibrate, ((n.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1, null,
Vibrator.MagnitudeTypes.NotificationMagnitude); //执行震动
} finally {
Binder.restoreCallingIdentity(identity2);
}
}
}

NotificationManagerService服务中就是通过AudioService服务获取IRingtonePlayer对象来控制SystemUI进程进行播放提示音的,

final IRingtonePlayer player = mAudioManager.getRingtonePlayer();    
player.playAsync(soundUri, record.sbn.getUser(), looping, audioAttributes);    

SystemUI进程播放提示音的流程如下:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/android/frameworks/base/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java 	

private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);

private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
@Override
public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
mAsyncPlayer.play(getContextForUser(user), uri, looping, aa); //异步播放提示音
}
}


/android/frameworks/base/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java

public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
Command cmd = new Command();
cmd.requestTime = SystemClock.uptimeMillis();
cmd.code = PLAY;
cmd.context = context;
cmd.uri = uri;
cmd.looping = looping;
cmd.attributes = attributes;
synchronized (mCmdQueue) {
enqueueLocked(cmd); //把异步播放提示音放到播放任务队列中
mState = PLAY;
}
}

private void enqueueLocked(Command cmd) {
mCmdQueue.add(cmd);
if (mThread == null) {
acquireWakeLock();
mThread = new CmdThread(); //创建处理队列任务的线程
mThread.start(); //启动线程开始处理队列中的任务
}
}

private final class CmdThread extends java.lang.Thread {
public void run() {
while (true) {
Command cmd = null;
synchronized (mCmdQueue) {
cmd = mCmdQueue.removeFirst(); //取队列中第一条播放任务
}
switch (cmd.code) {
case PLAY:
startSound(cmd); //开始处理播放提示音任务
break;
}
}
}

private void startSound(Command cmd) {
synchronized(mCompletionHandlingLock) {
mCompletionThread = new CreationAndCompletionThread(cmd);
synchronized(mCompletionThread) {
mCompletionThread.start(); //执行播放提示音线程
mCompletionThread.wait();
}
}
}

private final class CreationAndCompletionThread extends Thread {
public Command mCmd;
public CreationAndCompletionThread(Command cmd) {
super();
mCmd = cmd;
}
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();
synchronized(this) {
try {
MediaPlayer player = new MediaPlayer();
player.setAudioAttributes(mCmd.attributes);
player.setDataSource(mCmd.context, mCmd.uri);
player.setLooping(mCmd.looping);
player.prepare();
player.start(); //播放提示音
if (mPlayer != null) {
mPlayer.release();
}
catch (Exception e) {
Log.w(mTag, "error loading sound for " + mCmd.uri, e);
}
this.notify();
}
Looper.loop();
}
};

总结

要理解Notification框架的原理,需要理清NotificationManager和NotificationManagerService之间是怎么通信的,NotificationManagerService和SystemUI之间是怎么通信的。INotificationManager.Stub不仅作为NotificationManagerService和NotificationManager的远程通信方式,也是NotificationManagerService和SystemUI的远程通信方式,不过SystemUI进程会创建和启动一个系统服务NotificationListenerService,这个系统服务通过INotificationManager.Stub把INotificationListener.Stub对象远程传给NotificationListenerService服务中,让NotificationListenerService服务通过INotificationListener.Stub对象和系统服务NotificationListenerService通信,系统服务NotificationListenerService再调用SystemUI进程来更新UI。

23种设计模式的结构图

发表于 2017-02-18 | 分类于 设计模式 | 阅读次数:

设计模式分类

Abstract Factory(抽象工厂)

意图:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

适用性:

  • 一个系统要独立于它的产品的创建、组合和表示时。
  • 一个系统要由多个产品系列中的一个来配置时。
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时。

Builder(建造者)

意图:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用性:

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

Factory Method(工厂方法)

意图:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

适用性:

  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

Prototype(原型)

意图:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

适用性:

  • 当一个系统应该独立于它的产品创建、构成和表示时。
  • 当要实例化的类时在运行时刻指定时,例如通过动态装载。
  • 为了避免创建一个与产品类层次平行的工厂类层次时。
  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

Singleton(单例)

意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性:

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

Adapter Class/Object(适配器)

意图:
将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用性:

  • 你想使用一个已经存在的类,而它的接口不符合你的需求。
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅适用于对象Adapter )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

Bridge(桥接)

意图:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。

适用性:

  • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
  • (C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。
  • 有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh 称这种类层次结构为“嵌套的普化”(nested generalizations )。
  • 想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是Coplien 的String 类[ Cop92 ],在这个类中多个对象可以共享同一个字符串表示(StringRep )。

Composite(组合)

意图:
将对象组合成树形结构以表示“部分-整体”的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。

适用性:

  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

Decorator(装饰)

意图:
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。

适用性:

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤消的职责。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

Facade(外观)

意图:
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

适用性:

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。

Flyweight(享元)

意图:
运用共享技术有效地支持大量细粒度的对象。

适用性:

  • 一个应用程序使用了大量的对象。
  • 完全由于使用大量的对象,造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识。由于Flyweight 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。

Proxy(代理)

意图:
为其他对象提供一种代理以控制对这个对象的访问。

适用性:
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一 些可以使用Proxy 模式常见情况:

  • 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用NXProxy 类实现了这一目的。Coplien[Cop92] 称这种代理为“大使” (Ambassador )。
  • 虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的ImageProxy 就是这样一种代理的例子。
  • 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。例如,在Choices 操作系统[ CIRM93]中KemelProxies为操作系统对象提供 了访问保护。
  • 智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括:对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为SmartPointers[Ede92 ] )。当第一次引用一个持久对象时,将它装入内存。在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

Chain of Responsibility(责任链)

意图:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

适用性:

  • 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可处理一个请求的对象集合应被动态指定。

Command(命令)

意图:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

适用性:

  • 抽象出待执行的动作以参数化某对象,你可用过程语言中的回调(call back)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。
  • 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
  • 支持取消操作。Command的Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command 接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
  • 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务( transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。

Interpreter(解释器)

意图:
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

适用性:
当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

  • 该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式, 这样可以节省空间而且还可能节省时间。
  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。

Iterator(迭代器)

意图:
提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

适用性:

  • 访问一个聚合对象的内容而无需暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。

Mediator(中介者)

意图:
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

适用性:

  • 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
  • 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
  • 想定制一个分布在多个类中的行为,而又不想生成太多的子类。

Memento(备忘录)

意图:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

适用性:

  • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

Observer(观察者)

意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。

适用性:

  • 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

State(状态)

意图:
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

适用性:

  • 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常, 有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

Strategy(策略)

意图:
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

适用性:

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  • 一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

Template Method(模板方法)

意图:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

适用性:

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke 和Johnson所描述过的“重分解以一般化”的一个很好的例子[ OJ93 ]。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 控制子类扩展。模板方法只在特定点调用“hook ”操作(参见效果一节),这样就只允许在这些点进行扩展。

Visitor(访问者)

意图:
表示一个作用于某对象结构中的各元素的操作。它允许在不改变歌元素的类的前提下定义作用于这些元素的新操作。

适用性:

  • 一个对象结构包含很多类对象,它们有不同的接口,而用户想对这些对象实施一些依赖于某具体类的操作。
  • 需要对一个对象结构种的对象进行很多不同的并且不相关的操作,而有想要避免这些操作“污染”这些对象的类。 Visitor 使得用户可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用 Visitor 模式让每个应用仅包含需要用到的操作。
  • 定义对象结构的类很少改变,但经常需要在结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

面向对象设计原则

发表于 2017-01-14 | 分类于 设计模式 | 阅读次数:

面向对象设计原则概述

对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。在面向对象设计中,可维护性的复用是以设计原则为基础的。每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结构的设计水平。

面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则。面向对象设计原则也是我们用于评价一个设计模式的使用效果的重要指标之一,在设计模式的学习中,大家经常会看到诸如“XXX模式符合XXX原则”、“XXX模式违反了XXX原则”这样的语句。

最常见的7种面向对象设计原则如下表所示:

单一职责原则

单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。单一职责原则定义如下:

单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。

单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。

开闭原则

开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。开闭原则由Bertrand Meyer于1988年提出,其定义如下:

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

在开闭原则的定义中,软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。

任何软件都需要面临一个很重要的问题,即它们的需求会随时间的推移而发生变化。当软件系统需要面对新的需求时,我们应该尽量保证系统的设计框架是稳定的。如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。随着软件规模越来越大,软件寿命越来越长,软件维护成本越来越高,设计满足开闭原则的软件系统也变得越来越重要。

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

里氏代换原则

里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述如下:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,因此我们一般使用它的另一个通俗版定义:

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

例如有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。

里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

在使用里氏代换原则时需要注意如下几个问题:

  1. 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。

  2. 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

  3. Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。

依赖倒转原则

如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现。依赖倒转原则是Robert C. Martin在1996年为“C++Reporter”所写的专栏Engineering Notebook的第三篇,后来加入到他在2002年出版的经典著作“Agile Software Development, Principles, Patterns, and Practices”一书中。依赖倒转原则定义如下:

依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。

在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。

在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。

扩展
软件工程大师Martin Fowler在其文章Inversion of Control Containers and the Dependency Injection pattern中对依赖注入进行了深入的分析,参考链接:
http://martinfowler.com/articles/injection.html

接口隔离原则

接口隔离原则定义如下:

接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。这里的“接口”往往有两种不同的含义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象;另外一种是指某种语言具体的“接口”定义,有严格的定义和结构,比如Java语言中的interface。对于这两种不同的含义,ISP的表达方式以及含义都有所不同:

  1. 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时,这个原则可以叫做“角色隔离原则”。

  2. 如果把“接口”理解成狭义的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服务”,即为不同的客户端提供宽窄不同的接口。

合成复用原则

合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下:

合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。

合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简言之:复用时要尽量使用组合/聚合关系(关联关系),少用继承。

在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承,但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;而且继承只能在有限的环境中使用(如类没有声明为不能被继承)。

扩展
对于继承的深入理解,大家可以参考《软件架构设计》一书作者温昱先生的文章——《见山只是山见水只是水——提升对继承的认识》。

由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为“黑箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作;合成复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。

一般而言,如果两个类之间是“Has-A”的关系应使用组合或聚合,如果是“Is-A”关系可使用继承。”Is-A”是严格的分类学意义上的定义,意思是一个类是另一个类的”一种”;而”Has-A”则不同,它表示某一个角色具有某一项责任。

迪米特法则

迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP),其定义如下:

迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。

迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其朋友包括以下几类:

  1. 当前对象本身(this);
  2. 以参数形式传入到当前对象方法中的对象;
  3. 当前对象的成员对象;
  4. 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
  5. 当前对象所创建的对象。

任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。在应用迪米特法则时,一个对象只能与直接朋友发生交互,不要与“陌生人”发生直接交互,这样做可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。

迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。

在将迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

状态栏上闹钟图标显示过程简介

发表于 2016-03-21 | 分类于 Android , SystemUI | 阅读次数:

1. 状态栏上闹钟图标显示过程简介

闹钟App设置闹钟时,都是通过AlarmManager,即AlarmManagerService服务来实现的,
每次创建或者更新闹钟时,都会调用AlarmManager.setExact()来向AlarmManagerService服务设置闹钟响的时间;

AlarmManagerService服务通过native函数set()来设置内核中闹钟,并且发送广播AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED来通知SystemUI进行系统图标的显示,同时会循环调用native函数waitForAlarm()监听到Alarm变化,当监听到有Alarm时间到了,也会发送AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED广播出去;

SystemUI的PhoneStatusBarPolicy.mIntentReceiver会监听这个广播,当接收到AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED广播后,调用StatusBarManagerService服务来设置状态上显示的system icon和icon visibility:

mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);

在StatusBarManagerService.setIcon()中,服务会通过IStatusBar mBar来处理icon的显示情况,
mBar就是SystemUI中BaseStatusBar的IStatusBar.Stub子类CommandQueue类mCommandQueue对象,通过注册到StatusBarManagerService服务中的,
BaseStatusBar创建mCommandQueue对象时,还把实现了CommandQueue.Callbacks接口的BaseStatusBar对象也传递过去,
CommandQueue mCommandQueue = new CommandQueue(this,mIconList);
这样StatusBarManagerService服务就可以通过SystemUI中的IStatusBar.Stub子类CommandQueue类mCommandQueue对象来调用SystemUI来处理icon显示

注意:因为notification icon和system icon是状态栏上左右两边的不同的图标,左边是通知图标(notification icon),右边的是系统图标(system icon); system icon显示是不受notification影响的,所以调用NotificationManager发出通知是不会在system icon区域显示图标的,只会在notification icon区域显示,所以这里实现闹钟图标显示时是Alarm通过AlarmManagerService发广播给SystemUI的PhoneStatusBarPolicy,SystemUI再调用StatusBarManagerService服务添加icon,StatusBarManagerService服务再通过SystemUI的CommandQueue对象来实现在SystemUI中显示icon的。

2. 状态栏上闹钟图标显示过程的时序图

3. 状态栏上闹钟图标显示过程的实现代码

1).闹钟App中调用设置闹钟:

AlarmProvider.java

1
2
3
4
5
6
public static void enableNextAlert(Context context) {
...
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.setExact(AlarmManager.RTC_WAKEUP, item.alarmAlertTime, mPendingIntent);
...
}

2).AlarmManager设置闹钟:

在闹钟App中调用AlarmManager的setExact()方法后,这里AlarmManager就会通过IAlarmManager.Stub和AlarmManagerServie通信Binder通信。

AlarmManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
}

private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
...
try {
mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
workSource, alarmClock);
} catch (RemoteException ex) {
}
}

3).AlarmManagerService设置闹钟:

更新SystemUI状态栏中系统图标闹钟图标的广播是从AlarmManagerService这个服务中发出来了,这样可以所有App中设置闹钟的逻辑都会集中在这个服务中来处理。

AlarmManagerService.java

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
private final IBinder mService = new IAlarmManager.Stub() {
@Override
public void set(String callingPackage,
int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
...
setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
...
}
}

void setImpl(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int callingUid, String callingPackage) {
...
/* AppCore: Restrict 500 alarms for each App } */
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
interval, operation, directReceiver, listenerTag, flags, true, workSource,
alarmClock, callingUid, callingPackage);
...
}

private Alarm setImplLocked(int type, long when, long whenElapsed, long windowLength,
long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
String listenerTag, int flags, boolean doValidate, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
...
setImplLocked(a, false, doValidate);
return a;
}

private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
...
rescheduleKernelAlarmsLocked(); //调用内核的native方法设置闹钟
updateNextAlarmClockLocked(); //更新状态栏中显示的系统图标状态
...
}

private void updateNextAlarmClockLocked() {
...
updateNextAlarmInfoForUserLocked(userId, newAlarm); //更新指定userId状态栏中显示的系统图标状态
...
}

private void updateNextAlarmInfoForUserLocked(int userId,
AlarmManager.AlarmClockInfo alarmClock) {
...
mHandler.removeMessages(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED);
mHandler.sendEmptyMessage(AlarmHandler.SEND_NEXT_ALARM_CLOCK_CHANGED); //通过UI线程去发送广播
}

private class AlarmHandler extends Handler {
public void handleMessage(Message msg) {
...
switch (msg.what) {
case SEND_NEXT_ALARM_CLOCK_CHANGED:
sendNextAlarmClockChanged();
break;
...
}
}

private void sendNextAlarmClockChanged() {
....
getContext().sendBroadcastAsUser(NEXT_ALARM_CLOCK_CHANGED_INTENT,
new UserHandle(userId)); //发送广播去显示状态栏中的系统图标
....
}

private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT =
new Intent(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)
.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);

以下是如何调用内核来设置闹钟和闹钟时间到后的处理逻辑,可以参考如下,不过这里我们只是跟踪状态栏上系统图标中闹钟图标的显示过程,所以就不深入跟踪了,有兴趣的同事可以自己研究。

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
void rescheduleKernelAlarmsLocked() {				//调用内核的native方法设置闹钟
...
setLocked(ELAPSED_REALTIME, nextNonWakeup);
...
}

private void setLocked(int type, long when) {
...
set(mNativeData, type, alarmSeconds, alarmNanoseconds);
...
}

private native void set(long nativeData, int type, long seconds, long nanoseconds);

private class AlarmThread extends Thread
{
public AlarmThread()
{
super("AlarmManager");
}
public void run()
{
while (true)
{
...
int result = waitForAlarm(mNativeData); //循环调用阻塞函数waitForAlarm()来监听到Alarm变化
boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
rescheduleKernelAlarmsLocked(); //调用内核的native方法设置闹钟
updateNextAlarmClockLocked(); //更新状态栏中显示的系统图标状态
}
...
}
}
}

4). SystemUI接收广播:

PhoneStatusBarPolicy.java

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
private final StatusbarManager mService;

private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
updateAlarm();
}
...
}
};

public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
CastController cast, HotspotController hotspot, UserInfoController userInfoController,
BluetoothController bluetooth, RotationLockController rotationLockController,
DataSaverController dataSaver) {

// listen for broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
...
// Alarm clock
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, false);
}

private void updateAlarm() {
final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
if (mCurrentUserSetup && hasAlarm) {
//通过StatusbarManagerService去更新系统图标
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
}
//通过StatusbarManagerService去显示系统图标
mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);
}

5). StatusBarManagerService调用SystemUI的通信对象来添加系统图标:

StatusBarManagerService.java

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
private volatile IStatusBar mBar;

@Override
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
int switches[], List<IBinder> binders) {
...
mBar = bar;
...
}

@Override
public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
String contentDescription) {
int index = mIcons.getSlotIndex(slot);
StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,
iconLevel, 0,
contentDescription);
mIcons.setIcon(index, icon);
if (mBar != null) {
try {
mBar.setIcon(index, icon);
} catch (RemoteException ex) {
}
}
}

@Override
public void setIconVisibility(String slot, boolean visible) {
int index = mIcons.getSlotIndex(slot);
StatusBarIcon icon = mIcons.getIcon(index);
if (icon.visible != visible) {
icon.visible = visible;
if (mBar != null) {
try {
mBar.setIcon(index, icon);
} catch (RemoteException ex) {
}
}
}
}
}

这里的IStatusBar其实就是SystemUI中的CommandQueue类,它是IStatusBar.Stub的子类,它是在BaseStatusBar的start()方法中通过获取StatusBarManagerService服务对象,调用服务对象的registerStatusBar(IStatusBar bar, StatusBarIconList iconList,int switches[], List binders)方法把CommandQueue对象注册到StatusBarManagerService服务对象中来方便StatusBarManagerService服务和SystemUI进程之间进行通信的。BaseStatusBar中建立和StatusBarManagerService服务通信的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected IStatusBarService mBarService;

public void start() {
...
mCommandQueue = new CommandQueue(this, mIconList);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
try {
mBarService.registerStatusBar(mCommandQueue, mIconList, switches, binders);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
...
}

6). SystemUI中CommandQueue通信接口:

CommandQueue.java

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
private Callbacks mCallbacks;

public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
mCallbacks = callbacks; //mCallbacks是BaseStatusBar和PhoneStatusBar实现CommandQueue.Callbacks的接口对象
mList = list;
}

public void setIcon(int index, StatusBarIcon icon) {
synchronized (mList) {
int what = MSG_ICON | index;
mHandler.removeMessages(what);
mHandler.obtainMessage(what, OP_SET_ICON, 0, icon.clone()).sendToTarget(); //统一发到handler中处理
}
}

private final class H extends Handler {
public void handleMessage(Message msg) {
final int what = msg.what & MSG_MASK;
switch (what) {
case MSG_ICON: {
final int index = msg.what & INDEX_MASK;
final int viewIndex = mList.getViewIndex(index);
switch (msg.arg1) {
case OP_SET_ICON: {
StatusBarIcon icon = (StatusBarIcon)msg.obj;
StatusBarIcon old = mList.getIcon(index);
if (old == null) {
mList.setIcon(index, icon);
//调用PhoneStatusBar.addIcon()方法来添加系统图标
mCallbacks.addIcon(mList.getSlot(index), index, viewIndex, icon);
} else {
mList.setIcon(index, icon);
//调用PhoneStatusBar.addIcon()方法来更新系统图标
mCallbacks.updateIcon(mList.getSlot(index), index, viewIndex,
old, icon);
}
break;
}
}
break;
}
}
}
}

7). SystemUI中PhoneStatusBar调用状态栏图标控制类来添加系统图标:

PhoneStatusBar.java

1
2
3
4
5
StatusBarIconController mIconController;	//状态栏图标控制类

public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
mIconController.addSystemIcon(slot, index, viewIndex, icon);
}

8). SystemUI中StatusBarIconController添加系统图标:

StatusBarIconController.java & StatusBarIconView.java

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
60
61
62
63
64
65
//StatusBarIconController.java

private LinearLayout mStatusIcons;

public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
PhoneStatusBar phoneStatusBar) {
...
mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons); //系统图标布局容器
...
}

public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
boolean blocked = mIconBlacklist.contains(slot);
StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked, 0);
view.set(icon);
mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize)); //把图标添加到布局容器中
view = new StatusBarIconView(mContext, slot, null, blocked, 0);
view.set(icon);
mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
applyIconTint();
}



//StatusBarIconView.java

public boolean set(StatusBarIcon icon) {
if (!iconEquals) {
if (!updateDrawable(false /* no clear */)) return false;
}
if (!numberEquals) {
if (icon.number > 0 && getContext().getResources().getBoolean(
R.bool.config_statusBarShowNumber)) {
placeNumber();
} else {
mNumberBackground = null;
mNumberText = null;
}
invalidate();
}
if (!visibilityEquals) {
setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
}
return true;
}

public void updateDrawable() {
updateDrawable(true /* with clear */);
}

private boolean updateDrawable(boolean withClear) {
Drawable drawable = getIcon(mIcon);
setImageDrawable(drawable);
return true;
}

private Drawable getIcon(StatusBarIcon icon) {
return getIcon(getContext(), icon);
}

public static Drawable getIcon(Context context, StatusBarIcon icon) {
return icon.icon.loadDrawableAsUser(context, userId);
}

4.总结

通过以上对状态栏中闹钟图标的显示过程,我们可以知道,状态栏右边的系统图标的显示,是可以由系统App控制来显示的,系统App可以通过获取StatusBarManagerService这个服务对象来设置状态栏中的系统图标。

代码实现如下:

1
2
3
4
private static final String SLOT_ALARM_CLOCK = "alarm_clock";
private final StatusBarManager mService = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);

还要记得在AndroidManifest.xml文件中加入以下权限:

<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.STATUS_BAR" />
Evan-Lam

Evan-Lam

7 日志
5 分类
4 标签
RSS
GitHub 微博
Links
  • CSDN
  • 简书
  • 老罗的Android之旅
  • 创建狮
  • Google
  • AndroidXRef
© 2018 Evan-Lam