Google推荐的Android的网络架构实现

Google推荐的网络架构,关键部分如下:
Part 1:

Use Room to fetch and cache data
Ensure that your app stores all data on disk using a database or similar structure so that it performs optimally regardless of network conditions. Use the Room persistence library to cache data in a local database, and use WorkManager to update that cache when the device has a network connection.
Apps should cache content that is fetched from the network. Before making subsequent requests, apps should display locally cached data. This ensures that the app is functional regardless of whether the device is offline or on a slow or unreliable network.

Part 2:

Deduplicate network requests
An offline-first architecture initially tries to fetch data from local storage and, failing that, requests the data from the network. After being retrieved from the network, the data is cached locally for future retrieval. This helps to ensure that network requests for the same piece of data only occur once—with subsequent requests satisfied locally. To achieve this, use a local database for long-lived data (usually android.database.sqlite or SharedPreferences).
This architecture also simplifies an app’s flow between offline and online states as one side fetches from the network to the cache, while the other retrieves data from the cache to present to the user.
For transitory data, use a bounded disk cache such as a DiskLruCache. Data that doesn’t typically change should only be requested once over the network and cached for future use. Examples of such data are images and non-temporal documents like news articles or social posts.

先说Part2,这部分通过缓存就可以实现了,例如使用OkHttp访问网络时采用缓存技术,这部分应该是针对少量数据的情形,例如获取用户某些信息等,这些信息保存在缓存中就足够了,没有必要保存到本地数据库中(没有必要为一条记录新建一个数据表)。
Part 1部分将数据保存到本地数据库,是否还需要Part 2的网络缓存呢?这要看项目怎么考量了。个人觉得可以在非wifi情况下设置网络缓存,而WiFi网络情况下,强制使用网络数据。

OkHttp缓存相关部分,网上有很多文章,这里就不累述,这里注意是针对Part 1,实现优先从本地数据库读取数据,然后使用网络数据更新本地数据库。其实这个架构,有大神已实现得很好(https://xiaozhuanlan.com/topic/2156403789),我这里做了一些简化,结合了Google Part 1得内容来实现。

  1. 架构封装这面,参考了上面那位大神,做了一些简化并采用kotlin实现:
abstract class AbsDataSource<ResultType:Any> {
    private val result = MediatorLiveData<Result<ResultType>>()

    abstract fun loadFromDb():LiveData<ResultType>

    abstract fun shouldFetch(data: ResultType?):Boolean

    abstract fun onFetchFailed()

    abstract suspend fun saveNetResult(data: ResultType?)

    abstract fun createNetCall():LiveData<Result<ResultType>>

    fun getAsLiveData():MediatorLiveData<Result<ResultType>>{
        return result
    }

    init {
        val dbSource = this.loadFromDb()
        result.value = Result.Loading("db load", dbSource.value!!)
        result.addSource(dbSource) { data ->
            result.removeSource(dbSource)
            if(shouldFetch(data)) {
                fetchFromNetwork(dbSource)
            } else {
                onFetchFailed()
                result.addSource(dbSource) {
                    result.value = Result.Success(it)
                }
            }
        }
    }

    private fun fetchFromNetwork(dbSource: LiveData<ResultType>){
        val netSource = createNetCall()
        result.addSource(netSource) { response ->
            result.removeSource(netSource)
            if(response is Result.Success) {
                saveResultAndReInit(response)
            } else {
                result.addSource(dbSource) {
                    result.value = Result.Error((netSource.value as Result.Error).exception, it)
                }
            }
        }
    }

    private fun saveResultAndReInit(response:Result<ResultType>) {
        GlobalScope.launch{
            saveNetResult((response as Result.Success).data)
            result.addSource(loadFromDb()){
                result.value = Result.Success(it)
            }
        }
    }
}

首先从本地读取数据:loadFromDb(),对于列表式数据可以使用ROOM实现,如果是比较少量得数据,则可以用SharedPreferences。然后网络读取数据fetchFromNetwork,完成后保存到本都数据:saveNetResult,然后再loadFromDb()一次。
这就是网络架构通用得核心了,其它代码就是具体使用了,Result得代码如下:

sealed class Result<out T: Any> {
    data class Success<out T: Any>(val data:T):Result<T>()
    data class Loading<out T: Any>(val message:String, val data:T):Result<T>()
    data class Error<out T: Any>(val exception:Exception, val data:T):Result<T>()
    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error<*> -> "Error[exception=$exception]"
            is Loading<*> -> "Loading[message=$message]"
        }
    }
}
  1. 架构的使用
    遵循Google的设计,使用WorkManager来获取网络数据并更新, Repository的代码:
class FriendRepository(application: Application) {
    val friendDao = (application as AssistanceApplication).getDataBase().friendDao()
    val app = application

    fun update() = loadFriendsData()

    fun loadFriendsData() = object:AbsDataSource<List<FriendEntity>>(){
        override fun loadFromDb(): LiveData<List<FriendEntity>> = friendDao.loadAll()

        override fun onFetchFailed() {
            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        }

        override suspend fun saveNetResult(data: List<FriendEntity>?) = friendDao.saveFriendItems(data!!)

        override fun shouldFetch(data: List<FriendEntity>?): Boolean = true

        override fun createNetCall(): LiveData<Result<List<FriendEntity>>> {
            val result = MediatorLiveData<Result<List<FriendEntity>>>()
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()
            val token = Constants.getUserToken(app, true)
            val data = Data.Builder().putString(Constants.WS_MSG_TOKEN_SELF, token).build()
            val friendRequest =  OneTimeWorkRequest.Builder(FriendWorker::class.java)
                .setConstraints(constraints)
                .setInputData(data)
                .build()
            WorkManager.getInstance(app).enqueue(friendRequest)
            val workerInfo = WorkManager.getInstance(app).getWorkInfoByIdLiveData(friendRequest.id)
            result.addSource(workerInfo) {
                if (it != null && it.state == WorkInfo.State.SUCCEEDED) {
                    val response = it.outputData.getString("response")
                    val parseResult = parseData(response)
                    if(parseResult is Result.Success) {
                        result.postValue(parseResult)
                    } else {
                        onFetchFailed()
                    }
                    result.removeSource(workerInfo)
                } else if(it != null && (it.state == WorkInfo.State.FAILED || it.state == WorkInfo.State.CANCELLED)){
                    result.removeSource(workerInfo)
                }
            }

            return result
        }
    }.getAsLiveData()

    private fun parseData(data: String?):Result<List<FriendEntity>>{
        val friendList:MutableList<FriendEntity> = arrayListOf()
        if(data == null || data.isEmpty()) {
            return Result.Error(Exception("Network unavailable"), friendList)
        }
        //real parse data
        return Result.Success(friendList)
    }
}

FriendWorker进行真正的网络访问,这里使用Okhttp的同步方法即可,代码如下:

class FriendWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {
    override fun doWork(): Result {
        val token = inputData.getString(Constants.WS_MSG_TOKEN_SELF) ?: return Result.failure()
        val map = HashMap<String, String>()
        map[Constants.WS_MSG_TOKEN_SELF] = token
        val response = OkHttpUtil.baseSyncGet(Constants.getFriendListUrl(), map)
        val data = Data.Builder().putString("response", response).build()
        return Result.success(data)
    }
}
  1. ROOM以及ViewModel的代码,可参考上文提到博文,这部分都都差不多。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值