google 官方推出应用开发架构指南 -欧洲杯足彩官网

2顶
1踩

引用
简评:虽然说 android 的架构选择一直都很自由,mvp、mvc、mvvm 各有拥趸。但 google 最近还是推出了一份关于应用架构的实践指南,并给出了相当详尽的步骤和一些指导建议。希望大家都能看一看,学习一下,打造更加优秀易用的 app,也为 android 生态的改善做一点贡献。: )

最近,官方推出了一份关于应用架构的最佳实践指南。这里就给大家简要介绍一下:

首先,android 开发者肯定都知道 android 中有四大组件,这些组件都有各自的生命周期并且在一定程度上是不受你控制的。在任何时候,android 操作系统都可能根据用户的行为或资源紧张等原因回收掉这些组件。

这也就引出了第一条准则:「不要在应用程序组件中保存任何应用数据或状态,并且组件间也不应该相互依赖」。

最常见的错误就是在 activity 或 fragment 中写了与 ui 和交互无关的代码。尽可能减少对它们的依赖,这能避免大量生命周期导致的问题,以提供更好的用户体验。

第二条准则:「通过 model 驱动应用 ui,并尽可能的持久化」。
这样做主要有两个原因:
  • 如果系统回收了你的应用资源或其他什么意外情况,不会导致用户丢失数据。
  • model 就应该是负责处理应用程序数据的组件。独立于视图和应用程序组件,保持了视图代码的简单,也让你的应用逻辑更容易管理。并且,将应用数据置于 model 类中,也更有利于测试。
官方推荐的 app 架构

在这里,官方演示了通过使用最新推出的 来构建一个应用。

想象一下,您正在打算开发一个显示用户个人信息的界面,用户数据通过 rest api 从后端获取。

首先,我们需要创建三个文件:
  • user_profile.xml:定义界面。
  • userprofileviewmodel.java:数据类。
  • userprofilefragment.java:显示 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;
    }
}

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

注意其中的 viewmodel 和 lifecyclefragment 都是 android 新引入的,可以参考进行集成。

现在,我们完成了这三个模块,该如何将它们联系起来呢?也就是当 viewmodel 中的用户字段被设置时,我们需要一种方法来通知 ui。这就是 livedata 的用武之地了。
引用
是一个可被观察的数据持有者(用到了观察者模式)。其能够允许 activity, fragment 等应用程序组件对其进行观察,并且不会在它们之间创建强依赖。livedata 还能够自动响应各组件的声明周期事件,防止内存泄漏,从而使应用程序不会消耗更多的内存。
注意: livedata 和 rxjava 或 agera 的区别主要在于 livedata 自动帮助处理了生命周期事件,避免了内存泄漏。

所以,现在我们来修改一下 userprofileviewmodel:
public class userprofileviewmodel extends viewmodel {
    ...
    private livedata user;
    public livedata getuser() {
        return user;
    }
}

再在 userprofilefragment 中对其进行观察并更新我们的 ui:
@override
public void onactivitycreated(@nullable bundle savedinstancestate) {
    super.onactivitycreated(savedinstancestate);
    viewmodel.getuser().observe(this, user -> {
      // update ui
    });
}

获取数据
现在,我们联系了 viewmodel 和 fragment,但 viewmodel 又怎么来获取到数据呢?

在这个示例中,我们假定后端提供了 rest api,因此我们选用 来访问我们的后端。

首先,定义一个 webservice:
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 getuser(@path("user") string userid);
}

不要通过 viewmodel 直接来获取数据,这里我们将工作转交给一个新的 repository 模块。
引用
repository 模块负责数据处理,为应用的其他部分提供干净可靠的 api。你可以将其考虑为不同数据源(web,缓存或数据库)与应用之间的中间层。

public class userrepository {
    private webservice webservice;
    // ...
    public livedata getuser(int userid) {
        // this is not an optimal implementation, we'll fix it below
        final mutablelivedata data = new mutablelivedata<>();
        webservice.getuser(userid).enqueue(new callback() {
            @override
            public void onresponse(call call, response response) {
                // error case is left out for brevity
                data.setvalue(response.body());
            }
        });
        return data;
    }
}

管理组件间的依赖关系

根据上面的代码,我们可以看到 userrepository 中有一个 webservice 的实例,不要直接在 userrepository 中 new 一个 webservice。这很容易导致代码的重复与复杂化,比如 userrepository 很可能不是唯一用到 webservice 的类,如果每个用到的类都新建一个 webservice,这显示会导致资源的浪费。
这里,我们推荐使用 来管理这些依赖关系。

现在,让我们来把 viewmodel 和 repository 连接起来吧:
public class userprofileviewmodel extends viewmodel {
    private livedata 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 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 getuser(string userid) {
        livedata cached = usercache.get(userid);
        if (cached != null) {
            return cached;
        }
        final mutablelivedata 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() {
            @override
            public void onresponse(call call, response response) {
                data.setvalue(response.body());
            }
        });
        return data;
    }
}

持久化数据

现在当用户旋转屏幕或暂时离开应用再回来时,数据是直接可见的,因为是直接从缓存中获取的数据。但要是用户长时间关闭应用,并且 android 还彻底杀死了进程呢?

我们目前的实现中,会再次从网络中获取数据。这可不是一个好的用户体验。这时就需要数据持久化了。继续引入一个新组件 。
引用
room 能帮助我们方便的实现本地数据持久化,抽象出了很多常用的数据库操作,并且在编译时会验证每个查询,从而损坏的 sql 查询只会导致编译时错误,而不是运行时崩溃。还能和上面介绍的 livedata 完美合作,并帮开发者处理了很多线程问题。

现在,让我们来看看怎么使用 room 吧。: )

首先,在 user 类上面加上 @entity,将 user 声明为你数据库中的一张表。
@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 {
}

注意 mydatabase 是一个抽象类,room 会自动添加实现的。

现在我们需要一种方法来将用户数据插入到数据库:
@dao
public interface userdao {
    @insert(onconflict = replace)
    void save(user user);
    @query("select * from user where id = :userid")
    livedata load(string userid);
}

再在数据库类中加入 dao:
@database(entities = {user.class}, version = 1)
public abstract class mydatabase extends roomdatabase {
    public abstract userdao userdao();
}

注意上面的 load 方法返回的是 livedata,room 会知道什么时候数据库发生了变化并自动通知所有的观察者。这也就是 livedata 和 room 搭配的妙用。

现在继续修改 userrepository:
@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 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());
            }
        });
    }
}

可以看到,即使我们更改了 userrepository 中的数据源,我们也完全不需要修改 viewmodel 和 fragment,这就是抽象的好处。同时还非常适合测试,我们可以在测试 userprofileviewmodel 时提供测试用的 userrepository。
引用
下面部分的内容在原文中是作为附录,但我个人觉得也很重要,所以擅自挪上来,一起为大家介绍了。: )

在上面的例子中,有心的大家可能发现了我们没有处理网络错误和正在加载状态。但在实际开发中其实是很重要的。这里,我们就实现一个工具类来根据不同的网络状况选择不同的数据源。

首先,实现一个 resource 类:
//a generic class that describes a data with a status
public class resource {
    @nonnull public final status status;
    @nullable public final t data;
    @nullable public final string message;
    private resource(@nonnull status status, @nullable t data, @nullable string message) {
        this.status = status;
        this.data = data;
        this.message = message;
    }
    public static  resource success(@nonnull t data) {
        return new resource<>(success, data, null);
    }
    public static  resource error(string msg, @nullable t data) {
        return new resource<>(error, data, msg);
    }
    public static  resource loading(@nullable t data) {
        return new resource<>(loading, data, null);
    }
}

因为,从网络加载数据和从磁盘加载是很相似的,所以再新建一个 networkboundresource 类,方便多处复用。下面是 networkboundresource 的决策树:

api 设计:
// resulttype: type for the resource data
// requesttype: type for the api response
public abstract class networkboundresource {
    // called to save the result of the api response into the database
    @workerthread
    protected abstract void savecallresult(@nonnull requesttype item);
    // called with the data in the database to decide whether it should be
    // fetched from the network.
    @mainthread
    protected abstract boolean shouldfetch(@nullable resulttype data);
    // called to get the cached data from the database
    @nonnull @mainthread
    protected abstract livedata loadfromdb();
    // called to create the api call.
    @nonnull @mainthread
    protected abstract livedata> createcall();
    // called when the fetch fails. the child class may want to reset components
    // like rate limiter.
    @mainthread
    protected void onfetchfailed() {
    }
    // returns a livedata that represents the resource
    public final livedata> getaslivedata() {
        return result;
    }
}

注意上面使用了 apiresponse 作为网络请求, apiresponse 是对于 retrofit2.call 的简单包装,用于将其响应转换为 livedata。

下面是具体的实现:
public abstract class networkboundresource {
    private final mediatorlivedata> result = new mediatorlivedata<>();
    @mainthread
    networkboundresource() {
        result.setvalue(resource.loading(null));
        livedata dbsource = loadfromdb();
        result.addsource(dbsource, data -> {
            result.removesource(dbsource);
            if (shouldfetch(data)) {
                fetchfromnetwork(dbsource);
            } else {
                result.addsource(dbsource,
                        newdata -> result.setvalue(resource.success(newdata)));
            }
        });
    }
    private void fetchfromnetwork(final livedata dbsource) {
        livedata> apiresponse = createcall();
        // we re-attach dbsource as a new source,
        // it will dispatch its latest value quickly
        result.addsource(dbsource,
                newdata -> result.setvalue(resource.loading(newdata)));
        result.addsource(apiresponse, response -> {
            result.removesource(apiresponse);
            result.removesource(dbsource);
            //noinspection constantconditions
            if (response.issuccessful()) {
                saveresultandreinit(response);
            } else {
                onfetchfailed();
                result.addsource(dbsource,
                        newdata -> result.setvalue(
                                resource.error(response.errormessage, newdata)));
            }
        });
    }
    @mainthread
    private void saveresultandreinit(apiresponse response) {
        new asynctask() {
            @override
            protected void doinbackground(void... voids) {
                savecallresult(response.body);
                return null;
            }
            @override
            protected void onpostexecute(void avoid) {
                // we specially request a new live data,
                // otherwise we will get immediately last cached value,
                // which may not be updated with latest results received from network.
                result.addsource(loadfromdb(),
                        newdata -> result.setvalue(resource.success(newdata)));
            }
        }.execute();
    }
}

现在,我们就能使用 networkboundresource 来根据不同的情况获取数据了:
class userrepository {
    webservice webservice;
    userdao userdao;
    public livedata> loaduser(final string userid) {
        return new networkboundresource() {
            @override
            protected void savecallresult(@nonnull user item) {
                userdao.insert(item);
            }
            @override
            protected boolean shouldfetch(@nullable user data) {
                return ratelimiter.canfetch(userid) && (data == null || !isfresh(data));
            }
            @nonnull @override
            protected livedata loadfromdb() {
                return userdao.load(userid);
            }
            @nonnull @override
            protected livedata> createcall() {
                return webservice.getuser(userid);
            }
        }.getaslivedata();
    }
}

到这里,我们的代码就全部完成了。最后的架构看起来就像这样:

最后的最后,给出一些指导原则

下面的原则虽然不是强制性的,但根据我们的经验遵循它们能使您的代码更健壮、可测试和可维护的。
  • 所有您在 manifest 中定义的组件 - activity, service, broadcast receiver… 都不是数据源。因为每个组件的生命周期都相当短,并取决于当前用户与设备的交互和系统的运行状况。简单来说,这些组件都不应当作为应用的数据源。
  • 在您应用的各个模块之间建立明确的责任边界。比如,不要将与数据缓存无关的代码放在同一个类中。
  • 每个模块尽可能少的暴露内部实现。从过去的经验来看,千万不要为了一时的方便而直接将大量的内部实现暴露出去。这会让你在以后承担很重的技术债务(很难更换新技术)。
  • 在您定义模块间交互时,请考虑如何使每个模块尽量隔离,通过设计良好的 api 来进行交互。
  • 您应用的核心应该是能让它脱颖而出的某些东西。不要浪费时间重复造轮子或一次次编写同样的模板代码。相反,应当集中精力在使您的应用独一无二,而将一些重复的工作交给这里介绍的 android architecture components 或其他优秀的库。
  • 尽可能持久化数据,以便您的应用在脱机模式下依然可用。虽然您可能享受着快捷的网络,但您的用户可能不会。
引用
另外,kotlin 版本的多个官方 sample 也公布啦,感兴趣的同学赶紧去看看吧:
原文:

  • 大小: 26.8 kb
  • 大小: 23.3 kb
来自:
2
1
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 但 google 最近还是推出了一份关于应用架构的实践指南,并给出了相当详尽的步骤和一些指导建议。希望大家都能看一看,学习一下,打造更加优秀易用的 app,也为 android 生态的改善做一点贡献。: ) 今年,官方在i/...

  • 但 google 最近还是推出了一份关于应用架构的实践指南,并给出了相当详尽的步骤和一些指导建议。希望大家都能看一看,学习一下,打造更加优秀易用的 app,也为 android 生态的改善做一点贡献。: ) 最近,官方推出...

  • 今年的 google i/o 发布了一个最新的官方示例 now in android,这个示例的完整度比之前的 jetnews、sunflower 要高,后面也将基于这个仓库做进一步的说明解析,从一个完整项目的角度来看 android 新推出的架构指南。

  • 但 google 最近还是推出了一份关于应用架构的实践指南,并给出了相当详尽的步骤和一些指导建议。希望大家都能看一看,学习一下,打造更加优秀易用的 app,也为 android 生态的改善做一点贡献。: ) 最近,官方...

  • 目标设定是应用程序创建的战略和规划阶段中最重要的一步。如果您没有制定明确定义的目标,那么您的应用程序就会从一开始就失败。它们在宏伟的计划中很重要,尤其是在营销您的手机时。有了明确定义的目标,您将轻松...

  • 今年的 google i/o 发布了一个最新的官方示例 now in android,这个示例的完整度比之前的 jetnews、sunflower 要高,后面也将基于这个仓库做进一步的说明解析,从一个完整项目的角度来看 android 新推出的架构指南。...

  • android architecture components 是一个由官方推出的新库,它能够帮助你去构建一个健壮,易测,可维护的应用。目前它还未正式发布(now available in preview)。所以抱着强烈的好奇心去了解了一下。 本文译自...

  • 在 2017 年,android 推出了 android jetpack,它是新一代组件、工具和架构指导,旨在加快 android 应用开发速度 android jetpack 分为四大块:architecture、ui、foundationy 以及 behavior,随着时间的增加,...

  • 谷歌官方android应用架构库(android architecture components)学习完整版

  • 在本指南中,我们将分解网络应用程序架构的概念,并了解它如何影响你的应用程序的最终用户体验。最后,我们还将看看你可以实施的一些最佳实践,以获得你的网络应用的最大收益。

  • spring oauth2.0 开发指南(一):体系架构和开发概览 文章目录spring oauth2.0 开发指南(一):体系架构和开发概览一、开篇二、oauth2.0 体系结构场景假设 a:基于图像的物品分类系统(ibcs,image-based ...

  • python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。python社区提供了大量的第三方库,如numpy、pandas和requests,极大地丰富了python的应用领域,从数据科学到web开发。python库的丰富性是python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,matplotlib和seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

  • stm32单片机fpga毕设电路原理论文报告基于ide硬盘的数字图像存储技术研究本资源系百度网盘分享地址

  • 适合rust入门。深入浅出,事无巨细,远胜市面上所有入门书。而且是免费的

  • vb语言vb房屋租凭管理系统毕业设计(源代码 系统)本资源系百度网盘分享地址

  • 这个示例代码,我们实现了一个用 c 语言判断一个数是否为素数的函数,并通过 main() 函数来测试这个函数。整个过程简单明了,代码结构清晰,易于理解和修改。这个示例展示了 c 语言中函数的定义和调用,以及条件判断和循环等基本语法的使用。

  • 层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例层次化网络设计案例

  • 1、嵌入式物联网单片机项目开发实战,每个例程都经过实战检验,简单好用。 2、代码同系列芯片理论是通用的。其他单片机型号请自行更改。 3、软件下载时,请注意下载方式以及对应的下载工具。 4、技术v:wulianjishu666; 5、如果接入其他传感器,请查看发布的其他资料。 6、单片机与模块的接线,在代码当中均有定义,请自行对照。

  • 四脚板凳u型焊接端子冲压成形机sw18可编辑_零件图_机械工程图_机械三维3d建模图打包下载.zip

global site tag (gtag.js) - google analytics
网站地图