工作中遇到了一个问题:
封装了一个Activity组,这组Activity执行了相类似的业务A,但是少部分文案、图片是有差别的,而且要求在这组Activity执行的过程中,后台运行的其他业务可以随时(或在业务A回调时)修改这些显示内容。
不优雅的方案有:
- 回调中传递Activity的引用。这样限制了修改时机
- 在某单例中注册当前Activity,同时在Activity上暴露相应的修改接口。这样一来容易出现内存泄露,同时还要求其他业务了解业务A中的各种UI排布/命名等
实际上,这是一个跨Activity的MVVM问题,即双方定义好一个Model,业务A仅读取Model内容,并做好VM层和V层的工作。其他业务只需要理解Model的定义,持有Model并修改即可。
public class Config {
public static final String ACTION_CONFIG_CHANGE = "gt.research.androidbase.ACTION_CONFIG_CHANGE";
public static final String KEY_CHANGED_PARTS = "KEY_CHANGED_PARTS";
private int mId;
//just for convenient
private AtomicBoolean mHasComsumed = new AtomicBoolean(true);
//for multithreading
private final ThreadLocal<ArrayMap<String, Object>> mTransactionMap = new ThreadLocal<>();
private LocalBroadcastManager mBroadcastManager;
//model
private final ArrayMap<String, Object> mConfigs = new ArrayMap<>();
public Config(int id) {
mId = id;
}
//multiple changes
public Config beginTransaction(Context context) {
ArrayMap<String, Object> map = mTransactionMap.get();
if (null == map) {
map = new ArrayMap<>();
}
map.clear();
return this;
}
public Config commit(final Context context) {
final ArrayMap<String, Object> map = mTransactionMap.get();
if (null == map) {
return this;
}
//for multi-threading
ConfigManager.getInstance().runOnUiThread(new Runnable() {
@Override
public void run() {
mConfigs.putAll(map);
notifyChange(context, (String[]) map.keySet().toArray());
}
});
return this;
}
public Config set(final Context context, final String name, final Object object) {
ConfigManager.getInstance().runOnUiThread(new Runnable() {
@Override
public void run() {
mConfigs.put(name, object);
notifyChange(context, name);
}
});
return this;
}
public Object get(Context context, String name) {
//constrain on A
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
throw new RuntimeException("Wrong Thread");
}
return mConfigs.get(name);
}
//KVO
private Config notifyChange(Context context, String... names) {
LocalBroadcastManager broadcastManager = getBroadcastManager(context);
Intent intent = new Intent(ACTION_CONFIG_CHANGE + mId);
intent.setPackage("gt.research.androidbase");
intent.putExtra(KEY_CHANGED_PARTS, names);
broadcastManager.sendBroadcast(intent);
mHasComsumed.set(false);
return this;
}
private LocalBroadcastManager getBroadcastManager(Context context) {
if (null == mBroadcastManager) {
mBroadcastManager = LocalBroadcastManager.getInstance(context);
}
return mBroadcastManager;
}
public boolean shouldUpdate() {
return !mHasComsumed.getAndSet(true);
}
public String[] getAllNames() {
return (String[]) mConfigs.keySet().toArray();
}
}
public class ConfigManager {
private static volatile ConfigManager sInstance;
public static ConfigManager getInstance() {
if (null == sInstance) {
synchronized (ConfigManager.class) {
if (null == sInstance) {
sInstance = new ConfigManager();
}
}
}
return sInstance;
}
private ConfigManager() {
}
private final SparseArray<Config> mConfigs = new SparseArray<>();
private Handler mHandler = new Handler(Looper.getMainLooper());
public Config getConfig(int id) {
synchronized (mConfigs) {
Config config = mConfigs.get(id);
if (null == config) {
config = new Config(id);
}
mConfigs.put(id, config);
return config;
}
}
public void runOnUiThread(Runnable runnable) {
mHandler.post(runnable);
}
}
public class Main2Activity extends AppCompatActivity {
private TextView mText;
private int mId;
private String mAction;
//KVO
private BroadcastReceiver mConfigReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mAction.equals(intent.getAction())) {
String[] changedParts = intent.getStringArrayExtra(Config.KEY_CHANGED_PARTS);
Config config = ConfigManager.getInstance().getConfig(mId);
if (config.shouldUpdate()) {
updateViews(changedParts, config);
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mText = (TextView) findViewById(R.id.text2);
mId = getIntent().getIntExtra(MainActivity.KEY_ID, -1);
//a unique action just for this Activity
mAction = Config.ACTION_CONFIG_CHANGE + mId;
if (-1 == mId) {
return;
}
LocalBroadcastManager.getInstance(this).registerReceiver(mConfigReceiver,
new IntentFilter(mAction));
Config config = ConfigManager.getInstance().getConfig(mId);
//fetch all missed changes
if (config.shouldUpdate()) {
updateViews(config.getAllNames(), config);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mConfigReceiver);
}
private void updateViews(String[] changedParts, Config config) {
String key = changedParts[0];
String value = (String) config.get(this, key);
mText.setText(value);
}
}
public class MainActivity extends AppCompatActivity {
public static final String KEY_ID = "id";
private int mId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mId = Math.abs(new Random(System.currentTimeMillis()).nextInt());
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
intent.putExtra(KEY_ID, mId);
startActivity(intent);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
ConfigManager.getInstance().getConfig(mId).set(MainActivity.this, "lala", "dlfjlajf");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
});
}
PS.我这是戳中了什么搜索热词了啊。。。点进来的人们,不好意思,我只是做个笔记。