Guide to App Architecture

app开发者遇到的普遍问题

桌面应用大部分情况下都只有单一的入口,作为一整个进程运行。然而Android应用的组成更复杂,包含了activities、fragments、services、content providers、broadcast receivers等组件,因此要求app在用户来回切换界面和任务的时候考虑周全。

关键点是app的组件可能不按顺序地单独启动 ,可能在任何时候被系统或者用户终止掉。因为app组件拥有短暂的、开发者不可控的生命周期,因此不能把任何数据或者状态保存在app组件中。

基本结构原则

SoC原则(Separation of Concerns):关注点分离原则。常见的一种错误是把所有的代码都写到activity中或者fragment中。没有处理UI或者系统交互的代码不应该出现在这些类当中。保持activity和fragment的代码尽可能少可以减少生命周期的相关问题。记住:你并不拥有activity或者fragment这些类,他们只是用来维系操作系统和你的app之间的粘合剂。Android系统可能在用户操作或者系统低内存之下把系统组件终止掉。所以应该尽可能减少对于系统组件的依赖。

第二条重要原则是使用model来控制UI,特别是持久化的model。持久化可以保证数据不会丢失,应用正常响应。Model就是用来操作app的数据的,独立于app的Views或者组件,也就独立于这些组件的生命周期。保持UI代码的简单、不涉及逻辑,会更好掌控。基于model类去管理数据,可以让app保持可测性和持久性。

导入Architecture库

在project的build.gradle的allprojects闭包中把Google的Maven仓库添加进去:

allprojects {
    repositories {
        jcenter()
        maven { url 'https://maven.google.com' }
    }
}

如果下载依赖库不成功,可以考虑把https://maven.google.com更换为
https://dl.google.com/dl/android/maven2/,实际访问第一个网址会跳到第二个。

然后在module的build.gradle中根据需要声明依赖

// Lifecycle、LiveData和ViewModel
compile "android.arch.lifecycle:runtime:1.0.0-alpha8"
compile "android.arch.lifecycle:extensions:1.0.0-alpha8"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha8"

// Room
compile "android.arch.persistence.room:runtime:1.0.0-alpha8"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha8"
// Room的RxJava支持
compile "android.arch.persistence.room:rxjava2:1.0.0-alpha8"

推荐的app结构

使用Architecture Components来搭建app。注意不可能有一种搭建方法对每种场景都最佳,但是使用Architecture Components是不错的选择。下面以用户简介场景为例。

搭建用户界面

要驱动UI,数据模型需要包括两个数据元素
1. User ID:用户id,最好使用fragment的arguments来传递参数,即使进程被终止,id仍然会被保存下来。
2. User object:用户对象。
基于ViewModel类创建UserProfileViewModel类来保存信息。
ViewModel为一个特定的UI组件提供数据,比如一个activity或者fragment,并处理与业务数据部分的交流,比如调用其他组件加载数据,或者转发用户的修改。ViewModel完全独立于View。

ViewModel类的声明如下:

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

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

在Fragment中获取ViewModel:

public class UserProfileFragment extends LifecycleFragment {
    private static final String UID_KEY = "uid";
    private UserProfileViewModel viewModel;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        String userId = getArguments().getString(UID_KEY);
        viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
        viewModel.init(userId);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.user_profile, container, false);
    }
}

上述Fragment继承于实现了LifeCycleOwner接口的LifeCycleFragment,在Architecture库正式发布之后,support包的Fragment就会实现LifeCycleOwner了。

接下来就轮到LiveData出场了,LiveData是一个可观测的数据持有者,可以让app组件观测到LiveData所包含对象的变化,又不用创建强耦合关系。LiveData还可以很好地配合应用组件的生命周期避免内存泄露。可以在其他响应流的基础上使用LiveData。

所以这时候就改成使用LiveData< User>来代替User

public class UserProfileViewModel extends ViewModel {
    ...
    private LiveData<User> user;
    public LiveData<User> getUser() {
        return user;
    }
}

Fragment中就可以根据LiveData的回调来更新UI了:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    viewModel.getUser().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                viewModel.setProduct(productEntity); // 调用ViewModel中ObservableField的方法
            }
        });
}

ViewModel获取数据

假设后台提供了REST API,使用Retrofit进行网络请求

public interface Webservice {
    @GET("/users/{user}")
    Call<User> getUser(@Path("user") String userId);
}

注意ViewModel不能直接调用这个Webservice,ViewModel的作用范围仅限于Activity或者Fragment的生命周期内,可能造成数据丢失,此时应该使用一个Repository模块来获取数据。

Repository模块是用来操作数据的,可以用来协调网络请求、数据库、缓存。
下面创建一个UserRepository,用来把ViewModel与数据获取方式抽离开:

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) {
                // 省略错误情况
                data.setValue(response.body());
            }
        });
        return data;
    }
}

UserRepository包含一个Webservice对象,因此也就需要知道Webservice的创建细节,而且可能还有其他Repository需要用到Webservice对象,此时有两种方式来处理对象依赖关系。
1. 依赖注入:依赖注入可以定义依赖关系,而且不用去创建所依赖对象,在运行的时候是由另外一个类负责创建。可以使用Google的Dagger2。
2. Service Locator:提供一种方式来获取所依赖对象,不需要去创建他们。

现在我们使用定义的repository给ViewModel传递数据:

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;
    }
}

但是我们定义的repository每次都从服务器获取数据,效率不是很高,可以加上缓存:

@Singleton  // informs Dagger that this class should be constructed once
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;
    }
}

但是缓存在应用退出后也会消失,最好再加上一个数据持久化。

数据持久化

Room是一个对象映射数据库。

使用@Entity定义一个表格

@Entity
class User {
  @PrimaryKey
  private int id;
  private String name;
  private String lastName;
  // getters and setters for fields
}

继承于RoomDatabase创建数据库抽象类,系统会自动帮我们实例化:

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

定义数据库操作对象DAO,load可以返回LiveData对象,当数据库内容改变时,如果有活动的观测者则会通知其更新:

@Dao
public interface UserDao {
    @Insert(onConflict = REPLACE)
    void save(User user);
    @Query("SELECT * FROM user WHERE id = :userId")
    LiveData<User> load(String userId);
}

然后数据库中就可以引用DAO了:

@Database(entities = {User.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

在Repository就可以加上数据库方式了:

@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);
        // 直接从数据库返回一个LiveData对象
        return userDao.load(userId);
    }

    private void refreshUser(final String userId) {
        executor.execute(() -> {
            // 在子线程中执行
            // 检查是否有可用数据
            boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
            if (!userExists) {
                // 没有就请求网络
                Response response = webservice.getUser(userId).execute();
                // TODO check for error etc.
                // 更新数据库,LiveData会自动更新
                userDao.save(response.body());
            }
        });
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值