Android Architecture Components(下篇)

达芬奇密码2018-07-23 10:09

3、生命周期管理(LifeCycleObserver和LifeCycleOwner)

生命周期组件可以根据其他组件(如Activity Fragment等)的生命周期状态变化进行相应操作。这些组件可以帮助你写出更好组织,更轻量级,更容易管理的代码。

想象一下,如果我们要实现一个类似微信摇一摇震动的功能,而这个功能在用户锁屏时,即页面不可见时,是不能触发的,那么我们需要对onResume,onPause进行监听,操作如下:

class MyShakeListener {
public MyShakeListener (Context context, Callback callback) {
    // ...
}

void start() {
    // connect to system location service
}

void stop() {
    // disconnect from system location service
}
}

class MyActivity extends AppCompatActivity {
private MyShakeListener myShakeListener;

@Override
public void onCreate(...) {
    myShakeListener= new MyShakeListener(this, (location) -> {
        // update UI
    });
}

@Override
public void onStart() {
    super.onStart();
    myShakeListener.start();
    // manage other components that need to respond
    // to the activity lifecycle
    }

@Override
public void onStop() {
    super.onStop();
    myShakeListener.stop();
    // manage other components that need to respond
    // to the activity lifecycle
    }
}

这样操作是没有问题的,但是一个应用中大多数情况下是绝对不止一个功能需要监听类似的生命周期状态。一般我们用到的EventBus,ButterKnife等以及我们自定义的监听都需要用到类似功能。此时我们发现,我们需要在Activity或者Fragment的生命周期中,管理这么多的组件的相关生命周期方法,这样也太不优雅了。

我们可以通过LifecycleOwner 和LifecycleObserver来更好地处理生命周期状态变化的问题。

生命周期Events和状态变化如下图:

class MyShakeListener implements LifecycleObserver {
private boolean enabled = false;
public MyShakeListener(Context context, Lifecycle lifecycle, Callback callback) {
   ...
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
    if (enabled) {
       // connect
    }
}

public void enable() {
    enabled = true;
    if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
        // connect if not connected
    }
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
    // disconnect if connected
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

通过LifeCycleObserver以及LifeCycleOwner, 一个类可以通过给其中的方法添加注解的方式来管理生命周期,然后通过myLifecycleOwner(比如Activity Fragment)的addObServerder() 方法实现Observer对LifeOwner的生命周期的监听。

在进行生命周期的管理时,我们希望在某些不合适的生命周期阶段不触发某些操作。例如我们不能在activity的state已经保存的情况下,再去提交事务操作,这样会引发崩溃。而LifeCycleOwner允许我们查询当前的生命周期状态。LifeCycleOwner.getCurrentState(),可以根据当前状态来判断是否允许进行某些操作。

现在的MyShakeListener就已经是一个能感知生命周期的组件了。如果其他的Fragment 或者Activity需要用到它,只需要实例化MyShakeListener即可,其他剩余的操作都只在MyShakeListener中进行管理。

当然,你也可以通过如下方式,使你的class变成一个LifecycleOwner。你可以使用LifecycleRegistry类来实现,但是同时你需要通过下面的方式来分发相应的生命周期状态。

public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;

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

    mLifecycleRegistry = new LifecycleRegistry(this);
    mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}

@Override
public void onStart() {
    super.onStart();
    mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}

@NonNull
@Override
public Lifecycle getLifecycle() {
    return mLifecycleRegistry;
}

4、Room:

Room是Android官方提供的新的数据持久化方案组件。 Room组件主要可以进行数据库操作,并且可以和LiveData配合来监听数据库变化,以更新UI。

Room中有几个重要的注解:

@Database: 注解继承自RoomDatabase的类,主要用于创建数据库和Daos(数据访问对象)。

@Entity :用来注释实体类,@Database类通过entities属性,引用被@Entity注解的类,并通过 这个类的所有属性作为表的列名来创建数据库的表。

@Dao: 注解接口或抽象方法,用来提供访问数据库数据的方法。在使用@Database注解的类中必须定义一个不带参数的方法,这个方法返回使用@Dao注解的类。

1) 用注解 @Entity 定义实体类

@Entity
public class User {

@PrimaryKey
@NonNull
public String id;

public String name;

public String lastName;

public int age;

}

2)用@Database创建RoomDatabase的子类作为数据库。

@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

private static AppDatabase INSTANCE;

public abstract UserDao userModel();

public abstract BookDao bookModel();

public abstract LoanDao loanModel();

public static void destroyInstance() {
    INSTANCE = null;
  }

}

3)@Dao 创建数据访问接口Dao

@Dao
public interface UserDao {
@Query("select * from user")
List<User> loadAllUsers();

@Query("select * from user where id = :id")
User loadUserById(int id);

@Query("select * from user where name = :firstName and lastName = :lastName")
List<User> findUserByNameAndLastName(String firstName, String lastName);

@Insert(onConflict = IGNORE)
void insertUser(User user);

@Delete
void deleteUser(User user);

@Query("delete from user where name like :badName OR lastName like :badName")
int deleteUsersByName(String badName);

@Insert(onConflict = IGNORE)
void insertOrReplaceUsers(User... users);

@Delete
void deleteUsers(User user1, User user2);

@Query("SELECT * FROM User WHERE :age == :age") // TODO: Fix this!
List<User> findUsersYoungerThan(int age);

@Query("SELECT * FROM User WHERE age < :age")
List<User> findUsersYoungerThanSolution(int age);

@Query("DELETE FROM User")
void deleteAll();
}

4)在RoomDatabase中引用dao:

@Database(entities = {User.class, Book.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

private static AppDatabase INSTANCE;

public abstract UserDao userModel();

public abstract BookDao bookModel();
}

5、 如何获取数据并通知UI Controller更新UI

现在我们已经了解了上述Android架构化组件的基本用法,下面我们需要考虑一个问题。我们要如何更好的管理数据源,然后通知给UI Controller去更新数据呢?是的,没错,ViewModel + LiveData +Retrofit +Room +...

1)数据模型UserProfileViewModel ,用来保存需要UI更新所需要的数据

public class UserProfileViewModel extends ViewModel {
private String userId;
private User user;

public void init(String userId) {
    this.userId = userId;
}
public User getUser() {
    return user;
  }
}

2)如何通知UI更新

@Override
    public void onCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    String userId = getArguments().getString(UID_KEY);
    UserProfileViewModel   viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
    viewModel.init(userId);
    viewModel.getUser().observe(this, user -> {
      // update UI
    });
  }

3)如何获取数据:

a、常见的网络数据获取方式(比如Retrofit) b、本地持久化数据(比如Room)

public interface Webservice {
/**
 * @GET declares an HTTP GET request
 * @Path("user") annotation on the userId parameter marks it as a
 * replacement for the {user} placeholder in the @GET path
 */
@GET("/users/{user}")
Call<User> getUser(@Path("user") String userId);
}

4)封装UserRepository :

public class UserRepository {
private Webservice webservice;
// ...
public LiveData<User> getUser(int userId) {
    // This is not an optimal implementation, we'll fix it below
    final MutableLiveData<User> data = new MutableLiveData<>();
    webservice.getUser(userId).enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            // error case is left out for brevity
            data.setValue(response.body());
        }
    });
    return data;
  }
}

Android官方推荐我们使用Repository模式来进行数据获取逻辑的封装。Repository可以给app其余模块提供一个干净的api接口,这样其他模块就会独立于数据获取的逻辑之外,能更专注于他们本身的业务范畴。Repository知道从哪里获取数据,也知道数据更新时该如何调用。Repository可以将其他模块,比如本地持久化数据(Room),网络数据(Web Service), 缓存数据(Cache)等串联起来。 此时我们发现,ViewModel只需要从UserRepository来更新数据,但是ViewModel并不知道,也不需要关心这些数据从哪里来以及怎么来。从而达到ViewMode是与数据获取逻辑独立开的目的。

5)将ViewModel和Repository 串联起来。

public class UserProfileViewModel extends ViewModel {
private LiveData<User> user;
private UserRepository userRepo;

@Inject // UserRepository parameter is provided by Dagger 2
public UserProfileViewModel(UserRepository userRepo) {
    this.userRepo = userRepo;
}

public void init(String userId) {
    if (this.user != null) {
        // ViewModel is created per Fragment so
        // we know the userId won't change
        return;
    }
    user = userRepo.getUser(userId);
}

public LiveData<User> getUser() {
    return this.user;
  }
}

6) UserRepository从缓存中读取数据,如果缓存中没有,则从网络读取。

public class UserRepository {
private Webservice webservice;
// simple in memory cache, details omitted for brevity
private UserCache userCache;
public LiveData<User> getUser(String userId) {
    LiveData<User> cached = userCache.get(userId);
    if (cached != null) {
        return cached;
    }

    final MutableLiveData<User> data = new MutableLiveData<>();
    userCache.put(userId, data);
    // this is still suboptimal but better than before.
    // a complete implementation must also handle the error cases.
    webservice.getUser(userId).enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            data.setValue(response.body());
        }
    });
    return data;
  }
}

7)UserRepository从数据库中获取数据(Room),如果本地数据中没有,则从网络获取。

@Singleton
public class UserRepository {
private final Webservice webservice;
private final UserDao userDao;
private final Executor executor;

@Inject
public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
    this.webservice = webservice;
    this.userDao = userDao;
    this.executor = executor;
}

public LiveData<User> getUser(String userId) {
    refreshUser(userId);
    // return a LiveData directly from the database.
    return userDao.load(userId);
}

private void refreshUser(final String userId) {
    executor.execute(() -> {
        // running in a background thread
        // check if user was fetched recently
        boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
        if (!userExists) {
            // refresh the data
            Response response = webservice.getUser(userId).execute();
            // TODO check for error etc.
            // Update the database.The LiveData will automatically refresh so
            // we don't need to do anything else here besides updating the database
            userDao.save(response.body());
        }
    });
  }
}

总结:


UI Controller通过ViewModel来更新数据,而ViewModel通过LiveData监听数据变化并通知给UI层更新。ViewModel通过Repository模式来封装数据获取逻辑。这些数据可以来自缓存(Cache),本地持久化数据(Room Sqlite),以及网络请求(Retroft OKHttp)等。通过上述方式,整个数据获取的业务模块,UI Controller及App的其他模块,就能更加的独立和解耦,整个app的业务框架就更清晰,更容易扩展和维护了。

1、Android Architecture Components官方文档

2、android-lifecycles

3、android-persistence

4、App 组件化/模块化之路——Android 架构化组件

5、Android Room使用

相关阅读:

Android Architecture Components(上篇)

本文来自网易实践者社区,经作者朱强龙授权发布。