RoboBinding logo RoboBinding Follow @robobinding

1. RoboBinding是什么?

Android应用的粘接剂 - RoboBinding是一个实现了数据绑定 Presentation Model 模式的Android开源框架。RoboBinding 帮助你编写更可读,易于测试与维护的UI代码。

2. Hello AndroidMVVM(Presentation Model)

基于RoboBinding的应用,一个Activity通常由三个部分组成:Layout xml,Activity class 与 PresentatonModel。 以下以最简单的AndroidMVVM为例。

2.1. Layout

在layout中声明RoboBinding命名空间,属性与事件绑定。

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:bind="http://robobinding.org/android">
    <TextView
        bind:text="{hello}" />
        ...
    <Button
        android:text="Say Hello"
        bind:onClick="sayHello"/>
</LinearLayout>

2.2. Presentation Model(pure POJO)

在presentation model类中声明相应的属性与方法。Presentation model 为纯POJO类。

org.robobinding.androidmvvm.PresentationModel.java

@org.robobinding.annotation.PresentationModel
public class PresentationModel implements HasPresentationModelChangeSupport {
    private String name;
    public String getHello() {
        return name + ": hello Android MVVM(Presentation Model)!";
    }
    ...
    public void sayHello() {
        firePropertyChange("hello");
    }
}

2.3. Activity Class

在activity类中将layout与对应的presentation model绑定在一起。

org.robobinding.androidmvvm.MainActivity.java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        PresentationModel presentationModel = new PresentationModel();
        View rootView = Binders.inflateAndBindWithoutPreInitializingViews(this, R.layout.activity_main, presentationModel);
        setContentView(rootView);
    }
}

最简单的AndroidMVVM就完成了。这里我们用Binders工具类。在实际的开发中,我们推荐使用org.robobinding.binder.BinderFactoryBuilder。 然后将BinderFactory实例保存在Android application实例里或由第三方类库如RoboGuice来管理,以便在整个应用中共享。

2.4. RoboBinding的介绍视频

  • 2014年7月 Cheng Wei做的介绍视频可以在 这里找到。

  • 2012年2月 Robert Taylor做的介绍视频可以在 这里找到。

3. 开发环境

3.1. Eclipse

首先安装Eclipse

3.1.1. 不使用AspectJ

robobinding-[version]-with-dependencies.jarrobobinding-[version].jar + [google guava]-[11.0.1+].jar 加入项目的libs目录, 然后右击项目→Properties→Java Build Path→Libraries→Add Jars,将jar加入classpath。并在Order and Export中勾选它(或它们)。

Eclipse build settings
Figure 1. 项目构建设置(以robobinding-[version]-with-dependencies.jar为例)

3.1.2. 使用AspectJ

RoboBinding aspects可以帮你自动的产生代码,以便减少工作量。 首先安装Android Development Tools(ADT) Eclipse 插件。 右击项目→Configure→Convert to AspectJ Project。AspectJ natures将被添加到项目的.project文件。

robobinding-[version]-with-aop-and-dependencies.jarrobobinding-[version]-with-aop.jar + [google guava]-[11.0.1+].jar 加入项目的classpath。 并在Order and Export中勾选它(或它们)。

最后确认 robobinding jar在Aspect路径里,右击项目→Properties→AspectJ Build→Aspect Path→Add JARs。

Aspectj settings
Figure 2. AspectJ设置(以robobinding-[version]-with-aop-and-dependencies.jar为例)

3.1.3. Annotaton Processing设置

下载RoboBinding的codegen-[version]-with-dependencies.jar。然后按照下图进行设置。codegen-XX.jar只用于代码产生,项目不依赖于这个jar。

Annotation processing settings
Figure 3. Annotaton Processing设置

3.2. Android Studio

3.2.1. 不使用AspectJ

在gradle.build里添加robobinding依赖。

dependencies {
    ...
    compile"org.robobinding:robobinding:${robobindingVersion}"

    //或者使用with-dependencies jar(RoboBinding提供一个proguard过的体积小的with-dependencies jar。)。
    compile("org.robobinding:robobinding:${robobindingVersion}:with-dependencies") {
        exclude group: 'com.google.guava', module: 'guava'
    }
}

请参考RoboBinding 下的AndroidMVVM, RoboBinding-album-sample以及RoboBinding-gallery例子项目。

3.2.2. 使用AspectJ

在gradle.build里添加RoboBinding Android aspectj plugin。

buildscript {
    repositories {
        ...
        maven() {
            name 'RoboBinding AspectJPlugin Maven Repository'
            url "https://github.com/RoboBinding/RoboBinding-aspectj-plugin/raw/master/mavenRepo"
        }
    }

    dependencies {
        ...
        classpath 'org.robobinding:aspectj-plugin:0.8.+'
    }
}

...
apply plugin: 'org.robobinding.android-aspectj'

在gradle.build里添加robobinding依赖。

dependencies {
    ...
    compile "org.robobinding:robobinding:$robobindingVersion"
    aspectPath "org.robobinding:robobinding:$robobindingVersion"

    //或者使用with-aop-and-dependencies jar(RoboBinding提供一个proguard过的体积小的with-aop-and-dependencies jar。)。
    compile ("org.robobinding:robobinding:$robobindingVersion:with-aop-and-dependencies") {
        exclude group: 'com.google.guava', module: 'guava'
    }
    aspectPath ("org.robobinding:robobinding:$robobindingVersion:with-aop-and-dependencies") {
        exclude group: 'com.google.guava', module: 'guava'
    }
}

请参考RoboBinding 下的 RoboBinding-album-sample以及RoboBinding-gallery例子项目。

3.2.3. Annotation processing设置

在gradle.build里添加apt plugin。

buildscript {
    repositories {
        ...
    }

    dependencies {
        ...
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.+'
    }
}

...
apply plugin: 'com.neenbedankt.android-apt'

3.3. ProGuard

保留PresentationModel的共公方法以及生成的代码的构造函数信息。保留所有的annoations。

-keepattributes *Annotation*,Signature
-keep,allowobfuscation @interface org.robobinding.annotation.PresentationModel

-keep @org.robobinding.annotation.PresentationModel class * {
    public *** *(...);
}

-keep class * implements org.robobinding.itempresentationmodel.ItemPresentationModel{
    public *** *(...);
}

-keep class * extends org.robobinding.presentationmodel.AbstractPresentationModelObject{
    public <init>(...);
}

-keep class * extends org.robobinding.presentationmodel.AbstractItemPresentationModelObject{
    public <init>(...);
}

增加以下的内容,使ProGuard保留view listeners的构造函数名:

-keepclassmembers class * implements org.robobinding.viewattribute.ViewListeners {
    public <init>(...);
}

增加以下的内容,抑制google guava的javax.annotation.XX 引用警告。

-dontwarn javax.annotation.**

请参考RoboBinding 下的 RoboBinding-album-sample例子项目的ProGuard配置文件[project]/app/proguard-rules.txt。

4. 主要概念与特性

robobinding_based_app.png
Figure 4. 基于RoboBinding的Android应用

一个Android应用包含了若干个Activity以及其它的元素。在基于RoboBinding的应用里一个Activity包含了Activity主文件,Layout以及PresentationModel类。 (而Android普通应用一个Activity只包含了Activity主文件与Layout)。原先位于Activity内的显示层逻辑被提取到独立的文件PresentationModel内。 Activity利用RoboBinding将Layout与对应的显示逻辑PresentationModel绑定在一起。把Layout中的显示数据与PresentationModel中的属性绑定; Layout中的事件与PresentationModel中的方法绑定。RoboBinding替代了原先在Activity中的UI关联代码,减少应用代码。 在理想的情况下,PresentationModel只包含显示逻辑不包含UI代码,便与独立测试。

以下的例子代码来自 Robobinding Gallery

4.1. 单向属性绑定

单向绑定是指presentation model上的属性更新会自动的同步到相应的视图属性上。

activity_view.xml

<TextView
    bind:visibility="{integerVisibility}"/>

ViewPresentationModel.java

public int getIntegerVisibility() {
    return integerVisibilityRotation.value();
}

RoboBinding遵循Java Beans标准,当暴露属性时,我们将提供getter与setter方法。 单向绑定时,presentation model中的属性只要求有getter。因为视图不会更新回presentation model。 已支持的UI绑定属性,请参考API与支持的绑定属性JavaDocs

4.2. 双向属性绑定

双向绑定在单向绑定的基础上,增加了将视图上的变更同步回presentation model相应的属性上。

EditText的text属性是支持双向绑定的一个例子。双向绑定的语法是在单向绑定属性的前面加一个$符号。

activity_edittext.xml

<EditText
    bind:text="${text}"/>

org.robobinding.gallery.presentationmodel.EditTextPresentationModel.java

@PresentationModel
public class EditTextPresentationModel {
    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

当将属性声明为双向绑定时,presentation model的对应属性必须有setter方法,以便于视图的更新值被设置到presentation model属性上。

4.3. 事件处理

即将视图中的事件绑定到presentation model相应的方法上。

activity_gallery.xml

<Button
    bind:onClick="showDemo"/>

org.robobinding.gallery.presentationmodel.GalleryPresentationModel.java

@PresentationModel
public class GalleryPresentationModel
{
    ...
    public void showDemo()
    {
        ...
    }
}

当onClick 事件被触发时,showDemo方法被调用。showDemo方法可以带有可选的相应的事件参数,这里为org.robobinding.widget.view.ClickEvent。 已支持的UI事件,请参考API与支持的绑定属性JavaDocs

4.4. AdapterViews绑定

当我们需要绑定AdapterViews,RoboBinding需要你在presentation model上提供数据集属性。数据集属性类型可以是一个Array,List或者 org.robobinding.itempresentationmodel.TypedCursor。 除此之外,我们还要提供ItemPresentationModel(即数据项presentation model),以便将每个数据项的视图绑定到ItemPresentationModel上。 RoboBinding里,我们通过在数据集属性上使用@ItemPresentationModel annotation做到。

activity_adapter_view.xml

<ListView
    bind:itemLayout="@android:layout/simple_list_item_1"
    bind:itemMapping="[text1.text:{value}]"
    bind:source="{dynamicStrings}"/>

org.robobinding.gallery.presentationmodel.AdapterViewPresentationModel.java

@PresentationModel
public class AdapterViewPresentationModel
{
    ...
    @ItemPresentationModel(value=StringItemPresentationModel.class)
    public List<String> getDynamicStrings()
    {
        return getSelectedSource().getSample();
    }

以下提供ItemPresentationModel以及数据项layout。Android中Adapter实现了重用,所以ItemPresentationModel也会被重用,即需要实现updateData方法。

org.robobinding.gallery.presentationmodel.StringItemPresentationModel.java

public class StringItemPresentationModel implements ItemPresentationModel<String>
{
    private String value;

    @Override
    public void updateData(int index, String bean)
    {
        value = bean;
    }

    public String getValue()
    {
        return value;
    }
}

例子中数据项layout为android系统提供的simple_list_item_1.xml。通过bind:itemMapping="[text1.text:{value}]", 我们指定了simple_list_item_1.xml的text1.text关联到StringItemPresentationModel.value属性。

@ItemPresentationModel有一个factoryMethod属性。当ItemPresentationModel有一些外部的依赖时, 我们可以在PresentationModel里提供一个factoryMethod方法来创建这些ItemPresentationModels。 这样我们可以为这些ItemPresentationModel提供外部依赖并对其进行任意的配置。例如:

@PresentationModel
public class PresentationModelSample
{
    ...
    @ItemPresentationModel(value=ItemPresentationModelSample.class, factoryMethod="createItemPresentationModelSample")
    public List<String> getDynamicStrings()
    {
        return getSelectedSource().getSample();
    }

    public ItemPresentationModelSample createItemPresentationModelSample() {
        return ItemPresentationModelSample(dependency1, dependency2, ...);
    }

4.5. 羽量级关系数据与对象cursor映射

AdapterViews绑定中,我们提到数据集属性类型的其中一种为org.robobinding.itempresentationmodel.TypedCursor。 由于应用中我们通常都习惯于操作对象并尽量隔离关系数据操作的那部分代码,RoboBinding加入了羽量级对象化的Cursor - TypedCursor。 通过org.robobinding.itempresentationmodel.RowMapper<T>来将一行的关系数据映射为一个对象实例。

org.robobinding.gallery.presentationmodel.TypedCursorPresentationModel.java

@PresentationModel
public class TypedCursorPresentationModel {
    ...
    @ItemPresentationModel(value=ProductItemPresentationModel.class)
    public TypedCursor<Product> getProducts() {
        return allProductsQuery.execute(db);
    }
}

org.robobinding.gallery.model.typedcursor.GetAllQuery.java

public class GetAllQuery<T>
{
    private String tableName;
    private final RowMapper<T> rowMapper;

    public GetAllQuery(String tableName, RowMapper<T> rowMapper)
    {
        ...
        this.tableName = tableName;
        this.rowMapper = rowMapper;
    }

    public TypedCursor<T> execute(SQLiteDatabase db)
    {
        Cursor cursor = db.query(
                tableName,
                null,
                null,
                null,
                null,
                null,
                BaseColumns._ID+" ASC");
        return new TypedCursorAdapter<T>(cursor, rowMapper);
    }
}

org.robobinding.gallery.model.typedcursor.ProductRowMapper.java

public class ProductRowMapper implements RowMapper<Product> {

    @Override
    public Product mapRow(Cursor cursor) {
        String name = cursor.getString(cursor.getColumnIndex(ProductTable.NAME));
        String description = cursor.getString(cursor.getColumnIndex(ProductTable.DESCRIPTION));
        return new Product(name, description);
    }

}

4.6. 菜单绑定

将 res/menu 下的菜单资源与对应的Presentation Model绑定在一起。以下是一个简单的例子。

res/menu/context_menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://robobinding.org/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:title="Delete Product"
          bind:onMenuItemClick="deleteProduct"
          android:id="@+id/deleteProduct"
          app:showAsAction="always"/>

</menu>

org.robobinding.gallery.presentationmodel.ContextMenuPresentationModel.java

@PresentationModel
public class ContextMenuPresentationModel {
    ...
    public void deleteProduct(MenuItem menuItem) {
        ...
    }
}

4.7. Presentation Model

我们需要在每个PresentationModel上用@org.robobinding.annotation.PresentationModel进行标注。 当在PresentationModel里需要用到org.robobinding.presentationmodel.PresentationModelChangeSupport时, 我们的PresentationModel需要实现org.robobinding.presentationmodel.HasPresentationModelChangeSupport接口。 这样框架内部通过这个接口来使用同一个PresentationModelChangeSupport实例。

有两种方式实现PresentationModel。即使用AspectJ与不使用AspectJ。以下为两种方式的对比。

4.7.1. 不使用AspectJ

  • 依赖于robobinding-[version].jar 或 robobinding-[version]-with-dependencies.jar。

  • 其优点是无需额外的AspectJ依赖。最后的apk相对会小一点。

  • 需要手动编写firePropertyChange("propertyName")代码。

可以参考AndroidMVVMAndroid-CleanArchitecture项目。

4.7.2. 使用AspectJ

  • 使用robobinding-[version]-with-aop.jar或robobinding-[version]-with-aop-and-dependencies.jar。

  • 很多的firePropertyChange("propertyName")代码由aspectJ自动生成。

  • 缺点是必须依赖于AspectJ Runtime Library。最后的apk大小会增加一点。

可以参考Album SampleGallery项目。

5. 创建视图绑定实现

以下的例子代码来自 Robobinding Gallery

5.1. 简单的单向绑定属性

例如我们为android.View添加一个enabled绑定属性 - 源代码

@ViewBinding(simpleOneWayProperties = {"enabled"})
public class ViewBindingForView extends CustomViewBinding<View> {
}

然后在BinderFactoryBuilder加入 - 源代码

new BinderFactoryBuilder()
  .add(new ViewBindingForView().extend(View.class))
  .build();

RoboBinding 为我们自动产生以下代码。

public class ViewBindingForView$$VB implements ViewBinding<View>{
    final ViewBindingForView customViewBinding;

    public ViewBindingForView$$VB(ViewBindingForView customViewBinding) {
        this.customViewBinding = customViewBinding;
    }

    @Override
    public void mapBindingAttributes(BindingAttributeMappings<View> mappings) {
        mappings.mapOneWayProperty(ViewBindingForView$$VB.EnabledAttribute.class, "enabled");
        customViewBinding.mapBindingAttributes(mappings);
    }

    public static class EnabledAttribute implements OneWayPropertyViewAttribute<View, Boolean>
    {
        @Override
        public void updateView(View view, Boolean newValue) {
            view.setEnabled(newValue);
        }
    }
}

由于所有的绑定都是静态的,对性能没有影响。

5.2. 其它绑定实现

除了以上简单单向属性绑定之外,还有事件绑定,多值属性绑定与复合属性绑定。当要实现这些绑定时,我们需要自己扩展CustomViewBinding实现绑定。 例如实现事件绑定如下。

@ViewBinding
public class MyCustomViewBinding extends CustomViewBinding<CustomView> {
  @Override
  public void mapBindingAttributes(BindingAttributeMappings<CustomView> mappings) {
    mappings.mapEvent(OnCustomEventAttribute.class, "onCustomEvent");
  }

  public class OnCustomEventAttribute implements EventViewAttributeForView {
    ...
  }
}

其它的绑定实现,可以参考RoboBinding的绑定属性实现 - widget包下的源代码

5.3. 自定义组件或第三方组件

通过为自定义组件,第三方组件或未实现绑定的Android widget提供视图绑定实现,使它们更易于使用。在RoboBinding中,视图绑定实现方法是一致的。

custom_component.png
Figure 5. 自定义 Title Description Bar

我们以上图的自定义组件TitleDescriptionBar为例。该组件包含了标题与描述两个部分。在输入新的标题与描述后,点击'Apply',自定义组件的内容就更新为新的内容。

我们想使TitleDescriptonBar组件使用起来能像以下示例一样简单

activity_custom_component.xml

<org.robobinding.gallery.model.customcomponent.TitleDescriptionBar
        bind:title="{title}"
        bind:description="{description}"/>

以下是TitleDescriptionBar自定义组件的实现代码主要部分(如何实现自定义组件,请参考Android文档):

org.robobinding.gallery.model.customcomponent.TitleDescriptionBar.java

public class TitleDescriptionBar extends LinearLayout {
    private TextView title;
    private TextView description;

    public TitleDescriptionBar(Context context, AttributeSet attrs) {
        this(context, attrs, R.layout.title_description_bar);
    }

    protected TitleDescriptionBar(Context context, AttributeSet attrs, int layoutId) {
        super(context, attrs);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(layoutId, this);
        title = (TextView) findViewById(R.id.title);
        description = (TextView) findViewById(R.id.description);
        ...
    }

    public void setTitle(CharSequence titleText) {
        title.setText(titleText);
    }

    public void setDescription(CharSequence descriptionText) {
        description.setText(descriptionText);
    }
}

自定义组件的layout:title_description_bar.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://robobinding.org/android">
    <TextView android:id="@+id/title"/>
    <TextView android:text=": "/>
    <TextView android:id="@+id/description"/>

5.3.1. 实现绑定属性

TitleDescriptionBar有标题与描述两个简单单向属性绑定 - 源代码

@ViewBinding(simpleOneWayProperties = {"title", "description"})
public class TitleDescriptionBarBinding extends CustomViewBinding<TitleDescriptionBar> {
}

5.3.2. 注册视图绑定实现

通过org.robobinding.binder.BinderFactoryBuilder注册视图绑定实现 - 源代码

BinderFactory binderFactory = new BinderFactoryBuilder()
  .add(new TitleDescriptionBarBinding().forView(TitleDescriptionBar.class))
  .build();

这样我们很容易的就完成了视图绑定实现。我们以同样的方式可以为任何第三方组件或未实现绑定的Android widget提供视图绑定实现。

5.4. 覆盖已有的视图绑定实现

当RoboBinding框架已有的视图绑定实现不满足需求, 我们可以注册覆盖已有的框架所提供的默认实现。

BinderFactory binderFactory = new BinderFactoryBuilder()
  .add(new MyViewBindingForView().forView(View.class))
  .build();

@ViewBinding
static class MyViewBindingForView extends CustomViewBinding<View> {
  ...
}

5.5. 扩展已有的视图绑定实现

扩展已有的TextViewBinding并添加typeface属性绑定。

BinderFactory binderFactory = new BinderFactoryBuilder()
  .add(new MyTextViewBinding().extend(TextView.class))
  .build();

@ViewBinding(simpleOneWayProperties={"typeface"})
static class MyTextViewBinding extends CustomViewBinding<TextView> {
  ...
}

6. Album唱片集例子项目学习

唱片集例子项目是Martin Fowler原始版本基于RoboBinding的Android翻译(Martin Fowler基于.Net的 原始版本 )。 项目地址 RoboBinding-album-sample

album_sample_prototype.png
Figure 6. Album唱片集例子原型

以下"."表示相对于org.robobinding.albumsample的包路径。

以上是Album唱片集例子原型图。项目遵循RoboBinding应用的标准结构,即一个Activity由Activity主文件,Layout与PresentationModel Java文件组成。 项目源代码中包含以下几个包:org.robobinding.albumsample.activity包含所有Activity的主文件,org.robobinding.albumsample.presentationmodel包含所有PresentationModel文件, org.robobinding.albumsample.model仅包含一个Album实体实现文件,org.robobinding.albumsample.store包含一个基于内存Album实体存储实现AlbumStore。接下来列出上述五张图所对应的实现文件。

图[Home Activity]由.activity.HomeActivity,home_activity.xml与.presentationmodel.HomePresentationModel组成。

图[View Albums Activity]由.activity.ViewAlbumsActivity,view_albums_activity.xml与.presentationmodel.ViewAlbumsPresentationModel组成; 其唱片集每行的唱片信息由.presentationmodel.AlbumItemPresentationModel与album_row.xml组成;以及一个当唱片集为空时Layout显示文件albums_empty_view.xml。

图[Create Album Activity]与图[Edit Album Activity]由相同的.activity.CreateEditAlbumActivity,create_edit_album_activity.xml与.presentationmodel.CreateEditAlbumPresentationModel组成。

图[View Album Activity]由.activity.ViewAlbumActivity,view_album_activity.xml与.presentationmodel.ViewAlbumPresentationModel组成; 其删除对话框由.activity.DeleteAlbumDialog,delete_album_dialog.xml与.presentationmodel.DeleteAlbumDialogPresentationModel组成。

以下以[View Albums Activity]为例,对源代码做简单介绍。Activity类ViewAlbumsActivity只做了一件事,就是把Layout view_albums_activity.xml与ViewAlbumsPresentationModel关联起来。 view_albums_activity.xml里包含了三个子视图按顺序为TextView, ListView与Button。TextView没有包含任何绑定信息。 ListView的bind:source="{albums}"绑定到ViewAlbumsPresentationModel.albums数据集属性。 bind:onItemClick="viewAlbum"绑定到ViewAlbumsPresentationModel.viewAlbum(ItemClickEvent)方法,单击某个唱片项时,该事件方法将被调用。 bind:emptyViewLayout="@layout/albums_empty_view"设置了当唱片集为空时的显示内容Layout。 bind:itemLayout="@layout/album_row"设置了唱片项的行显示Layout,结合在ViewAlbumsPresentationModel.albums上给出的数据项PresentationModel,即@ItemPresentationModel(AlbumItemPresentationModel.class), 来显示每一个唱片行。在album_row.xml里包含了两个简单的TextView,其bind:text="{title}"bind:text="{artist}"分别绑定到AlbumItemPresentationModel.title/artist属性。 在view_albums_activity.xml里的最后一个Button视图,bind:onClick="createAlbum"绑定到ViewAlbumsPresentationModel.createAlbum()方法。

以下的入口类都位于Robobinding Gallery项目的org.robobinding.gallery.activity包下。

  • View绑定属性示例。入口类为ViewActivity。

  • EditText绑定属性示例。入口类为EditTextActivity。

  • AdapterView绑定属性示例。入口类为AdapterViewActivity。

  • ListView绑定属性示例。入口类为ListViewActivity。

  • RecyclerView绑定属性示例。入口类为RecyclerViewActivity。

  • 自定义组件绑定属性示例。入口类为CustomComponentActivity。

  • 对象化Cursor示例。入口类为TypedCursorActivity。

  • Fragment与ViewPager绑定示例。入口类为ListFragmentDemoActivity。

  • Options Menu绑定示例。入口类为OptionsMenuActivity。

  • Context Menu绑定示例。入口类为ContextMenuDemoActivity。

  • Contextual Action Mode绑定示例。入口类为ContextualActionModeActivity。

8. 项目结构组织与最佳实践

Presentation Model(MVVM)模式的初终是在MVC模式的基础之上,进一步的解耦,将UI的状态与逻辑放入 POJO Presentation Model,可被很容易的独立单元测试。 并使得View层→Presentation Model层→Model层 形成一个单向的依赖关系。当应用这个模式时,我们应该始终遵循这些基本原则。 Album Sample 是一个遵循最佳实践的例子。 推荐阅读Martin Fowler的原始 Presentation Model文章

8.1. 总体项目结构

Project structure
Figure 7. Project structure

在Android项目中,View层由Activity(Fragment)与Layout组成,Model层即业务模型层包含了各种的Services,持久化层,网络访问服务,以及业务服务等。 各层之间的依赖与访问关系如上图的箭头所示,View层不直接调用业务模型层。

8.2. 常见设计问题的解决方案

  • 当没有使用第三方依赖注入库时,业务模型层对象可以由View层的Activity实例化后传入Presentation Model。但View层不直接调用业务模型层。 只有Presentation Model与业务模型层交互。

  • 有些时候,Presentation Model层需要调用或使用View层的功能。我们通过给View层加入一个接口,然后让Presentation Model依赖于这个View接口的方式来解耦。 使PresentationModel能继续保持可被方便的独立单元测试。如果需要的话,我们可以将这些View接口归在Presentation Model层或Presentation Model包内, 使得依赖关系保持View→PresentationModel→Model的单向关系。以下是一个简单的示例:

interface MainView {
    void doSomeViewLogic();
}

class MainActivity extends Activity implements MainView {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        PresentationModel presentationModel = new PresentationModel(this);
        ...
    }

    public void doSomeViewLogic() {
        ...
    }
}

class PresentationModel {
    private MainView mainView;

    public PresentationModel(MainView mainView) {
        this.mainView = mainView;
    }

    public void someEvent() {
        mainView.doSomeViewLogic();
    }
}

9. 其它资源

2012年一月 Robert Taylor 写了一些入门的文章在 这里这里

2012年二月 在London SkillsMatter,Robert Taylor作的RoboBinding介绍视频可以在 这里找到。

2014年7月 Cheng Wei做的RoboBinding介绍视频可以在 这里找到。

2014年9月 Adil在 YOW 2014 Android MVVM 演讲如何用MVVM模式写出干净,易维护,可测试的Android代码。

AndroidMVVM 是一个使用MVVM模式最小的例子。

RoboBinding album sample 是Martin Fowler的 Presentation Model 模式原始例子的基于RoboBinding的Android翻译版本。

RoboBinding Gallery 展示RoboBinding的各种功能与特性的用法。