本文基于 Android 7.1.1 源码分析,转载请说明出处!
0 综述
我们可以通过 startForeground 方法来将一个服务设置成前台服务,具体的使用如下: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
27private void initNotification(Context context) {
    Notification.Builder builder = new Notification.Builder(context);
    builder.setOngoing(true).setSmallIcon(R.drawable.ic_launcher)
            .setContentTitle(context.getResources().getText(R.string.ticker_text))
            .setContentText(context.getResources().getText(R.string.ticker_text));
    builder.setPriority(Notification.PRIORITY_HIGH);
    mNotification = builder.build();
    mNotification.flags = mNotification.flags | Notification.FLAG_AUTO_CANCEL;
}
public int onStartCommand(Intent intent, int flags, int startId) {
    OppoLog.d(TAG, "onStartCommand");
    initNotification(this);
    
    startForeground(ID, mNotification);
    return super.onStartCommand(intent, flags, startId);
}
public void onDestroy() {
    super.onDestroy();
    OppoLog.d(TAG, "onDestroy");
    
    stopForeground(true);
}
其中,涉及到的方法有如下几个:1
2
3
4
5
6
7// 将服务设置到前台,如果 id 为 0,那就取消设置前台服务;
startForeground(int id, Notification notification) 
// 取消前台设置,removeNotification 表示是否移除通知;
stopForeground(boolean removeNotification)
// 取消前台设置,flags 可选 STOP_FOREGROUND_REMOVE 或者 STOP_FOREGROUND_DETACH
stopForeground(int flags)
我们来具体分析下,startForeground 方法的处理流程!
1 服务所在进程
1.1 Service.startForeground
我们先来看看 startForeground 方法!1
2
3
4
5
6
7
8
9
10
11public final void startForeground(int id, Notification notification) {
    try {
        
        // 调用 AMS 的 setServiceForeground 方法!
        mActivityManager.setServiceForeground(
                new ComponentName(this, mClassName), mToken, id,
                notification, 0);
    } catch (RemoteException ex) {
    }
}
这里的 mToken,在之前 Service 的创建时,系统进程赋给 Service 的,是一个 IBinder 对象,是 ServiceRecord 对象的引用!
这里的 mActivityManager 也是 Service 在创建后,内部保存的 AMS 的代理对象!
接着进入 ActivityManagerProxy 代理对象中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public void setServiceForeground(ComponentName className, IBinder token,
        int id, Notification notification, int flags) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    ComponentName.writeToParcel(className, data);
    data.writeStrongBinder(token);
    data.writeInt(id);
    if (notification != null) {
        data.writeInt(1);
        notification.writeToParcel(data, 0);
    } else {
        data.writeInt(0);
    }
    data.writeInt(flags);
    mRemote.transact(SET_SERVICE_FOREGROUND_TRANSACTION, data, reply, 0);
    reply.readException();
    data.recycle();
    reply.recycle();
}
接着进入系统进程!
2 系统进程
2.1 ActivityManagerN.onTransact
1  | case SET_SERVICE_FOREGROUND_TRANSACTION: {  | 
接着,进入 AMS:
2.2 ActivityManagerS.setServiceForeground
1  | 
  | 
接着,进入 ActiveServices:
2.3 ActiveServices.setServiceForegroundLocked
1  | public void setServiceForegroundLocked(ComponentName className, IBinder token,  | 
2.3.1 ActiveServices.findServiceLocked
找到对应的 ServiceRecord 对象!1
2
3
4
5private final ServiceRecord findServiceLocked(ComponentName name,
        IBinder token, int userId) {
    ServiceRecord r = getServiceByName(name, userId);
    return r == token ? r : null;
}
根据组件名和设别用户id,在 ActiveServices 的 mServiceMap 稀疏数组中,找到对应的 ServiceRecord 对象,然后和 Service 进程传递过来的 token,二者必须相等才行!
这里就不多说了!
2.3.2 ActiveServices.cancelForegroudNotificationLocked
1  | private void cancelForegroudNotificationLocked(ServiceRecord r) {  | 
ServiceRecord r 是本次需要设置为前台的服务,r.foregroundId 为前台 notification 的 id,这个函数的主要作用是,判断本次需要设置为前台的服务所属的应用是否有其他服务的 foregroundId 和 r.foregroundId 相同,如果相同,那就不能取消这个通知!
如果只有当前服务在使用这个通知的 id,那就取消这个旧的通知:
2.3.2.1 ServiceRecord.cancelNotification
1  | public void cancelNotification() {  | 
调用 NotificationManager 取消这个通知!
2.3.3 ServiceRecord.postNotification
1  | public void postNotification() {  | 
这个方法的主要作用是显示 notification!
2.3.4 ActiveServices.updateServiceForegroundLocked
这里是重点,更新服务的优先级和 oomAdj,设置服务为前台服务,参数分析:
- ProcessRecord proc:服务所在的进程!
 - boolean oomAdj:传入 true,表示需要更新 oomAdj 值!
 
1  | private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {  | 
显然,根据前面的属性设置 anyForeground 为 true !
下面就是更新服务所在进程的优先级和 oomAdj 的值了!
2.3.5 ServiceMap.ensureNotStartingBackground
1  | void ensureNotStartingBackground(ServiceRecord r) {  | 
这里和 startService 中的一样的,目的是将当前服务从 mStartingBackground 和 mDelayedStartList 中删除,因为服务被设置成了前台服务,如果当前服务从 mStartingBackground 中删除成功了,就要调用 rescheduleDelayedStarts 方法,继续发送 MSG_BG_START_TIMEOUT 消息!
这里具体的分析,请去看 startService 博文的第 2.3 节!
通过上面的分析,关键的地方是这里:1
2
3
4
5
6
7private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {
    
    ... ... ... ...
    
    // 进入这里进行进程的优先级和 oomAdj 的设置!
    mAm.updateProcessForegroundLocked(proc, anyForeground, oomAdj);
}
这里调用了 AMS 的 updateProcessForegroundLocked 方法来更新 Service 的进程优先级和 oomAdj 的值。我们继续来看!
2.4 ActivityManagerS.updateProcessForegroundLocked
参数传递:
- ProcessRecord proc:服务所在的进程;
 - boolean isForeground:传入 true;
 - boolean oomAdj:传入 true;
 
这里我们假设之前服务所在进程没有运行任何的前台服务!!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
52final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
        boolean oomAdj) {
        
    // proc.foregroundServices 表示进程是否运行前台服务!
    // 如果 isForeground 不等于 proc.foregroundServices,说明进程的
    if (isForeground != proc.foregroundServices) {
        
        // 将 proc.foregroundServices 设置为 isForeground 的值!
        proc.foregroundServices = isForeground;
        
        // mForegroundPackages 集合用来保存所有当前正在运行着前台服务的应用程序包信息
        // curProcs 则是运行着前台服务的进程对象 ProcessRecord!
        ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
                proc.info.uid);
        
        // 因为我们现在是设置服务为前台服务,所以 isForeground 是 true!
        if (isForeground) {
            
            // 将进程所属的应用程序包,添加到 mForegroundPackages 集合中;
            // 并将服务所在的进程也添加到 mForegroundPackages 中;
            if (curProcs == null) {
                curProcs = new ArrayList<ProcessRecord>();
                mForegroundPackages.put(proc.info.packageName, proc.info.uid, curProcs);
            }
            if (!curProcs.contains(proc)) {
                curProcs.add(proc);
                
                // 通知 BatteryStatsService 服务,有应用在前台执行服务!
                mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_FOREGROUND_START,
                        proc.info.packageName, proc.info.uid);
            }
        } else {
            if (curProcs != null) {
                if (curProcs.remove(proc)) {
                    mBatteryStatsService.noteEvent(
                            BatteryStats.HistoryItem.EVENT_FOREGROUND_FINISH,
                            proc.info.packageName, proc.info.uid);
                    if (curProcs.size() <= 0) {
                        mForegroundPackages.remove(proc.info.packageName, proc.info.uid);
                    }
                }
            }
        }
        
        // 这里为 true,更新进程的 oomAdj 的值!
        if (oomAdj) {
            updateOomAdjLocked();
        }
    }
}
这里有一些数据结构,我们来简单是说一下:
ProcessRecord.foregroundServices 表示该进程中是否在运行前台服务;
初次之外,还有一个 mForegroundPackages,用来保存运行着前台服务的应用程序包信息:1
2final ProcessMap<ArrayList<ProcessRecord>> mForegroundPackages
        = new ProcessMap<ArrayList<ProcessRecord>>();
他是 ProcessMap 类的对象,ProcessMap 是一个模板类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class ProcessMap<E> {
    final ArrayMap<String, SparseArray<E>> mMap
            = new ArrayMap<String, SparseArray<E>>();
    
    public E get(String name, int uid) {
        SparseArray<E> uids = mMap.get(name);
        if (uids == null) return null;
        return uids.get(uid);
    }
    
    public E put(String name, int uid, E value) {
        SparseArray<E> uids = mMap.get(name);
        if (uids == null) {
            uids = new SparseArray<E>(2);
            mMap.put(name, uids);
        }
        uids.put(uid, value);
        return value;
    }
    ... ... ...
}
ProcessMap 的内部变量 mMap 是一个 ArrayMap 类型的集合,其中 key 是应用程序的报名,而 value 是 SparseArray
这里我们就先看到这里,关于 updateOomAdjLocked 的逻辑处理,请去看。。。。
3 stopForeground 分析
上面分析了一些 startForeground 方法的主要流程,下面分析下 stopForeground 方法,stopForeground 方法一共有两个:1
2
3
4
5
6
7
8
9
10
11public final void stopForeground(boolean removeNotification) {
    stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : 0);
}
public final void stopForeground(int flags) {
    try {
        mActivityManager.setServiceForeground(
                new ComponentName(this, mClassName), mToken, 0, null, flags);
    } catch (RemoteException ex) {
    }
}
第一个方法最后还是会调用第二个方法!
这里我们先来解释两个参数变量:1
public static final int STOP_FOREGROUND_REMOVE = 1<<0;
如果设置了这个标志位,取消前台服务的同时,还会移除对应的通知,但是如果同一个应用中有其他前台服务关联着相同的通知,就不会移除该通知;
如果不设置这个标志位,通知只能通过 startForeground(int, Notification) 或者 stopForeground(int),或者服务被销毁的方式移除!
1  | public static final int STOP_FOREGROUND_DETACH = 1<<1;  | 
如果设置了这个标志位,取消前台服务的同时,不会移除对应的通知,但是会将通知和服务完全解除绑定,这时通知只有通过 NotificationManager 才能被取消!该标志位不能和 STOP_FOREGROUND_REMOVE 混合使用哦!
方法调用和 startForeground 方法很类似,最后会进入 ActiveSerivces 中去:
3.1 ActiveSerivces.setServiceForegroundLocked
参数传递:
- ComponentName className:服务的类名;
 - IBinder token;服务的 ServiceRecord 对象;
 - int id:传入 0;
 - Notification notification:传入 null;
 - int flags:传入具体的 flags;
 
1  | public void setServiceForegroundLocked(ComponentName className, IBinder token,  | 
这里就先分析这么多,其他内容后续在补充!