Android实践 — ikan项目(一)

  • 转载时请注明出处,谢谢
  • / 0评 / 1

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

    ​ 本系列文章开篇也简单介绍了该项目的缘由,那么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层源码

    发表评论

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