Potato icon indicating copy to clipboard operation
Potato copied to clipboard

Android: Develop Tips

Open yunshuipiao opened this issue 5 years ago • 5 comments

Android: Develop Tips

[TOC]

逐渐更新一些关于 Android 开发设计的一下小技巧

Multi-Module

多模块设计参考:模块分层设计

Design

屏幕适配:建议使用今日头条屏幕适配方案

Develop

  1. 借助 Lifecycle,使得有需要的 java 类或者自定义控件,自动感知生命周期,解决内存泄漏的问题。

    (参考:https://developer.android.com/topic/libraries/architecture/lifecycle)

  2. 所有引入的第三方开源库,强烈建议 进行单独封装, 方便后续替换或者升级。

    (比如图片记载,网络请求等等)

yunshuipiao avatar Aug 28 '19 06:08 yunshuipiao

ViewModel 用于多Activity 共享数据

一般来说,一个 Activity 对应于一个 ViewModel。但有一些全局情况,比如用户退出登录,多个页面需要被通知,可以借助 ViewModelProvider.Factory 创建共享的 ViewModel 来通信和同步数据。


object SharedViewModelFactory : ViewModelProvider.Factory {

    val viewModelMap = hashMapOf<String, ViewModel>()

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        val canonicalName = modelClass.canonicalName ?: ""
        if (modelClass == UserViewModel::class.java) {
            return if (viewModelMap.containsKey(canonicalName)) {
                viewModelMap[canonicalName] as T
            } else {
                val viewModel = modelClass.newInstance()
                viewModelMap[canonicalName] = viewModel
                viewModel as T
            }
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }

    fun clearViewModel() {
        viewModelMap.clear()
    }
}

yunshuipiao avatar Dec 17 '19 13:12 yunshuipiao

Gson 和 kotlin 处理服务器返回数据的几个问题

遵循两端相互不信任的原则,客户端定义的服务器返回数据接口要 Any? (可空)的。 基本数据类型可以不为空,但 String,对象类型必须要可以空,防止出现空指针错误。 对于 String,但服务器返回空时,可以使用下列方法来返回 “”。

    private static class StringTypeAdapter implements JsonDeserializer<String> {

        @Override
        public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                if (json.isJsonPrimitive()) {
                    // 基本类型
                    String str = json.getAsString();
                    if (str != null) {
                        return str;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "";
        }
    }

yunshuipiao avatar Dec 18 '19 08:12 yunshuipiao

Retrofit2 + Coroutine 使用的几个问题

  1. 一般服务器返回的数据都有固定的格式。在使用中异步执行可以处理错误。但在协程下使用,只会得到返回结果,没有 rxjava2 或则异步的错误分支处理。 解决方法:可定义 okhttp 的拦截器,对 chain.process() 进行处理,将异常结果转换为 code = 200 的满足需要的服务器数据。

  2. 测试 使用 runBlocking{} 进行测试,但不可用在 Android 程序中。

class Retrofit_Test {

    val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(ErrorHandlerInterceptor())
        .addInterceptor { throw RuntimeException("error") }
        .build()

    val retrofit = Retrofit.Builder().
        client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl("https://api.github.com/").build()


    @Test
    fun listRepos_test() {
        val githubService = retrofit.create(GitHubService::class.java)
//        val response = githubService
//            .listRepos("octocat")
//            .execute()
//        println("code: ${response.code()}, body: ${GsonUtils.toJson(response.body())}")

//        GlobalScope.launch(Dispatchers.Default) {
//            val res = githubService.listRepos2("123")
//            println("res: 123")
//        }
        runBlocking {
            val response = githubService.listRepos2("octocat")
            println("res, body: ${GsonUtils.toJson(response)}")
        }
    }
}

class ErrorHandlerInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
        val request = chain.request()
        var response: Response
        try {
            response = chain.proceed(request)
        } catch (e: Exception) {
            val result = BaseResponse<List<Repo>>().apply {
                result = -9999
                message = "internal error"
                data = null
            }
            response = Response.Builder()
                .body(ResponseBody.create(null, GsonUtils.toJson(result)))
                .code(200)
                .message("internal error ${e.message}")
                .protocol(Protocol.HTTP_2)
                .request(request).build()
        }
        return response
    }

}

interface GitHubService {
    @GET("users/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<List<Repo>>

    @GET("users/{user}/repos")
    suspend fun listRepos2(@Path("user") user: String): BaseResponse<String>
}

class BaseResponse<T> {
    var result = 0
    var message = ""
    var data: T? = null
}

class Repo(var id: Int = 0) {
}

yunshuipiao avatar Dec 22 '19 02:12 yunshuipiao

解决LiveData 使用的问题

多次消费事件问题

LiveData 会持有当前的值。 可能会多次重复执行,可以参考: LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

yunshuipiao avatar Dec 22 '19 06:12 yunshuipiao

TextView 不同状态下改变背景和字体颜色

改变背景

除了使用 code 去改变, 推荐定义 xml 文件,设置 view background .

android:background="@drawable/relax_tab_background"

res/drawable

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true"  android:drawable="@drawable/relax_tab_selected"/>
    <item android:state_selected="false" android:drawable="@drawable/relax_tab_unselected" />
</selector>

改变字体颜色

与改变背景相似,定义 xml 文件

android:textColor="@color/relax_tab_text_color"

res/color/

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/relax_white"
        android:state_selected="true"/>
    <item android:color="#000000" android:state_selected="false"/>

</selector>

yunshuipiao avatar Dec 24 '19 13:12 yunshuipiao