Android实践 — ikan项目(一)

/ 0评 / 3

  有很多业内同学对软件工程半知半解,具体表现在开完产品需求会议之后,照着产品经理给的需求文档就开始进入编码阶段,这类开发人员中不乏技术水平不错的,但最终工程效率却是比较低下,这种不专业软件开发方法该如何避免,我在本系列博客中会尽自己所能做讲解。

​ 本系列文章开篇也简单介绍了该项目的缘由,那么android工程如何有条不紊的进行呢? 必须要制定计划,监控计划并实施计划。虽然是一个人在做ikan产品,但是我深知计划的重要性,所以最开始做的工作不是马上编程而是制定计划。在重新审视产品需求后将产品需求根据模块依赖划分多个sprint,然后逐一完成,首个sprint需要做的工作是:

  1. 明确哪些是客户端该做的工作
    秉着轻客户端理念,客户端仅做数据的展示和处理与用户的交互工作。结合应用需求本身,哪些是ikan客户端可以不做的:

    • 影视剧的资源上传任务
    • 登录用户的认证有效期
  2. 明确和服务端交互的数据格式
    进一步明确服务端提供的是RESTful风格的微服务,客户端接收的content-type是JSON格式并且具体的响应格式为:

    {
      status_code:xxx
      msg:  xxx
      data: xxx
    }
    

    这里我稍作解释下,为什么不采用http code,因为多数业务http code是不能满足需求的,所以客户端仅解析http code为200的响应,请求结果的业务状态由status_code判定,data是业务bean实体,虽稍违RESTful理念,但实属无奈。

  3. 确定项目所采用的开发模式

    MVP模式是近几年android工程常用的开发模式,网络也有很多介绍该模式的文章,如果之前对该模式没有任何了解的同学可以搜索下相关文章学习下,我就不做细致的讲解了,至于我为什么会选择MVP模式进行开发是因为我觉得这种模式比较适合开发android应用,视图层和模型层间低耦合促进关注点分离,有利于模块化开发。 MVP模式示意图:

    mvp

在有以上自定规约后,android项目进入了预架构设计阶段,需要考虑的内容:接口数据怎么获取,接口数据缓存在哪个阶段处理,网络相关异常在哪里处理,视图渲染前的数据在哪里处理等。为了职责更加分明,项目分层架构已是必然,采用clean arch方案我把项目划分data、domain、presentation三层,然后基于此继续扩展,从data层抽离出net层,从presentation层抽离出mvp层和design层。然后开始逐一实施,从最外层data分离出的net层处理。net层毋庸置疑其职责处理网络相关的,一开始项目制定的规约包含网络RESTful微服务,为此客户端采用square开源的retrofit组件和okhttp组件, 这两个组件都是市场比较认可的,我在net层引入并稍作扩展。retrofit组件提供的两个接口需要简单介绍一下。

CallAdapter
实现该接口可以将response type从某一类型转成另一类型。本项目使用RxJava2CallAdapter将Callback Type转成Observable Type。

private fun createServiceCreator() = Retrofit.Builder()
        .baseUrl(apiBaseUrl)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()

Converter
实现该接口可以将http response body转换成我们想要的格式。规约中http请求返回的content-type
为JSON,那么这里直接使用Gson作为转换器。

private fun createServiceCreator() = Retrofit.Builder()
        .baseUrl(apiBaseUrl)
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build()

其次具体响应的JSON格式已规定好,为此客户端定义基础响应类型DataResponse来匹配。

data class DataResponse<T>(@SerializedName("status_code") var statusCode: Int = 0,
                           @SerializedName("msg") var msg: String? = null,
                           @SerializedName("data") var data: T? = null)

okhttp组件对外Interceptor接口,实现该接口可以对请求发出前的request对象或者请求响应后response对象分别做处理。本项目利用该机制将http请求固定参数添加到http header中。实现如下:

abstract class BaseNetworkRequestInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val headerParamsMap = injectParamIntoHeader(HashMap())
        val requestOrigin = chain.request()
        val headersOrigin = requestOrigin.headers()
        val headerBuilder = headersOrigin.newBuilder()

        for ((key, value) in headerParamsMap) {
            headerBuilder.set(key, value)
        }

        val request = requestOrigin.newBuilder().headers(headerBuilder.build()).build()
        return chain.proceed(request)
    }

    open fun injectParamIntoHeader(headerParamsMap: MutableMap<String, String>):        MutableMap<String, String> = headerParamsMap
}

以下是okhttp创建过程,其中为okhttpClient添加HttpLoggingInterceptor请求日志拦截。

private fun createHttpClient(): OkHttpClient {
    val httpClientBuilder = createHttpBuilder()

    if (BuildConfig.DEBUG) {
        val loggingInterceptor = HttpLoggingInterceptor { message ->
            Logger.d(message)
        }
        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        httpClientBuilder.addInterceptor(loggingInterceptor)
    }

    httpClientBuilder.connectTimeout(timeoutDuration, TimeUnit.SECONDS)
    httpClientBuilder.addInterceptor(interceptor)

    return httpClientBuilder.build()
}

接着将okhttp关联至retrofit

private fun createServiceCreator() = Retrofit.Builder()
        .baseUrl(apiBaseUrl)
        .client(createHttpClient())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .build()

以上过程仅仅是net链中的数环,有这些不足以支撑整个数据调用链,现在开始延长net链。想要请求网络必须要将retrofit对象暴露出来,因此必须提供一个Connector接口用来提供retrofit对象。代码如下:

interface Connector {

    fun getServiceCreator(): Retrofit
}

考虑到项目网络请求的安全性,项目的登陆和注册流程等账号相关操作采用https请求模式,其它请求采用http请求模式。默认的Http Connector创建过程如下:

abstract class BaseApiConnector(private val interceptor: Interceptor,
                                private val timeoutDuration: Long) : Connector {

    protected abstract val apiBaseUrl: String

    override fun getServiceCreator(): Retrofit = createServiceCreator()

    private fun createServiceCreator() = Retrofit.Builder()
            .baseUrl(apiBaseUrl)
            .client(createHttpClient())
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()

    open fun createHttpBuilder() = OkHttpClient.Builder()

    private fun createHttpClient(): OkHttpClient {
        val httpClientBuilder = createHttpBuilder()

        if (BuildConfig.DEBUG) {
            val loggingInterceptor = HttpLoggingInterceptor { message ->
                Logger.d(message)
            }
            loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
            httpClientBuilder.addInterceptor(loggingInterceptor)
        }

        httpClientBuilder.connectTimeout(timeoutDuration, TimeUnit.SECONDS)
        httpClientBuilder.addInterceptor(interceptor)

        return httpClientBuilder.build()
    }
}

负责进行https请求的connector

public abstract class BaseApiSSLConnector extends BaseApiConnector {

    private static List<byte[]> CERTIFICATES_DATA = new ArrayList<>();

    private Context context;

    public BaseApiSSLConnector(Context context, Interceptor signInterceptor, long timeout) {
        super(signInterceptor, timeout);

        this.context = context;
        createHttpCerts();
    }

    @Override
    public OkHttpClient.Builder createHttpBuilder() {
        List<InputStream> certificates = new ArrayList<>();
        List<byte[]> certsData = getCertificatesData();

        if (certsData != null && !certsData.isEmpty()) {
            for (byte[] bytes : certsData) {
                certificates.add(new ByteArrayInputStream(bytes));
            }
        }

        HttpsManager.SSLParams sslParams = HttpsManager.getSslSocketFactory(certificates, null, null);

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
                .hostnameVerifier(new HttpsManager.UnSafeHostnameVerifier());

        return builder;
    }

    private void createHttpCerts() {
        try {
            String[] certFiles = context.getAssets().list("certs");
            if (certFiles != null) {
                for (String cert : certFiles) {
                    InputStream is = context.getAssets().open("certs/" + cert);
                    addCertificate(is);
                }
            }
        } catch (IOException e) {
        }
    }

    private synchronized static void addCertificate(InputStream inputStream) {
        if (null != inputStream) {
            try {
                int ava;
                int len = 0;
                ArrayList<byte[]> data = new ArrayList<>();
                while ((ava = inputStream.available()) > 0) {
                    byte[] buffer = new byte[ava];
                    inputStream.read(buffer);
                    data.add(buffer);
                    len += ava;
                }

                byte[] buff = new byte[len];
                int dstPos = 0;
                for (byte[] bytes : data) {
                    int length = bytes.length;
                    System.arraycopy(bytes, 0, buff, dstPos, length);
                    dstPos += length;
                }
                CERTIFICATES_DATA.add(buff);
            } catch (IOException e) {
            }
        }
    }

    private static List<byte[]> getCertificatesData() {
        return CERTIFICATES_DATA;
    }
}

net链的最后一环,也是retrofit的基本使用,首先定义FeedService

interface FeedService {

    @GET("/feed/home/")
    fun getHomeFeeds(@QueryMap queryMap: Map<String, String>): Observable<DataResponse<List<FeedEntity>>>
}

之后通过connector或者retrofit对象创建feedService实例进行请求

    private val feedService: FeedService = connector
            .getServiceCreator()
            .create(FeedService::class.java)

    override fun getFeedDetail(feedParamProvider: FeedParamProvider): Observable<FeedEntity> {
        return feedService.getFeedDetail(feedParamProvider.getFeedId())
    }

项目net层简述至此告一段落了,有什么疑惑的地方可以留言,看到会第一时间回复。

net层源码

发表评论

电子邮件地址不会被公开。 必填项已用*标注