目前接手的一個業務,應用不是用Activity/Fragment作為界面組件,而是用Window浮窗的形式顯示,并且浮窗有很多種類型,每一種類型對應一類業務。那么怎么使用Jatpack的相關特性來設計架構并提高開發效率呢?分下面幾個模塊做分析:
ViewModel
通常在使用ViewModel的時候,是跟Activity/Fragment對應起來的,分別實例化一個對象,這個時候,ViewModel是存在Activity的ViewModelStore里面的,而且會監聽此Activity的生命周期,適時銷毀。
但是在沒有Activity的情況下,并且數據需要全局監聽的時候,可以創建一個全局的ViewModel,設計成單例模式的類,它的生命周期是伴隨應用的整個生命周期,如下:
object MainViewModel : ViewModel() {/*** 語音喚醒的狀態*/var mWakeUp = MutableLiveData<Int>()
}
其實在這里,已經可以不用去實現ViewModel這個類了,定義一個普通的單例模式類就行了:
object MainViewModel {/*** 語音喚醒的狀態*/var mWakeUp = MutableLiveData<Int>()
}
LiveData
MainViewModel中的LiveData主要分為兩大類,一類是全局的數據監聽,一類是針對于特定浮窗中使用到的數據。
第一類全局數據,可以使用全局監聽,它的數據監聽在應用整個生命周期都會有效:
// 全局檢測,不帶LifeCycleOwnerMainViewModel.mWakeUp.observeForever {Log.d(TAG, "wakeUp change $it")refreshWindowShown()}
第二類數據就是對應顯示浮窗的數據,它應該是需要帶生命周期的,即在彈窗顯示的時候才更新數據,而在彈窗消失后,不用再更新數據,避免資源的浪費和內存的泄漏:
class ShowWindow(context: Context) : BaseWindow(context){MainViewModel.mWindowContent.observe(this) {Log.d(TAG, "receive mWindowContent$it")binding.contentTv.text = it}
}
這里就有一個問題,Activity是接入了LifeCycleOwner的,但是Window浮窗是沒有接入的,而LiveData在訂閱非全局的觀察者時,需要有LifeCycleOwner參與,時候那應該怎么辦呢?
LifeCycleOwner
LifeCycleOwner在LifeCycle的設計策略中扮演的是生命周期提供者的角色,一個UI組件想要接入到LifeCycle的一系列規則中,就需要實現LifeCycleOwner,比如我們熟知的ComponentActivity就是實現了LifecycleOwner,并且通過mLifecycleRegistry去做的生命周期的關聯:
public class ComponentActivity extends Activity implementsLifecycleOwner,KeyEventDispatcher.Component {private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);@SuppressLint("RestrictedApi")@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);ReportFragment.injectIfNeededIn(this);}
}
高版本的SDK,是通過ReportFragment監聽生命周期,然后再通過mLifecycleRegistry去設置。
同理,我們的Window浮窗想要去把自身的生命周期導入到LifeCycle中,也是需要實現LifecycleOwner,然后通過mLifecycleRegistry去關聯生命周期,如下:
abstract class BaseWindow(val context : Context) : LifecycleOwner {private val lifecycleRegistry = LifecycleRegistry(this)open fun show() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)}open fun hide() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)}
}
即在show的時候,去開啟生命周期,而在hide的時候去結束生命周期。這樣,內部關聯的LiveData在感知到Window浮窗的生命周期onStart后,就會開啟數據的更新;在感知到onDestroy的時候去移除觀察者,結束數據更新,如下:
// 處理STARTED事件
@Override
boolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}// 處理 DESTROYED事件
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver); // 自動解綁return;}// 其他狀態處理...
}
ViewBinding
ViewBinding在Window浮窗的使用沒有任何障礙,通過配置gradle打開ViewBinding功能:
buildFeatures {viewBinding = true}
然后就可以在對應的浮窗中使用ViewBinding:
class ShowWindow(context: Context) : BaseWindow(context){private val binding : ShowwindowBinding by lazy { ShowwindowBinding.inflate(LayoutInflater.from(context)) }
}
這里的context是傳入的ApplicationContext,同樣是沒問題的。
這里想說一下基類和子類怎么處理Viewbinding。比如這一類浮窗中,它們有一些共同的界面邏輯,只是其中一塊顯示區域的內容有差距。這時候,我們可以抽取一個BaseWindow,并在BaseWindow中實現公共的UI和邏輯,同時在界面布局中預留出子類的顯示區域。這樣在BaseWindow和子類Window都可以使用ViewBinding。
也可以理解為,只要有一個layout布局文件,就對應一個ViewBinding類。具體的實現如下:
Base類的布局文件預留一個子類的內容顯示區域,不同的子類會填充不同的內容:
<FrameLayoutandroid:id="@+id/content_layout"android:layout_width="870dp"android:layout_height="match_parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintRight_toRightOf="parent"/>
Base類的代碼實現:
abstract class BaseWindow(context: Context){
private val binding : BaseWindowBinding by lazy { BaseWindowBinding.inflate(LayoutInflater.from(context)) }override fun getView(): View {if (binding.contentLayout.childCount == 0) {binding.contentLayout.addView(getContentLayout())}return binding.root}//預留給子類實現abstract fun getContentLayout() : View
在子類的實現中,使用ViewBindiing加載自己的布局模塊就好,并把自己的根布局通過getContentLayout方法返回。