阅读 2390

Jetpack之LiveData

首先放一张官方推荐的app设计架构图,想要了解更多(芝麻之门

LocalBroadcastManager

冷落的LBM

LocalBroadcastManager有点冷落,一个是很少人知道并且合理使用广播,很多人要么使用的是系统的全局广播BraoadCastRecever,要么使用EventBusRxAndroid等等其他观察者模式的三方库,慢慢的就失宠了。然鹅,并不只是这样,当现在你的项目迁移到Androidx时,会发现官方居然把它废弃了,随后官方明确指出最适合的替代品LiveDatareactive streams,见以下描述 (官方传送门

关于LBM

LocalBroadcastManager顾名思义就是本地广播,也是基于观察者模式的事件总线,用于应用内通信,比较安全和高效,虽然迁移到androidx后,在1.1.0高版本LocalBroadcastManager被标记过时,但是它的用处还是蛮大的,相比系统的广播,本地广播有着无法比拟的优越性,而且非常高效,相比第三方观察者模式库,满足条件下我宁愿使用1.0.0版本的LocalBroadcastManager

与系统广播的区别

  • 范围上:LocalBroadcastManager为本地广播,只能接受自身App发送的广播,只能用于应用内之间的通信,范围相对较小;而系统的BraoadCastRecever可以实现跨进程通讯,范围更大。
  • 效率上:LocalBroadcastManager通信核心是Handler,所以只能用于应用内通信,安全和效率都很高;而系统的BraoadCastRecever通信核心是Binder机制, 实现跨进程通信,范围更广,导致运行效率稍微逊一点。
  • 安全上:LocalBroadcastManager由于核心是Handler,而且只能动态注册,只能用于app内通信,安全上更加的有保障;而系统的BraoadCastRecever容易被利用,安全上相对较弱一点。

使用方式

注册广播:

IntentFilter filter = new IntentFilter();
filter.addAction(“你的Action”);
LocalBroadcastManager.getInstance(Context context).registerReceiver(BroadcastReceiver receiver, filter);
复制代码

发送广播:

LocalBroadcastManager.getInstance(Context context).sendBroadcast(Intent intent);
复制代码

取消注册:

LocalBroadcastManager.getInstance(this).unregisterReceiver(BroadcastReceiver receiver);
复制代码

接收广播:

final class MyBroadcastReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent!=null && “你的Action”.equals(intent.getAction())){
               // 处理逻辑
            }
        }
    }
复制代码

在《阿里巴巴Android开发手册(正式版)_v1.0.0》中有明确指出关于广播的使用规范问题,以下规范等级为强制,所以我们应用合理正确的使用广播,遵循绿色公约,避免信息泄露和拦截意图的风险,以下为规范描述:

如果广播仅限于应用内,则可以使用LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent 拦截的风险。

源码分析

    @NonNull
    public static LocalBroadcastManager getInstance(@NonNull Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());
            }
            return mInstance;
        }
    }
    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        mHandler = new Handler(context.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    }
复制代码

初始化上LocalBroadcastManager明显采取的是懒汉模式的单例模式,并且初始化的时候创建了一个Handler对象,在主线程上进行工作,用于处理发送的广播,这就是和系统BroadCastReciver的最大区别,系统广播则是通过Binder机制进行通信的,而本地广播采取的是Handler机制进行通信。

 private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers = new HashMap<>();
 private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new  HashMap<>();
 private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
 
 public void registerReceiver(@NonNull BroadcastReceiver receiver,
            @NonNull IntentFilter filter) {
        synchronized (mReceivers) {
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList<>(1);
                mReceivers.put(receiver, filters);
            }
            filters.add(entry);
            for (int i=0; i<filter.countActions(); i++) {
                String action = filter.getAction(i);
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
                entries.add(entry);
            }
        }
    }
复制代码

注册广播则明显能够看出是通过2个Map嵌套集合mReceiversmActions来进行协调运作的,而mReceivers则是以receiver作为key,存储的是一个接收记录ReceiverRecord集合,而ReceiverRecord是用来存储筛选器IntentFilter与广播接收器BroadcastReceiver及广播运行状态的,但是由于LocalBroadcastManager是单例模式,可以存放多个广播接收器BroadcastReceiver故采用的集合嵌套,同理mActions是以筛选器中的action作为key,存储的也是ReceiverRecord集合。

  public boolean sendBroadcast(@NonNull Intent intent) {
        synchronized (mReceivers) {
            final String action = intent.getAction();
            final String type = intent.resolveTypeIfNeeded(
                    mAppContext.getContentResolver());
            final Uri data = intent.getData();
            final String scheme = intent.getScheme();
            final Set<String> categories = intent.getCategories();
            ==========================省略部分===================
                if (receivers != null) {
                    for (int i=0; i<receivers.size(); i++) {
                        receivers.get(i).broadcasting = false;
                    }
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }
复制代码

明显看出本地广播是通过Handler进行发送消息的,检查排队队列是否有未发布的MSG_EXEC_PENDING_BROADCASTS,没有的话发送消息。

    void executePendingBroadcasts() {
        while (true) {
            final BroadcastRecord[] brs;
            synchronized (mReceivers) {
                final int N = mPendingBroadcasts.size();
                if (N <= 0) {
                    return;
                }
                brs = new BroadcastRecord[N];
                mPendingBroadcasts.toArray(brs);
                mPendingBroadcasts.clear();
            }
            for (int i=0; i<brs.length; i++) {
                final BroadcastRecord br = brs[i];
                final int nbr = br.receivers.size();
                for (int j=0; j<nbr; j++) {
                    final ReceiverRecord rec = br.receivers.get(j);
                    if (!rec.dead) {
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }
复制代码

当工作线程接收到Handler发送的消息时候,会执行executePendingBroadcasts()方法,发送广播的时候mPendingBroadcasts来存储Intent意图与广播接收器ReceiverRecord,当处理广播的时候,从mPendingBroadcasts中取出信息存放到广播接收器数组BroadcastRecord[]中,通过循环调用广播接收的抽象方法onReceive(Context context,Intent intent)进行消息发送到前台主线程,就这样完成了应用内通信。

初识LiveData

以下翻译可能有槽点,去官网查看更多更详细的信息(芝麻之门

介绍

LiveData是官方架构组件,是Jetpack众多组件的一个,它是一个可观察的数据持有者类。与常规的可观察对象不同,LiveData是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,比如activitiesfragments或者services。这种意识确保LiveData只更新处于活动生命周期状态的应用程序组件观察者。

LiveData认为,如果一个观察者的生命周期处于STARTEDRESUMED状态,那么这个观察者(由observer类表示)就处于活动状态。LiveData只将更新通知活动观察者。注册为监视LiveData对象的非活动观察者不会收到有关更改的通知。

您可以注册一个与实现LifecycleOwner接口的对象配对的观察者。当相应的生命周期对象的状态更改为DESTROYED时,此关系允许删除观察者。这对于活动和片段特别有用,因为它们可以安全地观察LiveData对象,而不用担心泄漏——当activitiesfragments的生命周期被破坏时,它们会立即取消订阅。

优势

  • UI界面与数据实时保证一致
    LiveData遵循观察者模式。当生命周期状态发生变化时,LiveData通知观察者Observer对象。您可以合并代码来更新这些观察者对象中的UI。您的观察者可以在每次发生更改时更新UI,而不是每次应用程序数据更改时都更新UI

  • 不会造成内存泄漏
    观察者被绑定到生命周期Lifecycle对象,并在其关联的生命周期被destroyed后进行清理。

  • 当界面销毁或者停止活动不会造成崩溃
    如果观察者的生命周期是不活动的,例如在后堆栈中的活动,那么它不会接收任何LiveData事件。

  • 不需要手动处理生命周期
    UI组件只观察相关数据,不停止或恢复观察。LiveData自动管理所有这些,因为它在观察过程中知道相关的生命周期状态变化。

  • 总是保证最新的数据
    如果一个生命周期变为不活动的,它将在再次活动时接收最新的数据。例如,在后台的活动在返回到前台后立即接收最新的数据。

  • 适当的配置更改
    如果某个activitysfragments由于配置更改(如设备旋转)而重新创建,它将立即接收最新可用数据。

  • 共享资源和数据
    您可以使用singleton模式扩展LiveData对象来包装系统服务,以便在您的应用程序中共享它们。LiveData对象连接到系统服务一次,然后任何需要该资源的观察者都可以查看LiveData对象。有关更多信息,请参见Extend LiveData

属性及方法

返回类型 方法 说明
T getValue() 返回T类型的对象值
boolean hasActiveObservers() 当前是否有活动的观察者observers
boolean hasObservers() 当前是否有观察者observers
void observe(LifecycleOwner owner, Observer<? super T> observer) 添加观察者对象到列表,配合生命周期
void observeForever(Observer<? super T> observer) 添加观察者对象到列表,无生命周期
void removeObserver(Observer<? super T> observer) 从观察者列表中移除给定的观察者
void removeObservers(LifecycleOwner owner) 删除与给定生命周期所有者绑定的所有观察者
void onActive() 当活动观察者的数量从0变为1时调用
void onInactive() 当活动观察者的数量从1变为0时调用
void postValue(T value) 将任务发布到主线程以设置给定值
void setValue(T value) 设置更新数据源

使用步骤

  • 依赖
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:1.1.1"
// just ViewModel
implementation "android.arch.lifecycle:viewmodel:1.1.1"
// just LiveData
implementation "android.arch.lifecycle:livedata:1.1.1"
复制代码
  • 创建LiveData实例来保存特定类型的数据。通常会结合ViewModel一起使用,也可以单独使用。
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> currentName;
    public MutableLiveData<String> getCurrentName() {
        if (currentName == null) {
            currentName = new MutableLiveData<String>();
        }
        return currentName;
    }
// Rest of the ViewModel...
}
复制代码

注意:确保将更新UI的LiveData对象存储在ViewModel对象中,而不是存储在Activity或Fragment中,原因如下: 以避免膨胀的Activity和Fragment。现在这些UI控制器负责显示数据,而不是保存数据状态。 将LiveData实例与特定的Activity或Fragment实例解耦,并允许LiveData对象在配置更改后仍然存在。

  • 创建一个观察者对象Observer,该对象定义onChanged()方法,该方法控制LiveData对象持有的数据更改时发生的情况。通常在UI控制器中创建一个观察者对象,例如一个Activity或者fragment
public class NameActivity extends AppCompatActivity {

    private NameViewModel model;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Other code to setup the activity...
        // Get the ViewModel.
        model = ViewModelProviders.of(this).get(NameViewModel.class);
        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                nameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.getCurrentName().observe(this, nameObserver);
    }
}
复制代码

在大多数情况下,应用程序组件的onCreate()方法是开始观察LiveData对象的合适位置,原因如下: 以确保系统不会从活动或fragment的onResume()方法发出冗余调用。 确保活动或片段具有数据,可以在活动时立即显示这些数据。一旦应用程序组件处于启动状态,它就会从它所观察的LiveData对象接收到最近的值。只有在设置了要观察的LiveData对象时才会发生这种情况。 通常,LiveData只在数据发生更改时提供更新,并且只向活动观察者提供更新。这种行为的一个例外是,当观察者从非活动状态更改为活动状态时,也会收到更新。此外,如果观察者第二次从非活动状态更改为活动状态,则只有当值自上次活动以来发生更改时,才会接收到更新。

  • 使用observe()方法将观察者对象附加到LiveData对象。方法接受LifecycleOwner对象。这订阅观察者对象到LiveData对象,以便在发生更改时通知它。通常将观察者对象附加到UI控制器中,例如ActivityFragment
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.getCurrentName().observe(this, nameObserver);
复制代码

您可以使用observeForever(observer)方法注册一个没有关联LifecycleOwner对象的观察者。在这种情况下,观察者总是被认为是活跃的,因此总是被通知修改。您可以删除调用removeObserver(Observer)方法的这些观察者。

  • LiveData扩展用法
public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}
复制代码

当LiveData对象具有活动观察者时,将调用onActive()方法。这意味着您需要开始从该方法观察股票价格更新。

当LiveData对象没有任何活动的观察者时,将调用onInactive()方法。因为没有观察者在听,所以没有理由保持与StockManager服务的连接。

setValue(T)方法更新LiveData实例的值,并将更改通知任何活动的观察者。

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}
复制代码

方法将fragment(它是LifecycleOwner的一个实例)作为第一个参数传递。这样做意味着这个观察者被绑定到与所有者关联的Lifecycle对象上,这意味着:

如果Lifecycle对象没有处于活动状态,那么即使值发生了更改,也不会调用观察者 销毁生命周期对象后,将自动删除观察者。

LiveData对象是生命周期感知的,这意味着您可以在多个ActivitysFragmentsservice之间共享它们。为了保持示例的简单性,可以将LiveData类实现为单例,如下所示:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}
复制代码

ActivityFragmentService中可以这么调用,通过单例实现共享数据

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(symbol).observe(this, price -> {
            // Update the UI.
        });
    }
}
复制代码

转化 LiveData

在将LiveData对象发送给观察者之前,您可能希望更改存储在LiveData对象中的值,或者您可能需要根据另一个LiveData实例的值返回另一个LiveData实例。生命周期包提供转换类,该类包含支持这些场景的helper方法。

Transformations.map ()

对存储在LiveData对象中的值应用函数,并将结果向下传播

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});
复制代码

Transformations.switchMap()

与map()类似,将函数应用于存储在LiveData对象中的值,并将结果解包并向下分派。传递给switchMap()的函数必须返回LiveData对象,如下例所示:

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
复制代码

您可以使用转换方法在观察者的生命周期中携带信息。除非观察者正在观察返回的LiveData对象,否则不会计算转换。因为转换是延迟计算的,所以与生命周期相关的行为是隐式传递的,不需要额外的显式调用或依赖关系。

如果您认为在ViewModel对象中需要一个生命周期对象,那么转换可能是一个更好的解决方案。例如,假设您有一个UI组件,它接受一个地址并返回该地址的邮政编码。您可以为这个组件实现简单的视图模型,如下面的示例代码所示:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS,不推荐
       return repository.getPostCode(address);
    }
}
复制代码

然后,UI组件需要从以前的LiveData对象注销注册,并在每次调用getPostalCode()时注册到新实例。此外,如果重新创建UI组件,它将触发对repository.getPostCode()方法的另一个调用,而不是使用前一个调用的结果。

相反,您可以将邮政编码查询实现为地址输入的转换,如下面的示例所示:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}
复制代码

在这种情况下,该postalCode字段被定义为转换addressInput。只要您的应用程序具有与该postalCode字段关联的活动观察者,就会在addressInput更改时重新计算并检索字段的值 。

此机制允许较低级别的应用程序创建LiveData按需延迟计算的对象。甲ViewModel对象可以容易地获得以引用LiveData的对象,然后在它们的顶部限定的变换规则。

创建新的转换

有十几种不同的特定转换可能对您的应用有用,但默认情况下不提供。要实现自己的转换,您可以使用MediatorLiveData该类,该类侦听其他LiveData对象并处理它们发出的事件。MediatorLiveData正确地将其状态传播到源LiveData对象。要了解有关此模式的更多信息,请参阅Transformations 该类的参考文档 。

合并多个LiveData源

MediatorLiveData是一个子类LiveData,允许您合并多个LiveData源。MediatorLiveData 只要任何原始LiveData源对象发生更改,就会触发对象的观察者。

例如,如果LiveDataUI中有一个可以从本地数据库或网络更新的对象,则可以将以下源添加到该 MediatorLiveData对象:

  • LiveData与存储在数据库中的数据关联的对象。

  • LiveData与从网络访问的数据关联的对象。

 LiveData liveData1 = ...;
 LiveData liveData2 = ...;

 MediatorLiveData liveDataMerger = new MediatorLiveData<>();
 liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
 liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
复制代码

总结

读到这里,是不是发现LiveData即有点像EventBus,又有点像Rxjava,这正是他的强大之处,另外它还能够结合组件的生命周期使用,不仅能够共享资源与数据,而且UI与数据同步也是非常实时的,正如官方对Jetpack组件的描述,不仅能够提高开发效率,而且让应用变得更加优质,如果你还未体验过,赶紧试一下吧。

关注下面的标签,发现更多相似文章
评论