月度归档:2016年05月

搞定Android Agera-04

Agera更新了,加入了RecycleView DataBinding的支持,先睹为快:

布局文件

<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable name="note" type="Note"/>
    <variable name="click" type="Receiver"/>
    <variable name="longClick" type="Predicate"/>
  </data>
  <TextView ... android:onClick="@{() -> click.accept(note)}"
      android:onLongClick="@{() -> longClick.apply(note)}"
      android:text="@{note.note}"/>
</layout>

dataBindingRepositoryPresenter

配合RepositoryAdapter使用,创建dataBindingRepositoryPresenter

RepositoryAdapter adapter = repositoryAdapter()
  .add(notesStore.getNotesRepository(), dataBindingRepositoryPresenterOf(Note.class)
      .layout(R.layout.text_layout)
      .itemId(BR.note)
      .handler(BR.click,
          (Receiver<Note>) note -> {
              ...;
          })
      .handler(BR.longClick,
          (Predicate<Note>) notesStore::deleteNote)
      .forList())
  .build();

// Setup the recycler view using the repository adapter
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.result);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

比较难理解的部分

.itemId(BR.note)创建了一个staticFunction(),用来提供BR.id
然后和data绑定
viewDataBinding.setVariable(itemId.apply(item), item);

  @Override
  public final void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
    ...
    presenters[resolvedRepositoryIndex].bind(
        data[resolvedRepositoryIndex], resolvedItemIndex, holder);
  }


    @Override
    public void bind(@NonNull final Object item, @NonNull final View view) {
      final ViewDataBinding viewDataBinding = DataBindingUtil.bind(view);
      viewDataBinding.setVariable(itemId.apply(item), item);
      for (final Pair<Integer, Object> handler : handlers) {
        viewDataBinding.setVariable(handler.first, handler.second);
      }
      viewDataBinding.executePendingBindings();
    }

Agera对RecycleView的支持还太弱

如果只是展示一个固定的列表,每次都整体更新,那么可以使用:

  /**
   * Invalidates the data set so {@link RecyclerView} will schedule a rebind of all data.
   */
  @Override
  public final void update() {
    dataInvalid = true;
    notifyDataSetChanged();
  }

GitHub地址

AndroidAgeraTutorial

搞定Android Agera-03

Agera一句话总结:通过事件源驱动指令串执行并提供数据[push event, pull data],对于执行前、执行中的分支处理缺少,等待官方更新。
适合场景:事件源集中,对数据处理简单。

下拉刷新、上拉加载的事件定义

下拉刷新、上拉加载其实就是分页加载,需要提供下拉刷新、上拉加载的事件和页数数据提供,可以定义成一个Repository

    // 默认值
    private int mPagination = 1;
    private MutableRepository<Integer> mMutableRepository;
    // 可提供变化的数据源(accept输入->get输出),数据变化时通知事件
    mMutableRepository = Repositories.mutableRepository(mPagination);

    @Override
    public void onRefresh() {
        mPagination = 1;
        // 更新页数 并 update
        mMutableRepository.accept(mPagination);
    }

    @Override
    public void onLoadMore(int pagination, int pageSize) {
        mRefreshRecyclerView.showLoadMoreView();
        mPagination = pagination;
        // 更新页数 并 update
        mMutableRepository.accept(mPagination);
    }


分页加载数据

通过下拉刷新、上拉加载的事件和页数来加载数据

mLoadDataRepository = Repositories.repositoryWithInitialValue(Result.<ApiResult<GirlInfo>>absent())
        .observe(mMutableRepository)
        .onUpdatesPerLoop()
        .goTo(networkExecutor)
        .attemptGetFrom(new GirlsSupplier(mMutableRepository)).orSkip()
        .thenTransform(new Function<ApiResult<GirlInfo>, Result<ApiResult<GirlInfo>>>() {
            @NonNull
            @Override
            public Result<ApiResult<GirlInfo>> apply(@NonNull ApiResult<GirlInfo> input) {
                return absentIfNull(input);
            }
        })
        .onDeactivation(RepositoryConfig.SEND_INTERRUPT)
        .compile();

RecycleView 数据更新

更新RecycleView比较蛋疼,需要区分下拉和刷新。。。

    @Override
    public void update() {
        Result<ApiResult<GirlInfo>> result = mLoadDataRepository.get();

        result.ifSucceededSendTo(xxx).ifFailedSendTo(yyy);
    }

GitHub地址

AndroidAgeraTutorial

搞定Android Agera-02

goto

默认是在当前线程执行,如果需要访问网络啥的,需要切换至work thread。由于是异步执行可能出现并发,提供了.onDeactivation(int) 和 .onConcurrentUpdate(int) 来取消任务。
不知道怎么切换至UI线程,update默认是切换回原来线程了。

    networkExecutor = Executors.newSingleThreadExecutor();

    mMutableRepository = Repositories.mutableRepository(mPagination);

    mLoadDataRepository = Repositories.repositoryWithInitialValue(Result.<>absent())
            .observe(mMutableRepository)
            .onUpdatesPerLoop()
            .goTo(networkExecutor)
            .attemptGetFrom(new GirlsSupplier(mMutableRepository)).orSkip()
            .onDeactivation(RepositoryConfig.SEND_INTERRUPT)
            .compile();

goLazy

Repository执行到goLazy时,停下来(其实是跳过指令),直到Repository.get()时候返回上一次的值,然后继续执行下面的指令。
有价值的场景:

1. 可能cancel的任务,减少了不必要的处理
2. 如果是异步执行,get会返回上一次的值
3. 同步执行就是顺序的哈,没有效果

  public synchronized Object get() {
    if (runState == PAUSED_AT_GO_LAZY) {
      int index = lastDirectiveIndex;
      runState = RUNNING_LAZILY;
      runFlowFrom(continueFromGoLazy(directives, index), false);
    }
    return currentValue;
  }

但是
mRepository.addUpdatable(this)会触发StartFlow(),就是一定会执行一次,这有卵用。

  @Override
  protected void observableActivated() {
    eventSource.addUpdatable(this);
    maybeStartFlow();
  }

Result and Receiver

可以比较好的处理异常情况

  result.ifSucceededSendTo(this)
        .ifFailedSendTo(new Receiver<Throwable>() {
            @Override
            public void accept(@NonNull Throwable value) {
                
            }
        });

GitHub地址

AndroidAgeraTutorial

搞定Android Agera-01

Repository结合了被观察者和数据提供者,接收事件通知,提供数据(Repository.get())。

Repository组成

一个典型Agera风格的响应式Client由以下几部分组成:

  1. 向Observables注册一个Updatable,用于事件通知;
  2. 可直接调用update,来初始化或更改Client状态;
  3. 等待Observables的通知,拉取最新的数据来更新Client;
  4. 向Observables注销updatable,释放资源。

简单的Repository(Simple repositories)

static repository: 提供相同的数据源而且不生成通知事件,只有get()方法;
mutable repository: 可提供变化的数据源(accept输入->get输出),数据变化时生成通知事件.

    private void setUpRepository() {
        mObservable = new OnClickObservable() {
            @Override
            public void onClick(View view) {
                mRepository.accept(MockRandomData.getRandomImage());
            }
        };

        mRepository = Repositories.mutableRepository(MockRandomData.getRandomImage());
    }

复杂的Repository(Complex repositories)

这个真是超复杂,只做个简单的整理。
可以将很多个Repository、Observable串起来,可以指定执行线程(UI/Work Thred),组成一个“命令”执行流,也可以取消和中止这个执行流。
Agera 提供了repository compiler,可以使用近乎自然语言来声明complex repository。

repository compiler

一个典型声明的顺序:


// 1.声明RepositoryCompiler,并初始化,返回REventSource实例。
Repositories.repositoryWithInitialValue(...);

// 2.指定事件源(Observable),可以多个,返回RFrequency实例。
Event sources - .observe(...);

// 3.设置通知频率(比如click频率限制),返回RFlow实例。
Frequency of reaction - .onUpdatesPer(...) or .onUpdatesPerLoop();

// 4.设置数据源(Supplier),返回RFlow或RTermination实例。
Data processing flow - .getFrom(...), .mergeIn(...), .transform(...), etc.;

// 5.设置数据输出?(Function),返回RConfig实例。
Miscellaneous configurations - .notifyIf(...), .onDeactivation(...), etc.;

// 6.生成Repository实例。
.compile()

非常机智的通告返回类型,达到声明指令有序的目的。
goto返回的是Self,所以可以使用在其中任何位置。

    @NonNull
    TSelf goTo(@NonNull Executor executor);

也可以指定懒加载(goLazy)

    @NonNull
    RSyncFlow<TVal, TPre, ?> goLazy();

总结:指定事件源、限制频率、提供数据源、配置执行线程、结果处理(Function)、支持懒加载,简直是无所不能的万种组合。
ps:还有很多其他的方法,后续慢慢用起来。

数据处理流程(Data processing flow)

当进入数据处理流程,Repository.get()的值作为输入值,往下执行,可以做些异常处理:
.attemptXXX返回RTermination
.check()返回RTermination
RTermination可以跳出或者终止数据处理流程

interface RTermination<TVal, TTerm, TRet> {
//跳过所有未完成的data processing flow
@NonNull
TRet orSkip();
//终止未完成的data processing flow,输入结果:termination clause
@NonNull
TRet orEnd(@NonNull Function<? super TTerm, ? extends TVal> valueFunction);
}

GitHub地址

AndroidAgeraTutorial

搞定Android Agera-00

刚刚阅读完Agera的源码,先写一篇增加搜索曝光度 🙂
阅读源码比阅读文档理解更深,所以开启阅读源码历程(看到好的项目,就去读源码)。
ps:由于RxJava还没研究过,据说是一样的东西,那么从简单的学起吧。

Agera概述

Agera is a super lightweight Android library that helps prepare data for consumption by the Android application components (such as Activities), or objects therein (such as Views), that have life-cycles in one form or another. It introduces a flavor of functional reactive programming, facilitates clear separation of the when, where and what factors of a data processing flow, and enables describing such a complex and asynchronous flow with a single expression, in near natural language.
一句话:Android支持响应式编程(Reactive programming)了。
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
例如,在命令式编程环境中,a := b + c 表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。

关键角色

Agera 提供了一个新的事件响应和数据请求的模型,被称之为 “Push event, pull data”。也就是一个事件发生了,会通过回调来主动告诉你,你关心的事件发生了。然后你需要主动的去获取数据,根据获取到的数据做一些操作。
Observable:agera中的被观察者
Updatable: agera中的观察者//Push event
Supplier: agera中提供数据的,通过get()方法获取数据//Pull data
Repository:agera中集成了Observable和Supplier功能的一个[提供数据的被观察者]

实现功能:改变字体颜色

传统的实现方式:

几行代码(设置click事件,然后设置颜色值)

    @Override
    public void init(Bundle savedInstanceState) {
        mBinding = DataBindingUtil.setContentView(this, R.layout.change_txt_color);

        mBinding.btnChangeColor.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        mBinding.setTxtColor(MockRandomData.getRandomColor());
    }

使用Agera实现:

点击Button->触发Update->get颜色值->setColor

点击Button 触发Update Event
    //一个onClick事件被观察者
    mObservable = new OnClickObservable() {
        @Override
        public void onClick(View view) {
            dispatchUpdate();
        }
    };
    //点击Button的时候,dispatchUpdate()
    mBinding.setObservable(mObservable);
    android:onClick="@{observable::onClick}"

Repository关联

    //数据源定义(颜色值)
    Supplier<Integer> supplier = new Supplier<Integer>() {
        @NonNull
        @Override
        public Integer get() {
            return MockRandomData.getRandomColor();
        }
    };
    //
    mRepository = Repositories.repositoryWithInitialValue(0)
            .observe(mObservable)
            .onUpdatesPerLoop()
            .thenGetFrom(supplier)
            .compile();
    //添加Updatable
    @Override
    protected void onResume() {
        super.onResume();
        mRepository.addUpdatable(this);
    }
    //注销Updatable
    @Override
    protected void onPause() {
        super.onPause();
        mRepository.removeUpdatable(this);
    }

set颜色值

    @Override
    public void update() {
        mBinding.setTxtColor(mRepository.get());
    }

GitHub地址

AndroidAgeraTutorial

Reference

  1. Google Agera 从入门到放弃
  2. Agera Wiki
  3. 开启漫漫的agera之旅

搞定DataBinding-04

使用ObservableField

如果只需要更新部分字段,那么直接使用DataBinding提供的ObservableField就好,简洁方便。

定义ViewModel:isSelected

public class VehicleInfo extends BaseModel {

    public ObservableBoolean isSelected;
    
}

databinding布局

<layout >
    ...
    <data>

        <variable name="info" type="VehicleInfo"/>

    </data>

    <LinearLayout>
        ...

        <ImageView android:background="@{info.isSelected ? 1 : 2}" />
        ...
    </LinearLayout>
</layout>

更新数据:isSelected

   VehicleInfo data = binding.getInfo();

   data.isSelected.set(true);

使用 extends BaseObservable

只是ViewModel定义不同,使用方式同上。

定义ViewModel:isSelected

通过注解Bindable来说明get方法,拉取value。必须是getXXXX()
notifyPropertyChanged()某个字段更新

public class BaseObservableVehicleInfo extends BaseObservable {

    private boolean isSelected;

    @Bindable
    public boolean getIsSelected() {
        return isSelected;
    }

    public void setIsSelected(boolean isSelected) {
        this.isSelected = isSelected;
        notifyPropertyChanged(BR.isSelected);
    }

}

使用 implements Observable

如果不方便继承 BaseObservable,那只好实现Observable接口了。

定义ViewModel:isSelected

只需要实现add/remove/notify

public class ObservableVehicleInfo implements Observable {

    private boolean isSelected;

    @Bindable
    public boolean getIsSelected() {
        return isSelected;
    }

    public void setIsSelected(boolean isSelected) {
        this.isSelected = isSelected;
        notifyPropertyChanged(BR.isSelected);
    }


    //for data binding Observable
    private transient PropertyChangeRegistry mCallbacks;
    @Override
    public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback onPropertyChangedCallback) {
        if (mCallbacks == null) {
            mCallbacks = new PropertyChangeRegistry();
        }
        mCallbacks.add(onPropertyChangedCallback);
    }

    @Override
    public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback onPropertyChangedCallback) {
        if (mCallbacks != null) {
            mCallbacks.remove(onPropertyChangedCallback);
        }
    }

    public synchronized void notifyChange() {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, 0, null);
        }
    }


    public void notifyPropertyChanged(int fieldId) {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, fieldId, null);
        }
    }
}

GitHub地址

AndroidDataBindingTutorial

搞定DataBinding-03

Java中实现的观察者模式核心角色

Observable(被观察者):

当数据发生改变时,向它的各个Observer(观察者)发出通知;

public class Observable {
    public synchronized void addObserver(Observer o) {
        ...
    }

    public synchronized void deleteObserver(Observer o) {
        ...
    }
    /**
     * 通知Observer数据变化
     */
    public void notifyObservers() {
        notifyObservers(null);
    }
}
Observer(观察者):

向Observable(可观察者)注册,当接收通知时更新

public interface Observer {

    /**
     * Observable变化时调用
     */
    void update(Observable observable, Object data);
}

DataBinding观察者

DataBinding观察者模式实现有些区别,对数据只实现了”pull”的方式。
Observer(观察者):
ViewDataBinding自动完成向Observable(被观察者)注册和更新
Observable(被观察者):
Observable可以继承BaseObservable、实现Observable接口、使用ObservableField
Android实现的BaseObservable

public class BaseObservable implements Observable {
    private transient PropertyChangeRegistry mCallbacks;//注册Observers

    public BaseObservable() {
    }
}

不想继承BaseObservable,可以直接使用ObservableField:

ObservableField<T>
ObservableParcelable<T extends Parcelable>
ObservableArrayList
ObservableArrayMap
ObservableBoolean
...

DataBinding调用链路

  1. Observable的定义
  2. ViewDataBinding创建
  3. executeBindings(),会注册好回调(Observer)
  4. Observable数据更新
  5. ViewDataBinding.requestRebind()
定义Observable
public class VehicleInfo extends BaseModel {
    private ObservableBoolean isSelected;
    private String  logoUrl;
    private String  brand;
    private String  description;
}
创建ViewDataBinding
@Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
    final VehicleInfo info = getItem(position);
    if(holder instanceof ViewHolder){
        ViewDataBinding binding = ((ViewHolder) holder).getBinding();
        binding.setVariable(BR.info, info);
        binding.setVariable(BR.itemCLick, itemListener);
        binding.setVariable(BR.selectedCLick, selectedListener);
        binding.executePendingBindings();
    }
}
注册观察者

在executePendingBindings()中完成,CreateWeakListener充当Observer

private boolean updateRegistration(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    if (observable == null) {
        return unregisterFrom(localFieldId);
    }
    WeakListener listener = mLocalFieldObservers[localFieldId];
    if (listener == null) {
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }
    if (listener.getTarget() == observable) {
        return false;//nothing to do, same object
    }
    unregisterFrom(localFieldId);
    registerTo(localFieldId, observable, listenerCreator);
    return true;
}

Observable通知

Observable.OnPropertyChangedCallback()回调更新

   VehicleInfo data = binding.getInfo();
   data.getIsSelected().set(true);

public void set(boolean value) {
    if (value != mValue) {
        mValue = value;
        notifyChange();
    }
}

//mCallbacks.get(i) --> WeakPropertyListener extends OnPropertyChangedCallback
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);

public void onNotifyCallback(Observable.OnPropertyChangedCallback 
            callback, Observable sender,int arg, Void notUsed) {
    callback.onPropertyChanged(sender, arg);
}

//WeakPropertyListener notify之后 回调的方法
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback{
    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        ViewDataBinding binder = mListener.getBinder();
        if (binder == null) {
            return;
        }
        Observable obj = mListener.getTarget();
        if (obj != sender) {
            return; // notification from the wrong object?
        }
        binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
    }
}
ViewDataBinding更新View

handleFieldChange(),如果数据发生变化,调用requestRebind()
不过只会更新需要更新的View,在executeBindings()方法中,这个逻辑比较复杂也不易阅读。
mDirtyFlags标示每一个binding,通过BitMask判断是否需要更新。

    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            requestRebind();
        }
    }

@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    ...
    //只有isSelectedInfoAndroi改变了, 才更新
    if ((dirtyFlags &amp;amp; 0x13L) != 0) {
        ...
    }
}

GitHub地址

AndroidDataBindingTutorial

Reference

  1. data-binding-part-observer-pattern
  2. Android Data Binding从抵触到爱不释手
  3. 观察者模式
  4. BitMask 使用参考