问题背景
在定制系统中,往往需要在自定义系统服务中实现硬件资源的控制接口,app调用了这些接口后,还没来得及释放就因为种种原因崩溃了。未释放的硬件资源可能会导致系统休眠功耗过高,或其它app用了该未释放硬件资源导致错误等等。
如CustomSystemService中实现了个接口power_ctrl()用于控制打印机,扫描等外设的供电。app在调用了power_ctrl()将电源开启后,在后面的运行过程中有时会因为其它原因导致app崩溃,但电源还开着未关闭,影响了机器的休眠功耗。因此需要有个方案来监控CustomSystemService客户端app的存活状态,并在app异常崩溃后将对应的资源释放掉,包括但不限于外设电源这种资源。
怎么添加自定义服务和对外提供接口可以参考:在Android中利用抽象类对外提供系统接口
实现
原理
在app所持有的CustomManagerImpl对象中创建一个IBinder对象,并将其传给服务端作为客户端app的代表,这时其实对CustomSystemService来说,CustomManagerImpl也是一个Binder服务端,可以通过IBinder.DeathRecipient判断CustomManagerImpl持有者是否存活。
步骤:
在CustomManagerImpl.java中新增一个成员变量IBinder mToken
private final IBinder mToken;
app在实例化CustomManager时会调用CustomManagerImpl(Context context, ICustomSystemService service):
public CustomManagerImpl(Context context, ICustomSystemService service) {
mService = service;
mContext = context;
mToken = new Binder();
mHandler = new MyHandler(mContext.getMainLooper());
}
在CustomManagerImpl.java中调用ptr_power_on(IBinder costomClient)时,传入mToken
public void ptr_power_on() {
try {
mService.ptr_power_on(mToken);
} catch (Exception e) {
e.printStackTrace();
}
}
CustomSystemService中:
ptr_power_on的实现:
public void ptr_power_on(IBinder customClient) {
power_ctrl(costomClient, true);
}
private synchronized void power_ctrl(IBinder client, boolean on) {
CustomClient customClient = findOrCreatCustomClient(client);
if (on) {
customClient.mPwrEnCnt ++;
setGpioValue(GPIO_CTRL_NUM_PWREN, 1);
} else {
if (customClient.mPwrEnCnt > 0) {
customClient.mPwrEnCnt--;
}
boolean NotNeedOff = false;
/* 所有 client 都执行了 power off 才真正执行下电动作 */
for (CustomClient c : mCustomClients) {
if (c.mPwrEnCnt > 0) {
NotNeedOff = true;
break;
}
}
if (!NotNeedOff) {
setGpioValue(GPIO_CTRL_NUM_PWREN, 0);
}
}
Log.d(TAG, "power_ctrl: " + customClient.mTag + " mPwrEnCnt = " + customClient.mPwrEnCnt);
}
其中CustomClient的定义如下:
private final class CustomClient implements IBinder.DeathRecipient {
public final IBinder mClient;
public String mTag;
public final String mPackageName;
public int mPwrEnCnt = 0;
public CustomClient(IBinder client, String tag, String packageName) {
mClient = client;
mTag = tag;
mPackageName = packageName;
Log.d(TAG, "CostomClient: " + tag + ", " + packageName);
}
@Override
public void binderDied() {
CostomSystemService.this.handleWakeLockDeath(this);
}
}
mCostomClients:
private final ArrayList<CostomClient> mCostomClients = new ArrayList<CostomClient>();
findOrCreatCostomClient():
private CostomClient findOrCreatCostomClient(IBinder client) {
synchronized (mLock) {
int index = findCostomClientIndexLocked(client);
if (index >= 0) {
return mCostomClients.get(index);
} else {
String callingApp = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
CostomClient costomClient = new CostomClient(client, Binder.getCallingPid() + ":" + client, callingApp);
try {
client.linkToDeath(costomClient, 0);
} catch (RemoteException ex) {
throw new IllegalArgumentException("costomClient is already dead.");
}
mCostomClients.add(costomClient);
return costomClient;
}
}
}
在findOrCreatCostomClient()中,client.linkToDeath(costomClient, 0)注册了客户端死掉后,服务端需调用的方法也就是CostomClient.binderDied()。
CostomClient.binderDied()接着调用CostomSystemService.handleWakeLockDeath()
private void handleWakeLockDeath(CostomClient costomClient) {
synchronized (mLock) {
Log.d(TAG, "handleWakeLockDeath: client =" + Objects.hashCode(costomClient.mClient)
+ " [" + costomClient.mTag + "]");
int index = mCostomClients.indexOf(costomClient);
if (index < 0) {
return;
}
removeCostomClientLocked(costomClient, index);
}
}
removeCostomClientLocked():
private void removeCostomClientLocked(CostomClient costomClient, int index) {
if (costomClient.mPwrEnCnt > 0) {
costomClient.mPwrEnCnt = 0;
power_ctrl(costomClient.mClient, false);
}
mCostomClients.remove(index);
}
在removeCostomClientLocked()中对死掉客户端所占用的资源进行了清理。