Avatar:安卓跨進程事件訂閱發佈
阿凡達:一個解決跨進程的事件訂閱發佈問題:
項目地址Avatar
1:跨進程通信 aidl+service
2:發佈的內容和訂閱者的信息進程共享
跨進程的通信可用採用binder機制,這裏用 aild.stub 的 binder 代理對象
訂閱發佈可用採用CS架構,將訂閱信息和發佈內容同步到一個Service,保證進程間數據一致
選擇service+adil的原因:
①service 四大組件之一,由AMS管理,提供binder接口,可用傳入 Messager.Binder/Aidl.Stub 等 IBinder的實現,來實現跨進程通信
②啓動service:採用bind 啓動,保證了客戶端bindService(intent,binder,connection)後,Service的onBind()方法通過AMS代理將binder傳遞到AMS,ServiceDispatcher.ConnectionInfo,將binder的connect/disconnect的回調(ServiceConnection)返回給客戶端
③每個進程有一份Avatar,每次register/unregister/post 都會通過bindService,向服務器Service發送訂閱者和訂閱內容,在Service進行同步和轉發
使用:
Step 1. Add the JitPack repository to your build file
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
implementation 'com.github.woaigmz:Avatar:0.0.1'
}
**Step 3.每個進程初始化 context 上下文,通過上下文來啓動service
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
//初始化多進程庫
Avatar.init(this);
if(!ProcessUtil.isMainProcess){
return;
}
//初始化主進程庫
}
}
**Step 4.事件發佈 home進程:HomeActivity
和EventBus對比,EventBus是不能跨進程的
public class HomeActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
findViewById(R.id.tv_close_main_page).setOnClickListener(this);
}
@Override
public void onClick(View v) {
EventBus.getDefault().post(new MessageEvent("改變背景顏色"));
Avatar.get().post(BusConstants.CHANGE_TEXT, "我是:MainActivity");
Avatar.get().post( BusConstants.CHANGE_COLOR, "#FF0000");
finish();
}
}
**Step 5. 主進程:MainActivity 訂閱事件
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv_name);
Avatar.get().register(this);
EventBus.getDefault().register(this);
tv.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, HomeActivity.class)));
}
@Subscribe(thread = ThreadMode.MAIN, tag = BusConstants.CHANGE_TEXT)
public void changeText(String s) {
Log.e(TAG, s);
tv.setText(s);
}
@Subscribe(thread = ThreadMode.MAIN, tag = BusConstants.CHANGE_COLOR)
public void changeColor(String s) {
Log.e(TAG, s);
tv.setTextColor(Color.parseColor(s));
}
@org.greenrobot.eventbus.Subscribe(threadMode = org.greenrobot.eventbus.ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.e("EventBus", Thread.currentThread().getName() + "- - -" + event.text);
tv.setBackgroundColor(Color.YELLOW);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
Avatar.get().unregister(this);
}
}
代碼邏輯:
aidl:
interface IAvatarAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void post(String tag,String content);
void register(String className);
void unregister(String className);
}
Service:
public class ShadowService extends Service {
// LruCache for post
private LinkedHashMap<String, PostCard> postMap;
// Subscribes: key-register value-SubscribeInfo
private HashMap<String, List<SubscribeInfo>> subscribes;
..... bind ...
IAvatarAidlInterface.Stub stub = new IAvatarAidlInterface.Stub() {
@Override
public void post(String tag, String content) {
.....
for (Map.Entry<String, List<SubscribeInfo>> entry : subscribes.entrySet()) {
for (SubscribeInfo info : entry.getValue()) {
if (tag.equals(info.getTag())) {
info.setEvent(eventObj);
try {
switch (info.getThreadMode()) {
case POSTING:
s.invokeSubscriber(info, entry.getKey());
break;
case MAIN:
if (s.isMainThread()) {
s.invokeSubscriber(info, entry.getKey());
} else {
s.getMainThreadPoster().enqueue(info, entry.getKey());
}
break;
case BACKGROUND:
if (s.isMainThread()) {
s.getBackgroundPoster().enqueue(info, entry.getKey());
} else {
s.invokeSubscriber(info, entry.getKey());
}
break;
case ASYNC:
s.getAsyncPoster().enqueue(info, entry.getKey());
break;
default:
throw new IllegalStateException("Unknown thread mode: " + info.getThreadMode());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void register(String className) {
processorRegisterToSubscribesMap(className);
}
@Override
public void unregister(String className) {
clearTargetRegisterInSubscribesMap(className);
}
};
method.invoke:
void invokeSubscriber(SubscribeInfo subscribeInfo, String source) {
try {
Object o = Avatar.getSourceCache().get(source);
if (o == null) {
return;
}
subscribeInfo.getMethod().invoke(o, subscribeInfo.getEvent());
} catch (Exception e) {
Log.e(TAG, e.toString());
throw new RuntimeException(e);
}
}
Client:
public void post(final String tag, final String content) {
if (!initFlag.get()) {
return;
}
try {
if (con == null) {
postInterval(tag, content);
} else {
if (con.getStub() != null) {
con.getStub().post(tag, content);
} else {
postInterval(tag, content);
}
}
} catch (Exception e) {
}
}
bindService:
/**** interval ******************************************************************************************************/
private void postInterval(final String tag, final String content) {
appContext.bindService(new Intent(appContext, ShadowService.class), con = new Connection(new ConnectionCallback() {
@Override
public void onConnected(IAvatarAidlInterface stub) throws RemoteException {
stub.post(tag, content);
}
@Override
public void onDisconnected(IAvatarAidlInterface stub) {
}
}), Context.BIND_AUTO_CREATE);
}