[Android] ActivityManager 를 이용해서 Foreground Service 만들기
일반적으로 화면을 가지고 있지 않는 서비스는 포그라운드 프로세스(Foreground Process)가 될 수 없다.
하지만 ActivityManager는 프로세스를 관리하고, 앱별로 포그라운드, 백그라운드 제어할 수 있는 설비를 갖추고 있다.
아래와 같이 ActivityManagerNative의 setProcessForeground 메서드를 이용하면 가능하다.
참고로 ActivityManagerNative 는 hide 클래스이기 때문에 컴파일시에 참조 되지 않지만 리플렉션을 이용해서 런타임시에 참조가 가능하다.
/*
* android.app.ActivityManagerNative.java(@hide class)
*/
IActivityManage rmAm = ActivityManagerNative.getDefault();
mAm.setProcessForeground(IBinder token, int pid, boolean isForeground)
public void setProcessForeground(IBinder token, int pid,
boolean isForeground) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
data.writeInt(pid);
data.writeInt(isForeground ? 1 : 0);
mRemote.transact(SET_PROCESS_FOREGROUND_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
Binder를 이용해서 ActivityManager Service 로 콜을 하는 것을 볼 수 있다.
구현체에서 보면 알 수 있듯이 이 메서드를 사용하기 위해서는 SET_PROCESS_LIMIT 가 허용이 되어야 하는데
해당 메서드는 일반 앱에서는 허용되지 않는다. platform key로 앱 서명할 경우 사용 가능하다.
"android.permission.SET_PROCESS_LIMIT"
Allows an application to set the maximum number of (not needed) application processes that can be running.
Not for use by third-party applications.
Constant Value: "android.permission.SET_PROCESS_LIMIT"
/*
* com.android.server.am.ActivityManagerService.java
*/
@Override
public void setProcessForeground(IBinder token, int pid, boolean isForeground) {
enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
"setProcessForeground()");
synchronized(this) {
boolean changed = false;
synchronized (mPidsSelfLocked) {
ProcessRecord pr = mPidsSelfLocked.get(pid);
if (pr == null && isForeground) {
Slog.w(TAG, "setProcessForeground called on unknown pid: " + pid);
return;
}
ForegroundToken oldToken = mForegroundProcesses.get(pid);
if (oldToken != null) {
oldToken.token.unlinkToDeath(oldToken, 0);
mForegroundProcesses.remove(pid);
if (pr != null) {
pr.forcingToForeground = null;
}
changed = true;
}
if (isForeground && token != null) {
ForegroundToken newToken = new ForegroundToken() {
@Override
public void binderDied() {
foregroundTokenDied(this);
}
};
newToken.pid = pid;
newToken.token = token;
try {
token.linkToDeath(newToken, 0);
mForegroundProcesses.put(pid, newToken);
pr.forcingToForeground = token;
changed = true;
} catch (RemoteException e) {
// If the process died while doing this, we will later
// do the cleanup with the process death link.
}
}
}
if (changed) {
updateOomAdjLocked();
}
}
}