使用LocalBroadcast实现跨Activity的MVVM

48 篇文章 0 订阅
36 篇文章 1 订阅

问题

工作中遇到了一个问题:
封装了一个Activity组,这组Activity执行了相类似的业务A,但是少部分文案、图片是有差别的,而且要求在这组Activity执行的过程中,后台运行的其他业务可以随时(或在业务A回调时)修改这些显示内容。
不优雅的方案有:
- 回调中传递Activity的引用。这样限制了修改时机
- 在某单例中注册当前Activity,同时在Activity上暴露相应的修改接口。这样一来容易出现内存泄露,同时还要求其他业务了解业务A中的各种UI排布/命名等

实际上,这是一个跨Activity的MVVM问题,即双方定义好一个Model,业务A仅读取Model内容,并做好VM层和V层的工作。其他业务只需要理解Model的定义,持有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);
    }
}
业务A
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.我这是戳中了什么搜索热词了啊。。。点进来的人们,不好意思,我只是做个笔记。

好的,这里我可以给你一个WPF使用MvvmLight实现MVVM的工程实例。 首先,你需要在Visual Studio中安装MvvmLight框架。可以通过NuGet来安装MvvmLight库,具体步骤如下: 1. 在Visual Studio中打开你的WPF工程。 2. 在Solution Explorer窗口中右键单击工程,选择“Manage NuGet Packages”。 3. 在“NuGet Package Manager”窗口中,在搜索框中输入“MvvmLight”,选择“MvvmLight”并安装。 接下来,我们就可以开始实现MVVM模式了。 1. 创建一个新的ViewModel类,继承自ViewModelBase类。 ```csharp using GalaSoft.MvvmLight; public class MainViewModel : ViewModelBase { //ViewModel的属性和命令 } ``` 2. 在ViewModel中实现需要绑定的属性和命令。 ```csharp private string _message; public string Message { get { return _message; } set { if (_message != value) { _message = value; RaisePropertyChanged(() => Message); } } } private RelayCommand _showMessageCommand; public RelayCommand ShowMessageCommand { get { if (_showMessageCommand == null) { _showMessageCommand = new RelayCommand(() => { MessageBox.Show(Message); }); } return _showMessageCommand; } } ``` 3. 在View中绑定ViewModel的属性和命令。 ```xaml <Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" xmlns:vm="clr-namespace:WpfApp1.ViewModel" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBox Text="{Binding Message, UpdateSourceTrigger=PropertyChanged}" /> <Button Grid.Row="1" Content="Show Message" Command="{Binding ShowMessageCommand}" /> </Grid> </Window> ``` 在这个例子中,我们创建了一个MainViewModel类,其中包含了一个Message属性和一个ShowMessageCommand命令。在View中,我们使用DataContext将ViewModel实例与View绑定,并使用Binding将ViewModel的属性和命令绑定到View上。 这样,我们就成功地使用MvvmLight实现MVVM模式。当用户在TextBox中输入文字并点击Button时,将会弹出一个对话框显示用户输入的文字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值