場景:
假設某SDK的aar有個服務,但是此服務必須通過Service啓動,並且改Service可能是多進程的,如果不想啓動service或避免使用多進程,以此避免和AMS通信,該如何實現呢
方案:
答案是:通過Context去Hook啓動行爲
如:DataService extend Service{....}
實現方法如下
public class DataContextImplHook extends ContextThemeWrapper {
private Service hookTargetService = null;
private AtomicInteger hookStartupCounter = null;
private final Map<KeyRecord, IBinder> connectionPool = new HashMap<>();
private boolean isAliveService = false;
private Handler mainThreadHandler = null;
public DataContextImplHook(Context base) {
super(base, 0);
mainThreadHandler = new Handler(Looper.getMainLooper());
}
boolean isWorkThread(){
return mainThreadHandler.getLooper()==Looper.myLooper();
}
@Override
public ComponentName startService(Intent service) {
ComponentName component = service.getComponent();
if (component != null && component.getClassName().equals(DataService.class.getName())) {
if(isWorkThread()) {
if (hookStartupCounter == null) {
hookStartupCounter = new AtomicInteger(1);
}
if (hookTargetService == null) {
isAliveService = true;
hookTargetService = new DataService();
hookTargetService.onCreate();
}
hookTargetService.onStartCommand(service, 0, hookStartupCounter.getAndIncrement());
return component;
}
ComponentName componentName = submit(new SyncTask<ComponentName>() {
@Override
public ComponentName doTask() {
return startService(service);
}
});
return componentName;
}
return super.startService(service);
}
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
ComponentName component = service.getComponent();
if (conn != null && component != null && component.getClassName().equals(DataService.class.getName())) {
if(isWorkThread()) {
if (hookStartupCounter == null) {
hookStartupCounter = new AtomicInteger(1);
}
if (hookTargetService == null) {
isAliveService = true;
hookTargetService = new DataService();
hookTargetService.onCreate();
}
IBinder binder = queryConnectionPool(service);
if (binder != null) {
hookTargetService.onRebind(service);
connectionPool.put(new KeyRecord(conn, service), binder);
conn.onServiceConnected(component, binder);
return true;
}
binder = hookTargetService.onBind(service);
if (binder != null && binder.isBinderAlive()) {
connectionPool.put(new KeyRecord(conn, service), binder);
conn.onServiceConnected(component, binder);
}
return binder != null;
}
Boolean submit = submit(new SyncTask<Boolean>() {
@Override
public Boolean doTask() {
return bindService(service,conn,flags);
}
});
return submit!=null?submit.booleanValue():false;
}
return super.bindService(service, conn, flags);
}
private IBinder queryConnectionPool(Intent service) {
if (service == null) {
return null;
}
Set<KeyRecord> keySet = connectionPool.keySet();
KeyRecord key = null;
for (KeyRecord record : keySet) {
if (service.filterEquals(record.intent)) {
key = record;
break;
}
}
if (key == null) {
return null;
}
IBinder binder = connectionPool.get(key);
if (binder == null || !binder.isBinderAlive()) {
connectionPool.remove(key);
return null;
}
return binder;
}
@Override
public void unbindService(ServiceConnection conn) {
execute(new Runnable() {
@Override
public void run() {
doUnBind(conn);
}
});
super.unbindService(conn);
}
private boolean doUnBind(ServiceConnection conn) {
List<KeyRecord> recordList = new LinkedList<>();
for (Map.Entry<KeyRecord, IBinder> entry : connectionPool.entrySet()) {
KeyRecord key = entry.getKey();
if (key == null) continue;
if (key.connection == conn) {
recordList.add(key);
}
}
for (KeyRecord record : recordList) {
connectionPool.remove(record);
record.connection.onServiceDisconnected(record.intent.getComponent());
}
if (!isAliveService && connectionPool.isEmpty()) {
if (hookTargetService != null) {
hookTargetService.onDestroy();
hookTargetService = null;
}
}
if(!recordList.isEmpty()){
return true;
}
return false;
}
@Override
public boolean stopService(Intent service) {
ComponentName component = service.getComponent();
if (component != null && component.getClassName().equals(DataService.class.getName())) {
if(isWorkThread()){
isAliveService = false;
if (hookTargetService != null) {
if(connectionPool.isEmpty()) {
hookTargetService.onDestroy();
hookTargetService = null;
}
}
return hookTargetService !=null;
}
Boolean submit = submit(new SyncTask<Boolean>() {
@Override
public Boolean doTask() {
return stopService(service);
}
});
return submit!=null?submit.booleanValue():false;
}
return super.stopService(service);
}
static class KeyRecord {
Intent intent;
ServiceConnection connection;
public KeyRecord(ServiceConnection conn, Intent intent) {
this.intent = intent;
this.connection = conn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof KeyRecord)) return false;
KeyRecord record = (KeyRecord) o;
return (Objects.equals(intent, record.intent) || intent.filterEquals(record.intent)) &&
Objects.equals(connection, record.connection);
}
@Override
public int hashCode() {
return Objects.hash(intent, connection);
}
}
static abstract class SyncTask<V> {
V result;
final CountDownLatch countDownLatch = new CountDownLatch(1);
public void execute() {
try {
result = doTask();
}catch (Throwable throwable){
throw throwable;
}finally {
countDownLatch.countDown();
}
}
public abstract V doTask();
public V getResult() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
public void forceCountDown() {
countDownLatch.countDown();
}
}
public <V> V submit(final SyncTask<V> task) {
if (task == null) {
return null;
}
try {
this.mainThreadHandler.post(new Runnable() {
@Override
public void run() {
task.execute();
}
});
}catch (Exception e){
e.printStackTrace();
task.forceCountDown();
}
return task.getResult();
}
public void execute(final Runnable runnable) {
if (isWorkThread()) {
runnable.run();
return;
}
this.mainThreadHandler.post(runnable);
}
}