请点赞,你的点赞对我意义重大,满足下我的虚荣心。
Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] 跟我一起成长。
前言
LiveData 是 Jetpack 组件中较常用的组件之一,曾经也是实现 MVVM 模式的标准组件之一,不过目前 Google 更多推荐使用 Kotlin Flow 来代替 LiveData;
虽然 LiveData 不再是 Google 主推的组件,但考虑到 LiveData 依然存在于大量存量代码中,以及 LiveData 伴随着 Android 生态发展过程中衍生的问题和解决方案,我认为 LiveData 依然有存在的意义。虽然我们不再优先使用 LiveData,但不代表学习 LiveData 没有价值。
这篇文章是 Jetpack 系列文章第 2 篇,专栏文章列表:
一、架构组件:
1、Lifecycle:生命周期感知型组件的基础
2、LiveData:生命周期感知型数据容器(本文)
3、ViewModel:数据驱动型界面控制器
4、Flow:LiveData 的替代方案
5、从 MVC 到 MVP、MVVM、MVI:Android UI 架构演进
6、ViewBinding:新一代视图绑定方案
7、Fragment:模块化的微型 Activity
8、RecyclerView:可复用型列表视图
9、Navigation:单 Activity 多 Fragment 的导航方案
10、Dagger2:从 Dagger2 到 Hilt 玩转依赖注入(一)
11、Hilt:从 Dagger2 到 Hilt 玩转依赖注入(二)
12、OnBackPressedDispatcher:处理回退事件的新姿势
二、其他:
1、AppStartup:轻量级初始化框架
2、DataStore:新一代键值对存储方案
3、Room:ORM 数据库访问框架
4、WindowManager:加强对多窗口模式的支持
5、WorkManager:加强对后台任务的支持
6、Compose:新一代视图开发方案
1. 认识 LiveData
1.1 为什么要使用 LiveData?
LiveData 是基于 Lifecycle 框架实现的生命周期感知型数据容器,能够让数据观察者更加安全地应对宿主(Activity / Fragment 等)生命周期变化,核心概括为 2 点:
1、自动取消订阅: 当宿主生命周期进入消亡(DESTROYED)状态时,LiveData 会自动移除观察者,避免内存泄漏;
2、安全地回调数据: 在宿主生命周期状态低于活跃状态(STAETED)时,LiveData 不会回调数据,避免产生空指针异常或不必要的性能损耗;当宿主生命周期不低于活跃状态(STAETED)时,LiveData 会重新尝试回调数据,确保观察者接收到最新的数据。
1.2 LiveData 的使用方法
1、添加依赖: 在 build.gradle 中添加 LiveData 依赖,需要注意区分过时的方式:
// 过时方式(lifecycle-extensions 不再维护)
implementation "androidx.lifecycle:lifecycle-extensions:2.4.0"
// 目前的方式:
def lifecycle_version = "2.5.0"
// Lifecycle 核心类
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
2、模板代码: LiveData 通常会搭配 ViewModel 使用,以下为使用模板,相信大家都很熟悉了:
NameViewModel.kt
class NameViewModel : ViewModel() {
val currentName: MutableLiveData by lazy {
MutableLiveData()
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val model: NameViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// LiveData 观察者
val nameObserver = Observer { newName ->
// 更新视图
nameTextView.text = newName
}
// 注册 LiveData 观察者,this 为生命周期宿主
model.currentName.observe(this, nameObserver)
// 修改 LiveData 数据
button.setOnClickListener {
val anotherName = "John Doe"
model.currentName.value = anotherName
}
}
}
3、注册观察者: LiveData 支持两种注册观察者的方式:
LiveData#observe(LifecycleOwner, Observer) 带生命周期感知的注册: 更常用的注册方式,这种方式能够获得 LiveData 自动取消订阅和安全地回调数据的特性;
LiveData#observeForever(Observer) 永久注册: LiveData 会一直持有观察者的引用,只要数据更新就会回调,因此这种方式必须在合适的时机手动移除观察者。
Observer.java
// 观察者接口
public interface Observer {
void onChanged(T t);
}
4、设置数据: LiveData 设置数据需要利用子类 MutableLiveData 提供的接口:setValue() 为同步设置数据,postValue() 为异步设置数据,内部将 post 到主线程再修改数据。
MutableLiveData.java
public class MutableLiveData extends LiveData {
// 异步设置数据
@Override
public void postValue(T value) {
super.postValue(value);
}
// 同步设置数据
@Override
public void setValue(T value) {
super.setValue(value);
}
}
1.3 LiveData 存在的局限
LiveData 是 Android 生态中一个的简单的生命周期感知型容器。简单即是它的优势,也是它的局限,当然这些局限性不应该算 LiveData 的缺点,因为 LiveData 的设计初衷就是一个简单的数据容器,需要具体问题具体分析。对于简单的数据流场景,使用 LiveData 完全没有问题。
1、LiveData 只能在主线程更新数据: 只能在主线程 setValue,即使 postValue 内部也是切换到主线程执行;
2、LiveData 数据重放问题: 注册新的订阅者,会重新收到 LiveData 存储的数据,这在有些情况下不符合预期(具体见第 TODO 节);
3、LiveData 不防抖问题: 重复 setValue 相同的值,订阅者会收到多次 onChanged()
回调(可以使用 distinctUntilChanged()
优化);
4、LiveData 丢失数据问题: 在数据生产速度 > 数据消费速度时,LiveData 无法观察者能够接收到全部数据。比如在子线程大量 postValue
数据但主线程消费跟不上时,中间就会有一部分数据被忽略。
1.4 LiveData 的替代者
1、RxJava: RxJava 是第三方组织 ReactiveX 开发的组件,Rx 是一个包括 Java、Go 等语言在内的多语言数据流框架。功能强大是它的优势,支持大量丰富的操作符,也支持线程切换和背压。然而 Rx 的学习门槛过高,对开发反而是一种新的负担,也会带来误用的风险。
2、Kotlin Flow: Kotlin Flow 是基于 Kotlin 协程基础能力搭建的一套数据流框架,从功能复杂性上看是介于 LiveData 和 RxJava 之间的解决方案。Kotlin Flow 拥有比 LiveData 更丰富的能力,但裁剪了 RxJava 大量复杂的操作符,做得更加精简。并且在 Kotlin 协程的加持下,Kotlin Flow 目前是 Google 主推的数据流框架。
关于 Kotlin Flow 的更多内容,我们在 4、Flow:LiveData 的替代方案 这篇文章讨论过。
2. LiveData 实现原理分析
2.1 注册观察者的执行过程
LiveData 支持使用 observe() 或 observeForever() 两种方式注册观察者,其内部会分别包装为 2 种包装对象:
1、observe(): 将观察者包装为 LifecycleBoundObserver 对象,它是 Lifecycle 框架中 LifecycleEventObserver
的实现类,因此它可以绑定到宿主(参数 owner)的生命周期上,这是实现 LiveData 自动取消订阅和安全地回调数据的关键;
2、observeForever(): 将观察者包装为 AlwaysActiveObserver,不会关联宿主生命周期,当然你也可以理解为全局生命周期。
注意: LiveData 内部会禁止一个观察者同时使用 observe() 和 observeForever() 两种注册方式。但同一个 LiveData 可以接收 observe() 和 observeForever() 两种观察者。
LiveData.java
private SafeIterableMap, ObserverWrapper> mObservers = new SafeIterableMap<>();
// 注册方式 1:带生命周期感知的注册方式
@MainThread
public void observe(LifecycleOwner owner, Observer observer) {
// 1.1 主线程检查
assertMainThread("observe");
// 1.2 宿主生命周期状态是 DESTROY,则跳过
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
// 1.3 将 Observer 包装为 LifecycleBoundObserver
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// 1.4 禁止将 Observer 绑定到不同的宿主上
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer with different lifecycles");
}
if (existing != null) {
return;
}
// 1.5 将包装类注册到宿主声明周期上
owner.getLifecycle().addObserver(wrapper);
}
// 注册方式 2:永久注册的方式
@MainThread
public void observeForever(Observer observer) {
// 2.1 主线程检查
assertMainThread("observeForever");
// 2.2 将 Observer 包装为 AlwaysActiveObserver
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
// 2.3 禁止将 Observer 注册到生命周期宿主后又进行永久注册
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing instanceof LiveData.LifecycleBoundObserver) {
throw new IllegalArgumentException("Cannot add the same observer with different lifecycles");
}
if (existing != null) {
return;
}
// 2.4 分发最新数据
wrapper.activeStateChanged(true);
}
// 注销观察者
@MainThread
public void removeObserver(@NonNull final Observer observer) {
// 主线程检查
assertMainThread("removeObserver");
// 移除
ObserverWrapper removed = mObservers.remove(observer);
if (removed == null) {
return;
}
// removed.detachObserver() 方法:
// LifecycleBoundObserver 最终会调用 Lifecycle#removeObserver()
// AlwaysActiveObserver 为空实现
removed.detachObserver();
removed.activeStateChanged(false);
}
2.2 生命周期感知源码分析
LifecycleBoundObserver 是 LifecycleEventObserver
的实现类,当宿主生命周期变化时,会回调其中的 LifecycleEventObserve#onStateChanged() 方法:
LiveData$ObserverWrapper.java
private abstract class ObserverWrapper {
final Observer mObserver;
boolean mActive;
// 观察者持有的版本号
int mLastVersion = START_VERSION; // -1
ObserverWrapper(Observer observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {
}
void activeStateChanged(boolean newActive) {
// 同步宿主的生命状态
if (newActive == mActive) {
return;
}
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
// STARTED 状态以上才会尝试分发数据
if (mActive) {
dispatchingValue(this);
}
}
}
Livedata$LifecycleBoundObserver.java
// 注册方式:observe()
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer observer) {
super(observer);
mOwner = owner;
}
// 宿主的生命周期大于等于可见状态(STARTED),认为活动状态
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
// 宿主生命周期进入 DESTROYED 时,会移除观察者
if (currentState == DESTROYED) {
removeObserver(mObserver);
return;
}
Lifecycle.State prevState = null;
while (prevState != currentState) {
prevState = currentState;
// 宿主从非可见状态转为可见状态(STARTED)时,会尝试触发数据分发
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
AlwaysActiveObserver.java
// 注册方式:observeForever()
private class AlwaysActiveObserver extends ObserverWrapper {
AlwaysActiveObserver(Observer observer) {
super(observer);
}
@Override
boolean shouldBeActive() {
return true;
}
}
2.3 同步设置数据的执行过程
LiveData 使用 setValue() 方法进行同步设置数据(必须在主线程调用),需要注意的是,设置数据后并不一定会回调 Observer#onChanged() 分发数据,而是需要同时满足 2 个条件:
条件 1: 观察者绑定的生命周期处于活跃状态;
observeForever() 观察者:一直处于活跃状态;
observe() 观察者:owner 宿主生命周期处于活跃状态。
条件 2: 观察者的持有的版本号小于 LiveData 的版本号时。
LiveData.java
// LiveData 持有的版本号
private int mVersion;
// 异步设置数据 postValue() 最终也是调用到 setValue()
@MainThread
protected void setValue(T value) {
// 主线程检查
assertMainThread("setValue");
// 版本号加一
mVersion++;
mData = value;
// 数据分发
dispatchingValue(null);
}
// 数据分发
void dispatchingValue(ObserverWrapper initiator) {
// 这里的标记位和嵌套循环是为了处理在 Observer#onChanged() 中继续调用 setValue(),
// 而产生的递归设置数据的情况,此时会中断旧数据的分发,转而分发新数据,这是丢失数据的第 2 种情况。
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
// onStateChanged() 走这个分支,只需要处理单个观察者
considerNotify(initiator);
initiator = null;
} else {
// setValue() 走这个分支,需要遍历所有观察者
for (Iterator, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
// 尝试触发回调,只有观察者持有的版本号小于 LiveData 持有版本号,才会分发回调
private void considerNotify(ObserverWrapper observer) {
// STARTED 状态以上才会尝试分发数据
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
// 版本对比
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
// 分发回调
observer.mObserver.onChanged((T) mData);
}
总结一下回调 Observer#onChanged() 的情况:
1、注册观察者时,观察者绑定的生命处于活跃状态,并且 LiveData 存在已设置的旧数据;
2、调用 setValue() / postValue() 设置数据时,观察者绑定的生命周期处于活跃状态;
3、观察者绑定的生命周期由非活跃状态转为活跃状态,并且 LiveData 存在未分发到该观察者的数据(即观察者持有的版本号小于 LiveData 持有的版本号);
提示: observeForever() 虽然没有直接绑定生命周期宿主,但可以理解为绑定的生命周期是全局的,因此在移除观察者之前都是活跃状态。
2.4 异步设置数据的执行过程
LiveData 使用 postValue() 方法进行异步设置数据(允许在子线程调用),内部会通过一个临时变量 mPendingData 存储数据,再通过 Handler 将切换到主线程并调用 setValue(临时变量)。因此,当在子线程连续 postValue() 时,可能会出现中间的部分数据不会被观察者接收到。
LiveData.java
final Object mDataLock = new Object();
static final Object NOT_SET = new Object();
// 临时变量
volatile Object mPendingData = NOT_SET;
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
// 重置临时变量
mPendingData = NOT_SET;
}
// 真正修改数据的地方,也是统一到 setValue() 设置数据
setValue((T) newValue);
}
};
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
// 临时变量被重置时,才会发送修改的 Message,这是出现背压的第 1 种情况
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
总结一下 LiveData 可能丢失数据的场景,此时观察者可能不会接收到所有的数据:
情况 1(背压问题): 使用 postValue() 异步设置数据,并且观察者的消费速度小于数据生产速度;
情况 2: 在观察者处理回调(Observer#obChanged())的过程中重新设置新数据,此时会中断旧数据的分发,部分观察者将无法接收到旧数据;
情况 3: 观察者绑定的生命周期处于非活跃状态时,连续使用 setValue() / postValue() 设置数据时,观察将无法接收到中间的数据。
注意: 丢失数据不一定是需要解决的问题,需要视场景分析。
2.5 LiveData 数据重放原因分析
LiveData 的数据重放问题也叫作数据倒灌、粘性事件,核心源码在 LiveData#considerNotify(Observer) 中:
首先,LiveData 和观察者各自会持有一个版本号 version,每次 LiveData#setValue 或 postValue 后,LiveData 持有的版本号会自增 1。在 LiveData#considerNotify(Observer) 尝试分发数据时,会判断观察者持有版本号是否小于 LiveData 的版本号(Observer#mLastVersion >= LiveData#mVersion 是否成立),如果成立则说明这个观察者还没有消费最新的数据版本。
而观察者的持有的初始版本号是 -1,因此当注册新观察者并且正好宿主的生命周期是大于等于可见状态(STARTED)时,就会尝试分发数据,这就是数据重放。
为什么 Google 要把 LiveData 设计为粘性呢?LiveData 重放问题需要区分场景来看 —— 状态适合重放,而事件不适合重放:
当 LiveData 作为一个状态使用时,在注册新观察者时重放已有状态是合理的;
当 LiveData 作为一个事件使用时,在注册新观察者时重放已经分发过的事件就是不合理的。
3. LiveData 数据重放问题的解决方案
这里我们总结一下业界提出处理 LiveData 数据重放问题的方案:
3.1 Event 事件包装器
实现一个事件包装器,内部使用一个标志位标记事件是否已经被消费过。这样的话,当观察者收到重放的数据时,由于其中的标记位已经显示被消费,因此会抛弃该事件。
不过,虽然这个方法能够解决数据倒灌问题,但是会有副作用:对于多个观察者的情况,只允许第一个观察者消费,而后续的观察者无法消费实现,这一般是不能满足需求的。
open class Event(private val content: T)
3.2 SingleLiveData 事件包装器变型方案
SingeLiveData 是 Google 官方的方案,在 LiveData 内部通过一个原子标志位来标记事件是否已经被消费过。这个方法本质上和 Event 实现包装器是一样的,因此也存在完全相同的副作用。
SingleLiveEvent.java
public class SingleLiveEvent extends MutableLiveData {
private static final String TAG = "SingleLiveEvent";
// 消费标记位
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
3.3 反射修改观察者版本号
业界分享出来的一个方案,不确定思路原创源。实现方法是在注册新观察者时,通过反射的手段将观察者持有的版本号(Observer#mLastVersion)同步为 LiveData 的版本号。缺点是使用反射,但确实能够解决多观察者问题。
private void hook(@NonNull Observer observer) throws Exception {
//get wrapper's version
Class classLiveData = LiveData.class;
Field fieldObservers = classLiveData.getDeclaredField("mObservers");
fieldObservers.setAccessible(true);
Object objectObservers = fieldObservers.get(this);
Class classObservers = objectObservers.getClass();
Method methodGet = classObservers.getDeclaredMethod("get", Object.class);
methodGet.setAccessible(true);
Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);
Object objectWrapper = null;
if (objectWrapperEntry instanceof Map.Entry) {
objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();
}
if (objectWrapper == null) {
throw new NullPointerException("Wrapper can not be bull!");
}
Class classObserverWrapper = objectWrapper.getClass().getSuperclass();
Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");
fieldLastVersion.setAccessible(true);
//get livedata's version
Field fieldVersion = classLiveData.getDeclaredField("mVersion");
fieldVersion.setAccessible(true);
Object objectVersion = fieldVersion.get(this);
//set wrapper's version
fieldLastVersion.set(objectWrapper, objectVersion);
}
3.4 UnPeekLiveData 反射方案优化
UnPeekLiveData 是 KunMinX 提出并开源的方案,主要思路是将 LiveData 源码中的 Observer#mLastVersion 和 LiveData#mVersion 在子类中重新实现一遍。在 UnPeekLiveData 中会有一个原子整型来标记数据版本,并且每个 Observer 在注册时会拿到当前 LiveData 的最新数据版本,而在 Observer#onChanged 中会对比两个版本号来决定是否分发。这个过程中没有使用反射,也不会存在不支持多观察者的问题。
ProtectedUnPeekLiveData.java
public class ProtectedUnPeekLiveData extends LiveData {
private final static int START_VERSION = -1;
private final AtomicInteger mCurrentVersion = new AtomicInteger(START_VERSION);
protected boolean isAllowNullValue;
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
super.observe(owner, createObserverWrapper(observer, mCurrentVersion.get()));
}
@Override
public void observeForever(@NonNull Observer observer) {
super.observeForever(createObserverWrapper(observer, mCurrentVersion.get()));
}
public void observeSticky(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
super.observe(owner, createObserverWrapper(observer, START_VERSION));
}
public void observeStickyForever(@NonNull Observer observer) {
super.observeForever(createObserverWrapper(observer, START_VERSION));
}
@Override
protected void setValue(T value) {
mCurrentVersion.getAndIncrement();
super.setValue(value);
}
class ObserverWrapper implements Observer {
private final Observer mObserver;
private int mVersion = START_VERSION;
public ObserverWrapper(@NonNull Observer observer, int version) {
this.mObserver = observer;
this.mVersion = version;
}
@Override
public void onChanged(T t) {
if (mCurrentVersion.get() > mVersion && (t != null || isAllowNullValue)) {
mObserver.onChanged(t);
}
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ObserverWrapper that = (ObserverWrapper) o;
return Objects.equals(mObserver, that.mObserver);
}
@Override
public int hashCode() {
return Objects.hash(mObserver);
}
}
@Override
public void removeObserver(@NonNull Observer observer) {
if (observer.getClass().isAssignableFrom(ObserverWrapper.class)) {
super.removeObserver(observer);
} else {
super.removeObserver(createObserverWrapper(observer, START_VERSION));
}
}
private ObserverWrapper createObserverWrapper(@NonNull Observer observer, int version) {
return new ObserverWrapper(observer, version);
}
public void clear() {
super.setValue(null);
}
}
UnPeekLiveData.java
public class UnPeekLiveData extends ProtectedUnPeekLiveData {
@Override
public void setValue(T value) {
super.setValue(value);
}
@Override
public void postValue(T value) {
super.postValue(value);
}
public static class Builder {
private boolean isAllowNullValue;
public Builder setAllowNullValue(boolean allowNullValue) {
this.isAllowNullValue = allowNullValue;
return this;
}
public UnPeekLiveData create() {
UnPeekLiveData liveData = new UnPeekLiveData<>();
liveData.isAllowNullValue = this.isAllowNullValue;
return liveData;
}
}
}
3.5 Kotlin Flow
Google 对 Flow 的定位是 Kotlin 环境下对 LiveData 的替代品,使用 SharedFlow 可以控制重放数量,可以设置为 0 表示禁止重放。
4. 基于 LiveData 的事件总线 LiveDataBus
如果我们把事件理解为一种数据,LiveData 可以推数据自然也可以推事件,于是有人将 LiveData 封装为 “广播”,从而实现 “事件发送者” 和 “事件观察者” 的代码解耦,例如美团版本的 LiveDataBus。相较于 EventBus,LiveDataBus 实现更强的生命周期安全;相较于接口,LiveData 的约束力更弱。
4.1 LiveDataBus 什么场景适合?
无论是 EventBus 还是 LiveDataBus,它们本质上都是 “多对多的广播”,它们仅适合作为全局的事件通信,而页面内的事件通信应该继续采用 ViewModel + LiveData 等方案。这是因为事件总线缺乏 MVVM 模式建立的唯一可信源约束,事件发出后很难定位是哪个消息源推送出来的。
4.2 LiveDataBus 的实现
LiveDataBus 代码不多,核心在于使用哈希表保存事件名到 LiveData 的映射关系:
LiveDataBus.java
public final class LiveDataBus {
// 事件名 - LiveData 哈希表
private final Map> bus;
private LiveDataBus() {
bus = new HashMap<>();
}
// 全局单例模式
private static class SingletonHolder {
private static final LiveDataBus DEFAULT_BUS = new LiveDataBus();
}
public static LiveDataBus get() {
return SingletonHolder.DEFAULT_BUS;
}
// 根据事件名映射 LiveData
public MutableLiveData with(String key, Class type) {
if (!bus.containsKey(key)) {
// 构造新的 LiveData 对象
bus.put(key, new BusMutableLiveData<>());
}
return (MutableLiveData) bus.get(key);
}
// 根据事件名映射 LiveData
public MutableLiveData with(String key) {
return with(key, Object.class);
}
private static class ObserverWrapper implements Observer {
private Observer observer;
public ObserverWrapper(Observer observer) {
this.observer = observer;
}
@Override
public void onChanged(@Nullable T t) {
if (observer != null) {
if (isCallOnObserve()) {
return;
}
observer.onChanged(t);
}
}
private boolean isCallOnObserve() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if (stackTrace != null && stackTrace.length > 0) {
for (StackTraceElement element : stackTrace) {
if ("android.arch.lifecycle.LiveData".equals(element.getClassName()) &&
"observeForever".equals(element.getMethodName())) {
return true;
}
}
}
return false;
}
}
private static class BusMutableLiveData extends MutableLiveData {
private Map observerMap = new HashMap<>();
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
super.observe(owner, observer);
try {
hook(observer);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void observeForever(@NonNull Observer observer) {
if (!observerMap.containsKey(observer)) {
observerMap.put(observer, new ObserverWrapper(observer));
}
super.observeForever(observerMap.get(observer));
}
@Override
public void removeObserver(@NonNull Observer observer) {
Observer realObserver = null;
if (observerMap.containsKey(observer)) {
realObserver = observerMap.remove(observer);
} else {
realObserver = observer;
}
super.removeObserver(realObserver);
}
// 也可以使用其他方案
private void hook(@NonNull Observer observer) throws Exception {
//get wrapper's version
Class classLiveData = LiveData.class;
Field fieldObservers = classLiveData.getDeclaredField("mObservers");
fieldObservers.setAccessible(true);
Object objectObservers = fieldObservers.get(this);
Class classObservers = objectObservers.getClass();
Method methodGet = classObservers.getDeclaredMethod("get", Object.class);
methodGet.setAccessible(true);
Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);
Object objectWrapper = null;
if (objectWrapperEntry instanceof Map.Entry) {
objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();
}
if (objectWrapper == null) {
throw new NullPointerException("Wrapper can not be bull!");
}
Class classObserverWrapper = objectWrapper.getClass().getSuperclass();
Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");
fieldLastVersion.setAccessible(true);
//get livedata's version
Field fieldVersion = classLiveData.getDeclaredField("mVersion");
fieldVersion.setAccessible(true);
Object objectVersion = fieldVersion.get(this);
//set wrapper's version
fieldLastVersion.set(objectWrapper, objectVersion);
}
}
}
使用 LiveDataBus:
LiveDataBus.get().with("key_test").setValue("");
LiveDataBus.get()
.with("key_test", String.class)
.observe(this, new Observer() {
@Override
public void onChanged(@Nullable String s) {
}
});
4.3 如何加强 LiveDataBus 事件约束
无论是 EventBus 还是 LiveDataBus 都没有对事件定义进行约束,不同开发者 / 不同组件可能会定义相同的事件字符串而导致冲突。
为了优化这个问题,可以使用美团 ModularEventBus 方案:用接口定义事件来实现强约束,在动态代理中取 接口名_方法名
作为事件名,再完成后续 LiveDataBus 的交互。
LiveDataBus.java
class LiveDataBus {
fun of(clz: Class): E {
if(!clz.isInterface){
throw IllegalArgumentException("API declarations must be interfaces.")
}
if(0 < clz.interfaces.size){
throw IllegalArgumentException("API interfaces must not extend other interfaces.")
}
return Proxy.newProxyInstance(clz.classLoader, arrayOf(clz), InvocationHandler { _, method, _->
// 取“接口名_方法名”作为事件名,再转交给 LiveDataBus
return@InvocationHandler get().with(
"${clz.canonicalName}_${method.name}",
(method.genericReturnType as ParameterizedType).actualTypeArguments[0].javaClass)
}) as E
}
}
另外,事件接口可以交给 APT 注解处理器生成:通过 DemoEvent 定义事件名常量,用 APT 将事件名转换为事件接口的方法:
DemoEvent.java
//可以指定module,若不指定,则使用包名作为module名
@ModuleEvents()
public class DemoEvents {
//不指定消息类型,那么消息的类型默认为Object
public static final String EVENT1 = "event1";
//指定消息类型为自定义Bean
@EventType(TestEventBean.class)
public static final String EVENT2 = "event2";
//指定消息类型为java原生类型
@EventType(String.class)
public static final String EVENT3 = "event3";
}
EventsDefineOfDemoEvents.java
package com.sankuai.erp.modularevent.generated.com.meituan.jeremy.module_b_export;
public interface EventsDefineOfDemoEvents extends com.sankuai.erp.modularevent.base.IEventsDefine {
com.sankuai.erp.modularevent.Observable EVENT1();
com.sankuai.erp.modularevent.Observable EVENT2(
);
com.sankuai.erp.modularevent.Observable EVENT3();
}
使用:
LiveDataBus
.get()
.of(EventsDefineOfDemoEvents::class.java)
.EVENT1()
.post(true)
LiveDataBus
.get()
.of(EventsDefineOfDemoEvents::class.java)
.EVENT1()
.observe(this, Observer {
Log.i(LOG, it.toString())
})
5. 总结
到这里,Jetpack 中的 LiveData 组件就讲完了,由于美团的 ModularEventBus 并没有开源,下篇文章我们直接来做一次学习落地。关注我,带你了解更多。
参考资料
LiveData 概览 —— 官方文档
重学安卓:吃透 LiveData 本质,享用可靠消息鉴权机制 —— KunMinX 著
重学安卓:LiveData 数据倒灌 “背景缘由全貌” 独家解析 —— KunMinX 著
关于 LiveData 粘性事件所带来问题的解决方案—— 慕尼黑 著
带你了解 LiveData 重放污染的前世今生—— 徐宜生 著
Android 消息总线的演进之路:用 LiveDataBus 替代 RxBus、EventBus —— 美团技术团队
Android 组件化方案及组件消息总线 modular-event 实战 —— 美团技术团队
基于 LiveData 实现事件总线思路和方案 —— toothpickTina 著
你的点赞对我意义重大!微信搜索公众号 [彭旭锐],希望大家可以一起讨论技术,找到志同道合的朋友,我们下次见!
你可能感兴趣的:(Android,android,android,jetpack,架构)
Qiankun 微前端框架全面解析:架构、原理与最佳实践
赵大仁
前端 技术 微前端 javascript 深度学习
Qiankun微前端框架全面解析:架构、原理与最佳实践随着前端应用的不断发展,单体前端项目在复杂度、维护成本和团队协作上面临越来越多的挑战。微前端(MicroFrontends)作为解决方案之一,可以帮助企业拆分前端应用,提高可扩展性和独立部署能力。Qiankun作为基于single-spa的微前端框架,提供了一种简单高效的方式来实现微前端架构。本文将深入解析Qiankun的工作原理、核心功能、实
Android Binder机制详解及实现
ByteWhiz
android binder Android
AndroidBinder机制是Android系统中用于进程间通信(IPC)的核心机制。它提供了一种高效而安全的方式,使不同的应用程序或组件能够在不同的进程中进行通信。本文将详细介绍AndroidBinder机制的原理和实现,并提供相应的源代码示例。一、AndroidBinder机制的原理Binder基本概念Binder是一种进程间通信(IPC)机制,由三个主要组件组成:服务端(Service)、
Android Binder 用法详解
令狐掌门
Android开发笔记 android binder Android Binder
Binder是Android系统中的一种进程间通信(IPC)机制,它允许不同进程之间进行高效通信。Binder在Android系统中被广泛使用,例如在Activity与Service的交互中。Binder的基本组成实现Binder通信通常包含以下几个关键部分:AIDL接口定义:通过AndroidInterfaceDefinitionLanguage定义接口服务端实现:实现AIDL接口并在Servi
AWS Amazon Aurora MySQL 性能监控与安全治理实战指南
ivwdcwso
运维 aws mysql 安全 rds
引言AmazonAuroraMySQL凭借其云原生架构和高性能特性,已成为企业核心数据库的首选。然而,缺乏体系化的监控与安全治理可能导致资源浪费、性能瓶颈甚至数据泄露。本文结合AWS官方最佳实践与真实运维场景,从监控工具、性能调优、安全加固到自动化治理,构建全链路解决方案。©ivwdcwso(ID:u012172506)一、监控体系搭建:工具选型与指标解读1.原生工具组合:精准捕捉核心指标Clou
ReactNative组件详解
Code4Android
编程语言 android ios react-native
ReactNative学习记录传送门ReactNative核心思想就是组件化,它基于前端框架React,在我们使用其开发Android和iOS的时候,共用一套组件即一套代码,增加了代码复用性。今天的这篇文章不不分析过多的知识点,主要介绍如下内容:如何进行自定义组件如何使用自定义组件组件的生命周期自定义组件ReactNative中我们实现的UI都是有组件组成的,但是有时候为了实现我们想要的效果,并且
React入门简介
地信小学生
WebGIS一起学 react.js 前端 前端框架 javascript
Vue与React是前端编程中常用的两个JS框架,两者的一些简单如下所示。Vue建立在一个基于组件的架构上,以组件为中心,可以更轻松地创建动态用户界面。它还有一个强大的双向数据绑定系统,可以让我们轻松地保持数据和用户界面同步;Vue轻量、灵活且强大,最重要的是对于初学者来说相对简单。Vue中文官网包含了详细的说明与教程:https://cn.vuejs.org/React为开发人员提供了一种声明式
golang mysql分表_go分库分表 主从分离例子
连根塞
golang mysql分表
网上有很多介绍分库分表的文章,方法很多:```分区表切分垂直切分水平切分区间切分取模切分```这里不细说分库分表简单,但后期会带来一系列的难题:```事务Join分页```**数据库:**```master和slave是一个主从架构imagespider_db:[ImageSpider](https://github.com/bccber/imagespider)项目采集回来的数据,不需要部署主从
Window 10使用WSL2成功编译Android R
shusuanly
车机系统 android framework
一、安装WSL参考Microsoft官方文档安装WSL2,并从MicrosoftStore选择Linux发行版进行安装,此处选择Ubuntu18.04.5LTS安装。首次进入Linux需设置Linux用户名和密码二、迁移WSL
使用react-native-vector-icons
孟宪磊mxl
react native react.js javascript
ReactNativeVectorIcons是一个在GitHub上非常受欢迎的ReactNative图标库。它提供了许多常见的图标集,如FontAwesome和Ionicons等。使用这个库可以简化在ReactNative项目中使用图标的过程。一、安装npminstallreact-native-vector-icons二、配置环境(android)1.打开android/app/build.gr
React Native 新架构,前端开发框架
2401_83974173
2024年程序员学习 react native 架构 react.js
JSthread会先对其序列化,形成下面一条消息UIManager.createView([343,“RCTView”,31,{“backgroundColor”:-16181,“width”:200,“height”:200}])通过Bridge发到ShadowThread。ShadowTread接收到这条信息后,先反序列化,形成Shadowtree,然后传给Yoga,形成原生布局信息。接着又通
LLM OS 系统架构详细设计
AI天才研究院
AI大模型企业级应用开发实战 系统架构
LLMOS系统架构详细设计1.背景介绍近年来,大型语言模型(LargeLanguageModel,LLM)取得了飞速发展,在自然语言处理、对话系统、文本生成等领域展现出卓越的性能。然而,现有的LLM系统架构仍然存在诸多局限性,例如可扩展性不足、资源利用率低下、缺乏灵活的应用开发支持等。为了充分发挥LLM的潜力,迫切需要一个高效、灵活、易用的LLM操作系统(OperatingSystem,OS)。本
tensorflow Serving架构详解和代码示例
lloyd_chou
算法 ml 机器翻译 智慧城市 边缘计算 人工智能
本文介绍tensorflowServing的原理和代码实现,并提供简要的代码阅读指导.如何serve一个模型具体的步骤可以参考官方文档.主要包括两个部分:1.导出模型1.启动服务需要说明的是导出模型部分.如果要把我们训练的模型拿来提供服务,除了模型本身外,还需要一些额外的信息,比如模型的名称,输入、输出对应的tensor信息,方法名,这些东西可以让TFS进行请求数据的格式检查以及目标模型查找.这就
凤凰架构-向微服务迈进
七路灯
读书 架构 架构
周志明《凤凰架构:构建可靠的大型分布式系统》https://icyfenix.cn/向微服务迈进,目的->前提->边界->治理目录目的:微服务的驱动力前提:微服务需要的条件边界:微服务的粒度治理:理解系统复杂性静态的治理发展的治理软件研发中任何一项技术、方法、架构都不可能是银弹。假如只能用一个词来形容微服务解决问题的核心思想,笔者给的答案就是“分治”,这即是微服务的基本特征,也是微服务应对复杂性的
集群、分布式和微服务
happy_king_zi
微服务 分布式 分布式 微服务 架构
一、架构演变从单机结构到集群结构,你的代码基本无需要作任何修改,你要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当你要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统我们建议,系统设计之初就采用微服务架构,这样后期运维的成本更低。但如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。所以,对于老系统而言,究竟是继续保持集群模
15.凤凰架构:构建可靠的大型分布式系统 --- 服务网格
enlyhua
架构 架构
第15章服务网格容器编排系统管理的最细粒度只能到达容器层次,在此粒度下的技术细节,仍然只能依赖程序员自己来管理,编排系统很难提供有效的支持。服务网格:是一种用于管理服务间通信的基础设施,职责是支持现代云原生应用网络请求在复杂拓扑环境中的可靠传递。在实践中,服务网格通常会以轻量化网络代理的形式来体现,这些代理与应用程序代码部署在一起,对应用程序来说,它完全不会感知到代理的存在。服务网格只是一种处理程
凤凰架构——世间安得两全法,不负如来不负卿
ezreal_pan
架构 凤凰架构 技术书
因为不确定,所以面临着选择,因为有了选择,所以有了妥协,因为有了妥协,也就有了痛苦。是以,仓央嘉措追问,也是芸芸众生的追问:“世间安得两全法,不负如来不负卿。”今年早些时候写了一篇《世间安得两全法,不负如来不负卿》科普文,在这篇科普文里,我写到了由于电池会经历电能到化学能转换的过程,目前无法实现极速充电,电容虽然可以极速充电,但是能量密度又太低的问题,所以不得不妥协,出了一个中间态的产品——混动车
凤凰架构-演进中的架构
metazz
分布式 微服务
原始分布式时代1、惠普公司=》网络运算架构(NCA)=》远程服务调用的雏形卡内基梅隆大学=》AFS文件系统=》分布式文件系统的最早实现麻省理工学院=》Kerberos协议=》服务认证和访问控制的基础性协议,分布式服务安全性的重要支撑,目前仍被用于实现包括Windows和MacOS在内众多操作系统的登录、认证功能。2、OSF(开放软件基金会)发起制订=》DCE(分布式运算环境)分布式技术体系3、DC
凤凰架构:构建清晰的分布式系统架构
CxzLoop
架构 java 微服务 分布式
在软件开发领域,构建可扩展和高性能的分布式系统是一个关键的挑战。分布式系统需要处理大量的并发请求,同时保持高可用性和容错性。为了解决这些问题,凤凰架构(PhoenixArchitecture)提供了一种清晰的分布式系统架构设计方法。本文将介绍凤凰架构的核心原则和示例源代码,帮助读者理解如何构建可靠的分布式系统。微服务架构凤凰架构采用微服务架构作为基础。微服务架构将一个大型应用程序拆分成一组小型、自
深度学习数据集封装-----目标检测篇
科研小天才
深度学习 目标检测 人工智能
前言在上篇文章中,我们深入探讨了图像分类数据集的制作流程。图像分类作为计算机视觉领域的一个基础任务,通常被认为是最为简单直接的子任务之一。然而,当我们转向目标检测任务时,复杂度便显著提升,尤其是在标注框的处理环节。不同的模型架构往往对标注框的处理方式有着各自独特的要求。以YOLO系列为例,它自有一套成熟且高效的方法来应对这一挑战。鉴于篇幅有限,本文暂不深入展开YOLO的相关内容,感兴趣的读者可以查
凤凰架构:构建可靠的大型分布式系统
邱燕义
凤凰架构:构建可靠的大型分布式系统【下载地址】凤凰架构构建可靠的大型分布式系统凤凰架构:构建可靠的大型分布式系统项目地址:https://gitcode.com/Open-source-documentation-tutorial/7e46b项目介绍在当今数字化时代,大型分布式系统已成为企业级应用的核心。然而,构建一个既可靠又高效的分布式系统并非易事。为了帮助开发者、架构师和系统工程师应对这一挑战
ARM架构的CentOS 8服务器 使用pip时常见的报错及其解决方法(踩过的坑的总结)
来自于狂人
服务器 arm开发 centos
1.缺失基础开发工具组典型报错:error:command'gcc'failedwithexitstatus1gcc:error:Python.h:Nosuchfileordirectory原因:ARM平台上的多数Python包需本地编译,但系统未安装必要的开发工具和头文件。解决方案:CentOS8专用命令:#安装开发工具链sudodnfgroupinstall"DevelopmentTools"
深入理解 Transformer:用途、原理和示例
范吉民(DY Young)
简单AI学习 transformer 深度学习 人工智能
深入理解Transformer:用途、原理和示例一、Transformer是什么Transformer是一种基于注意力机制(AttentionMechanism)的深度学习架构,在2017年的论文“AttentionIsAllYouNeed”中惊艳登场。它打破了传统循环神经网络(RNN)按顺序处理序列、难以并行计算以及卷积神经网络(CNN)在捕捉长距离依赖关系上的局限,另辟蹊径地采用多头注意力机制
遨游防爆智能终端“问诊”工业制造,开出数据采集“良方”
AORO_BEIDOU
制造
在数据驱动的时代,唯有采集足够规模的工业数据,方能支撑起基于工业大数据的深度分析与智能决策,从而驱动传统产业的蜕变与升级。但是,数据采集之路并非坦途,面临着设备协议多样、接口不一等挑战。技术难题求解,往往要在市场找良方。AOROM5-5G防爆智能终端遨游通讯防爆智能终端其独特之处在于全景前瞻架构的设计理念。在产品定义之初,便充分考虑了未来可能的数据采集需求,预留了丰富的接口,可根据企业的实际需求,
DeepSeek赋能生活全场景:20个职场人/学生/宝妈必备AI实践指南
小小鸭程序员
java python spring cloud 云原生 kafka
2024春节AI圈顶流:国产大模型DeepSeek持续霸屏!除技术解析外,更值得关注的是其在实际生活场景中的落地应用。本文整理20个高价值使用姿势,助你快速解锁AI助手生产力!一、学习成长加速器1.智能简历优化师使用场景:输入基础工作经历,自动生成ATS友好型简历,附带岗位关键词匹配与成就量化建议高阶技巧:上传JD文件,获取定制化简历修改报告2.论文架构大师核心功能:根据研究主题自动生成三级大纲框
kubeadm_k8s_v1.31高可用部署教程
techzhi
kubernetes 容器 云原生
kubeadm_k8s_v1.31高可用部署教程实验环境部署拓扑图**部署署架构****LoadBalance****Controlplanenode****Workernode****资源分配(8台虚拟机)**集群列表前置准备关闭swap开启ipv4转发更多设置1、VerifytheMACaddressandproduct_uuidareuniqueforeverynode2、Checknetw
linux-运维进阶-28 LNMP动态网站架构
IT@feng
Linux-运维进阶 LNMP linux
linux-运维进阶-28LNMP动态网站架构LNMP动态网站架构LNMP动态网站部署架构是一套由Linux+Nginx+MySQL+PHP组成的动态网站系统解决方案。LNMP中的字母L是Linux系统的意思,不仅可以是RHEL、CentOS、Fedora,还可以是Debian、Ubuntu等系统开发环境的部署在使用源码包安装服务程序之前,首先要让安装主机具备编译程序源码的环境,他需要具备C语言、
mall-swarm微服务商城系统实战:Spring Cloud & Alibaba集成应用
Amarantine Lee
本文还有配套的精品资源,点击获取简介:mall-swarm是一套基于微服务架构的电子商务平台,集成SpringCloud框架和阿里巴巴的Nacos、Sentinel等技术,提供了服务发现、配置管理、流量控制等核心功能。本系统通过使用Eureka或Nacos进行服务注册与发现,Sentinel实现服务保护,以及SpringCloudConfig或Nacos进行动态配置管理,展示了如何构建一个高可用的
Spring Cloud Alibaba入门教程合集-01【微服务和Spring Cloud Alibaba介绍】
图灵学院架构师
Java 前段 spring 微服务 java microservices spring 性能优化
1、微服务介绍1.1系统架构演变随着互联网的发展,网站应用的规模也在不断的扩大,进而导致系统架构也在不断的进行变化。从互联网早起到现在,系统架构大体经历了下面几个过程:单体应用架构—>垂直应用架构—>分布式架构—>SOA架构—>微服务架构,当然还有悄然兴起的ServiceMesh(服务网格化)。接下来我们就来了解一下每种系统架构是什么样子的,以及各有什么优缺点。1.1.1单体应用架构互联网早期,一
Elasticsearch冷热分离与索引生命周期管理
Cloud_Tech
elasticsearch 大数据 数据分析 数据库 阿里云
本文介绍在Elasticsearch集群上,通过生命周期管理ILM(IndexLifecycleManagement)功能,实现冷热数据分离的实践流程。通过本实践,您既可以实现在保证集群读写性能的基础上,自动维护集群上的冷热数据,又能通过优化集群架构,降低企业生产成本。背景信息当今大数据时代,数据时刻在更新变化。尤其是随着时间的积累,存储在Elasticsearch中的数据会越来越多,当数据达到一
java面试题-微服务相关组件
Builder 王
java面试题 java 微服务 开发语言
Nacos1.什么是Nacos?Nacos是一个开源项目,由阿里巴巴推出,旨在为构建云原生应用提供动态服务发现、配置管理和服务管理平台。Nacos支持微服务的发现、配置和管理,提供简单易用的特性集,能够快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos帮助用户更敏捷和容易地构建、交付和管理微服务平台,构建以“服务”为中心的现代应用架构,例如微服务范式、云原生范式。此外,Nacos提供
ViewController添加button按钮解析。(翻译)
张亚雄
c
<div class="it610-blog-content-contain" style="font-size: 14px"></div>// ViewController.m
// Reservation software
//
// Created by 张亚雄 on 15/6/2.
mongoDB 简单的增删改查
开窍的石头
mongodb
在上一篇文章中我们已经讲了mongodb怎么安装和数据库/表的创建。在这里我们讲mongoDB的数据库操作
在mongo中对于不存在的表当你用db.表名 他会自动统计
下边用到的user是表明,db代表的是数据库
添加(insert):
log4j配置
0624chenhong
log4j
1) 新建java项目
2) 导入jar包,项目右击,properties—java build path—libraries—Add External jar,加入log4j.jar包。
3) 新建一个类com.hand.Log4jTest
package com.hand;
import org.apache.log4j.Logger;
public class
多点触摸(图片缩放为例)
不懂事的小屁孩
多点触摸
多点触摸的事件跟单点是大同小异的,上个图片缩放的代码,供大家参考一下
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener
有关浏览器窗口宽度高度几个值的解析
换个号韩国红果果
JavaScript html
1 元素的 offsetWidth 包括border padding content 整体的宽度。
clientWidth 只包括内容区 padding 不包括border。
clientLeft = offsetWidth -clientWidth 即这个元素border的值
offsetLeft 若无已定位的包裹元素
数据库产品巡礼:IBM DB2概览
蓝儿唯美
db2
IBM DB2是一个支持了NoSQL功能的关系数据库管理系统,其包含了对XML,图像存储和Java脚本对象表示(JSON)的支持。DB2可被各种类型的企 业使用,它提供了一个数据平台,同时支持事务和分析操作,通过提供持续的数据流来保持事务工作流和分析操作的高效性。 DB2支持的操作系统
DB2可应用于以下三个主要的平台:
工作站,DB2可在Linus、Unix、Windo
java笔记5
a-john
java
控制执行流程:
1,true和false
利用条件表达式的真或假来决定执行路径。例:(a==b)。它利用条件操作符“==”来判断a值是否等于b值,返回true或false。java不允许我们将一个数字作为布尔值使用,虽然这在C和C++里是允许的。如果想在布尔测试中使用一个非布尔值,那么首先必须用一个条件表达式将其转化成布尔值,例如if(a!=0)。
2,if-els
Web开发常用手册汇总
aijuans
PHP
一门技术,如果没有好的参考手册指导,很难普及大众。这其实就是为什么很多技术,非常好,却得不到普遍运用的原因。
正如我们学习一门技术,过程大概是这个样子:
①我们日常工作中,遇到了问题,困难。寻找解决方案,即寻找新的技术;
②为什么要学习这门技术?这门技术是不是很好的解决了我们遇到的难题,困惑。这个问题,非常重要,我们不是为了学习技术而学习技术,而是为了更好的处理我们遇到的问题,才需要学习新的
今天帮助人解决的一个sql问题
asialee
sql
今天有个人问了一个问题,如下:
type AD value
A
意图对象传递数据
百合不是茶
android 意图Intent Bundle对象数据的传递
学习意图将数据传递给目标活动; 初学者需要好好研究的
1,将下面的代码添加到main.xml中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http:/
oracle查询锁表解锁语句
bijian1013
oracle object session kill
一.查询锁定的表
如下语句,都可以查询锁定的表
语句一:
select a.sid,
a.serial#,
p.spid,
c.object_name,
b.session_id,
b.oracle_username,
b.os_user_name
from v$process p, v$s
mac osx 10.10 下安装 mysql 5.6 二进制文件[tar.gz]
征客丶
mysql osx
场景:在 mac osx 10.10 下安装 mysql 5.6 的二进制文件。
环境:mac osx 10.10、mysql 5.6 的二进制文件
步骤:[所有目录请从根“/”目录开始取,以免层级弄错导致找不到目录]
1、下载 mysql 5.6 的二进制文件,下载目录下面称之为 mysql5.6SourceDir;
下载地址:http://dev.mysql.com/downl
分布式系统与框架
bit1129
分布式
RPC框架 Dubbo
什么是Dubbo
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。其核心部分包含: 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。 集群容错: 提供基于接
那些令人蛋痛的专业术语
白糖_
spring Web SSO IOC
spring
【控制反转(IOC)/依赖注入(DI)】:
由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。
简单的说:对象的创建又容器(比如spring容器)来执行,程序里不直接new对象。
Web
【单点登录(SSO)】:SSO的定义是在多个应用系统中,用户
《给大忙人看的java8》摘抄
braveCS
java8
函数式接口:只包含一个抽象方法的接口
lambda表达式:是一段可以传递的代码
你最好将一个lambda表达式想象成一个函数,而不是一个对象,并记住它可以被转换为一个函数式接口。
事实上,函数式接口的转换是你在Java中使用lambda表达式能做的唯一一件事。
方法引用:又是要传递给其他代码的操作已经有实现的方法了,这时可以使
编程之美-计算字符串的相似度
bylijinnan
java 算法 编程之美
public class StringDistance {
/**
* 编程之美 计算字符串的相似度
* 我们定义一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:
* 1.修改一个字符(如把“a”替换为“b”);
* 2.增加一个字符(如把“abdd”变为“aebdd”);
* 3.删除一个字符(如把“travelling”变为“trav
上传、下载压缩图片
chengxuyuancsdn
下载
/**
*
* @param uploadImage --本地路径(tomacat路径)
* @param serverDir --服务器路径
* @param imageType --文件或图片类型
* 此方法可以上传文件或图片.txt,.jpg,.gif等
*/
public void upload(String uploadImage,Str
bellman-ford(贝尔曼-福特)算法
comsci
算法 F#
Bellman-Ford算法(根据发明者 Richard Bellman 和 Lester Ford 命名)是求解单源最短路径问题的一种算法。单源点的最短路径问题是指:给定一个加权有向图G和源点s,对于图G中的任意一点v,求从s到v的最短路径。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore zu 也为这个算法的发展做出了贡献。
与迪科
oracle ASM中ASM_POWER_LIMIT参数
daizj
ASM oracle ASM_POWER_LIMIT 磁盘平衡
ASM_POWER_LIMIT
该初始化参数用于指定ASM例程平衡磁盘所用的最大权值,其数值范围为0~11,默认值为1。该初始化参数是动态参数,可以使用ALTER SESSION或ALTER SYSTEM命令进行修改。示例如下:
SQL>ALTER SESSION SET Asm_power_limit=2;
高级排序:快速排序
dieslrae
快速排序
public void quickSort(int[] array){
this.quickSort(array, 0, array.length - 1);
}
public void quickSort(int[] array,int left,int right){
if(right - left <= 0
C语言学习六指针_何谓变量的地址 一个指针变量到底占几个字节
dcj3sjt126com
C语言
# include <stdio.h>
int main(void)
{
/*
1、一个变量的地址只用第一个字节表示
2、虽然他只使用了第一个字节表示,但是他本身指针变量类型就可以确定出他指向的指针变量占几个字节了
3、他都只存了第一个字节地址,为什么只需要存一个字节的地址,却占了4个字节,虽然只有一个字节,
但是这些字节比较多,所以编号就比较大,
phpize使用方法
dcj3sjt126com
PHP
phpize是用来扩展php扩展模块的,通过phpize可以建立php的外挂模块,下面介绍一个它的使用方法,需要的朋友可以参考下
安装(fastcgi模式)的时候,常常有这样一句命令:
代码如下:
/usr/local/webserver/php/bin/phpize
一、phpize是干嘛的?
phpize是什么?
phpize是用来扩展php扩展模块的,通过phpi
Java虚拟机学习 - 对象引用强度
shuizhaosi888
JAVA虚拟机
本文原文链接:http://blog.csdn.net/java2000_wl/article/details/8090276 转载请注明出处!
无论是通过计数算法判断对象的引用数量,还是通过根搜索算法判断对象引用链是否可达,判定对象是否存活都与“引用”相关。
引用主要分为 :强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Wea
.NET Framework 3.5 Service Pack 1(完整软件包)下载地址
happyqing
.net 下载 framework
Microsoft .NET Framework 3.5 Service Pack 1(完整软件包)
http://www.microsoft.com/zh-cn/download/details.aspx?id=25150
Microsoft .NET Framework 3.5 Service Pack 1 是一个累积更新,包含很多基于 .NET Framewo
JAVA定时器的使用
jingjing0907
java timer 线程 定时器
1、在应用开发中,经常需要一些周期性的操作,比如每5分钟执行某一操作等。
对于这样的操作最方便、高效的实现方式就是使用java.util.Timer工具类。
privatejava.util.Timer timer;
timer = newTimer(true);
timer.schedule(
newjava.util.TimerTask() { public void run()
Webbench
流浪鱼
webbench
首页下载地址 http://home.tiscali.cz/~cz210552/webbench.html
Webbench是知名的网站压力测试工具,它是由Lionbridge公司(http://www.lionbridge.com)开发。
Webbench能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。webbench的标准测试可以向我们展示服务器的两项内容:每秒钟相
第11章 动画效果(中)
onestopweb
动画
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/
windows下制作bat启动脚本.
sanyecao2314
java cmd 脚本 bat
java -classpath C:\dwjj\commons-dbcp.jar;C:\dwjj\commons-pool.jar;C:\dwjj\log4j-1.2.16.jar;C:\dwjj\poi-3.9-20121203.jar;C:\dwjj\sqljdbc4.jar;C:\dwjj\voucherimp.jar com.citsamex.core.startup.MainStart
Java进行RSA加解密的例子
tomcat_oracle
java
加密是保证数据安全的手段之一。加密是将纯文本数据转换为难以理解的密文;解密是将密文转换回纯文本。 数据的加解密属于密码学的范畴。通常,加密和解密都需要使用一些秘密信息,这些秘密信息叫做密钥,将纯文本转为密文或者转回的时候都要用到这些密钥。 对称加密指的是发送者和接收者共用同一个密钥的加解密方法。 非对称加密(又称公钥加密)指的是需要一个私有密钥一个公开密钥,两个不同的密钥的
Android_ViewStub
阿尔萨斯
ViewStub
public final class ViewStub extends View
java.lang.Object
android.view.View
android.view.ViewStub
类摘要: ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。当 ViewSt