使用LifeCycle解耦页面与组件

1,494 阅读2分钟

1. 案例分析

假设有这样一个常见的需求:在用户打开某个页面时,获取用户当前的地理位置。面对该需求,我们通常会这样写代码。

package com.kf.ui;

import android.os.Bundle;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

/**
 * TODO Description 文件描述信息
 *
 * @author nomi
 * @package com.kf.ui
 * @date 2021/7/22-10:56 上午
 */
public class LocationActivity extends AppCompatActivity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        //初始化位置管理器
        initLocationManager();
    }

    /**
     * 初始化定位操作
     */
    private void initLocationManager() {
        Log.d("Location", "initLocationManager");
    }


    @Override
    protected void onResume() {
        super.onResume();
        //开始获取用户的地理位置
        startGetLocation();
    }

    @Override
    protected void onPause() {
        super.onPause();
        //停止获取用户的地理位置
        stopGetLocation();
    }

    /**
     * 当Activity 执行 onResume() 方法时,该方法会被自动调用
     */
    private void startGetLocation() {
        Log.d("Location", "startGetLocation");
    }

    /**
     * 当Activity 执行 onPause() 方法时,该方法会被自动调用
     */
    private void stopGetLocation() {
        Log.d("Location", "stopGetLocation");
    }
}

从以上代码可以看出,获取地理位置这个需求的实现,与页面的生命周期息息相关。如果我们希望将获取地理位置这一功能独立成一个组件,那么生命周期是必须要考虑在内的。我们不得不在页面生命周期的各个回调方法中,对组件进行通知,因为组件不能主动感知生命周期的变化。 ​

2. LifeCycIe的原理

LifeCycle是如何解决这个问题的呢?Jetpack为我们提供了两个类:LifecycleOwner(被观察者)和LifecycleObserver(观察者)。即通过观察者模式,实现对页面生命周期的监听。通过查看SupportActivity的源码,可以看到,在新版本的SDK包中,Activity已经默认实现了LifecycleOwner接口。LifecycleOwner接口中只有一个getLifecycle(LifecycleObserverobserver)方法,LifecycleOwner正是通过该方法实现观察者模式的。源码示例如下。 image.png 从以上源码可知,SupportActivity已经替我们实现了被观察者应该实现的那一部分代码。因此,我们不需要再去实现这部分代码。当我们希望监听Activity的生命周期时,只需要实现观察者那一部分的代码,即让自定义组件实现LifecycleObserver接口即可。该接口没有接口方法,无须任何具体实现。 ​

SupportActivity和ComponentActivity的代码区别不大:


public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {
static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
private final SavedStateRegistryController mSavedStateRegistryController =
        SavedStateRegistryController.create(this);

// Lazily recreated from NonConfigurationInstances by getViewModelStore()
private ViewModelStore mViewModelStore;
private ViewModelProvider.Factory mDefaultFactory;

private final OnBackPressedDispatcher mOnBackPressedDispatcher =
        new OnBackPressedDispatcher(new Runnable() {
            @Override
            public void run() {
                ComponentActivity.super.onBackPressed();
            }
        });

@LayoutRes
private int mContentLayoutId;

/**
 * Default constructor for ComponentActivity. All Activities must have a default constructor
 * for API 27 and lower devices or when using the default
 * {@link android.app.AppComponentFactory}.
 */
public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    //noinspection ConstantConditions
    if (lifecycle == null) {
        throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                + "constructor. Please make sure you are lazily constructing your Lifecycle "
                + "in the first call to getLifecycle() rather than relying on field "
                + "initialization.");
    }
    if (Build.VERSION.SDK_INT >= 19) {
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_STOP) {
                    Window window = getWindow();
                    final View decor = window != null ? window.peekDecorView() : null;
                    if (decor != null) {
                        decor.cancelPendingInputEvents();
                    }
                }
            }
        });
    }
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });

    if (19 <= SDK_INT && SDK_INT <= 23) {
        getLifecycle().addObserver(new ImmLeaksCleaner(this));
    }
}

3. 解决方案

现在,让我们利用LifeCycle改写该需求。我们的目的是将该功能从Activity中独立出去,在减少耦合度的同时,又不影响对生命周期的监听。1.编写一个名为LocationLifeCycleListener 的类。该类就是我们的自定义组件,我们需要让该组件实现LifecycleObserver接口。与获取地理位置相关的代码在该类中完成。对于组件中那些需要在页面生命周期发生变化时得到通知的方法,我们需要在这些方法上使用@OnLifecycleEvent(Lifecycle.Event.ON_XXX)标签进行标识。这样,当页面生命周期发生变化时,这些被标识过的方法便会被自动调用。如下所示。

package com.kf.ui.util;

import android.app.Activity;
import android.util.Log;

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

/**
 * TODO Description 文件描述信息
 *
 * @author nomi
 * @package com.kf.ui.util
 * @date 2021/7/22-10:46 上午
 */
public class LocationLifeCycleListener implements LifecycleObserver {

    public LocationLifeCycleListener(Activity context, OnLocationChangedListener onLocationChangedListener) {
        //初始化位置管理器
        initLocationManager();
    }

    /**
     * 初始化定位操作
     */
    private void initLocationManager() {
        Log.d("Location", "initLocationManager");
    }

    /**
     * 当Activity 执行 onResume() 方法时,该方法会被自动调用
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    private void startGetLocation() {
        Log.d("Location", "startGetLocation");
    }

    /**
     * 当Activity 执行 onPause() 方法时,该方法会被自动调用
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    private void stopGetLocation() {
        Log.d("Location", "stopGetLocation");
    }


    public interface OnLocationChangedListener {
        /**
         * 获取定位信息
         *
         * @param latitude
         * @param longitude
         */
        void onChanged(double latitude, double longitude);
    }
}

在MainActivity中,只需要引用MyLocationListener即可,不用再关心Activity生命周期变化对该组件所带来的影响。生命周期的管理完全交给MyLocationListener内部自行处理。在Activity中要做的只是通过getLifecycle().addObserver()方法,将观察者与被观察者绑定起来,代码如下所示。 ​

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

/**
 * TODO Description 文件描述信息
 *
 * @author nomi
 * @package com.kf.ui
 * @date 2021/7/22-10:56 上午
 */
public class LocationLifecycleActivity extends AppCompatActivity {

    private LocationLifeCycleListener locationLifeCycleListener;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        locationLifeCycleListener = new LocationLifeCycleListener(this, new LocationLifeCycleListener.OnLocationChangedListener() {
            @Override
            public void onChanged(double latitude, double longitude) {
                //展示收到的位置信息

            }
        });

        //将观察者与被观察者绑定
        getLifecycle().addObserver(locationLifeCycleListener);
    }
}

LifeCycle完美解决了组件对页面生命周期的依赖问题,使组件能够自己管理其生命周期,而无须在页面中对其进行管理。这无疑大大降低了代码的耦合度,提高了组件的复用程度,也杜绝了由于对页面生命周期管理的疏忽而引发的内存泄漏问题,这在项目工程量大的情况下是非常有帮助的。 ​

除Activity之外,在新版本的SDK中,Fragment同样也默认实现了LifecycleOwner接口。因此,以上案例同样适用于Fragment。Fragment的源码如下所示。 image.png

摘自 叶坤 著《Android Jetpack应用指南item.jd.com/12684819.ht…