Skip to content

Android平台提供强大稳健以及通用的网络通信服务层,我的目标是 —— 简单且完美

Notifications You must be signed in to change notification settings

codeMingLi/MyHttp

Repository files navigation

English version is being re-translated, coming soon...

Android平台提供强大稳健以及通用的网络通信服务层,我的目标是 —— 简单且完美 

最新版本

V1.0.0

同类对比

  1. 完美配套市面所有第三方网络库
  2. 接入和使用简单易懂;
  3. 扩展性极强,满足项目后续改造;
  4. 提供各种苛刻的业务性需求;
  5. 代码简洁优美;

一、功能介绍

  1. 提供简单易用的多线程并发访问(线程,队列等)
  2. 提供对业务层的访问代码保持不变
  3. 提供自定义网络访问和第三方网络库的切换和更新
  4. 提供网络层的日志统计,实现无缝对接服务端的日志系统
  5. 支持https以及签名证明等
  6. 提供高性能访问机制(DNS解析,长短链接以及重传机制等优化)
  7. 提供业务层的自定义特色需求
  8. 支持响应数据的缓存,根据缓存类型实现各种复杂的数据缓存
  9. 提供拦截器和过滤器 10.实现一套简化版的响应式编程,支持线程调度器的使用和常见操作符实现(类似rxjava)

二、典型应用

  1. 中小企业网络访问层

  2. 目前此套网络通信架构库已经服务了上亿流水和历经多年的磨练而出。

三、基础功能

  1. 添加依赖和配置
dependencies{// 替换成最新版本, 需要注意的是api compile 'com.xiaoniu.corelib:myhttp:1.0.0'... }
  1. 添加混淆规则(如果使用了Proguard)
暂无
  1. 初始化使用 在程序启动加载myhttp进行初始化工作
Builderbuilder = newBuilder(this, Constants.BASE_URL) .enableStatistic(true) .appTrustCaStr(Constants.TRUST_CA_STR) .appValidateHttpsCa(true) .connectionTimeout(Consts.DEFAULT_SOCKET_TIMEOUT) .readConnectionTimeout(Consts.DEFAULT_SOCKET_TIMEOUT) .filter(newLoginFilter()) //.filter(new MultipleResponseFilter().addFilter(new LoginFilter())) .cookieController(newAppCookieController()) .enableDns(false) //.dnsResolverController(new HttpDnsResolverController())//.client(new OkHttpClient()) .client(newHttpUrlConnectionClient()); Global.init(builder);

详见com.xiaoniu.finance.myhttp.Global

  1. 请求API
目前网络请求是POST和GET两个方式为主,可以根据自己需求特定调用 调用方式:HttpManager.getInstance().doPost(request); 
//DemoApi类/** * test数据 */publicstaticvoidrequestTest(Stringtype, StringrequestId, OnRequestListenerl){Map<String, String> map = newHashMap<String, String>(); map.put("type", type); StringrequestUrl = BASE_URL + "xxxn.json"; Requestrequest = RequestCreator.createRequest(l); request.setHttpType(Consts.HTTP_TYPE_POST); request.setUrl(requestUrl); request.setUriParam(map); request.setTaskGroupdID(requestId); request.setCacheData(RequestCacheType.ClearAndUpdate); request.setParser(newJsonParser(GeneralProjectResponse.getParseType())); HttpManager.getInstance().doPost(request)}

注:数据解析通过IDataParser接口,具体数据解析接口的实现请查看进阶用法

  1. 调用和数据处理操作

5.1 调用操作

在需要使用的地方进行网络调用,DemoApi.requestTest(xxx)即可完美访问网络数据;

目前本人公司是通过eventbus架构方式访问网络,代码如下,关于eventbus用法请查看进阶用法

/**网络请求数据方法*/privatevoidrequestData(finalbooleanisShowLoading){if (isRequesting){return} if (isFinishing()){return} if (isShowLoading){mLoadingDialogHelper.showLoadingDialog(this, false, "加载中。。。")} isRequesting = true; //request apiDemoApi.requestTest("YXN", TAG, newOnInnerRequestListener(newAppMessageEvent.TestResponseEvent()))}

5.2. 请求响应处理操作

@Subscribe(threadMode = ThreadMode.MAIN) publicvoidprocessTest(AppMessageEvent.TestResponseEventresponseEvent){isRequesting = false; mLoadingDialogHelper.dismissLoadingDialog(); Objectresult = responseEvent.result; Requestrequest = responseEvent.request; intstate = responseEvent.state; if (request.isCancelReqesut()){return} StringerrorTip = getResponeErrorTip(state, result, true); if (!TextUtils.isEmpty(errorTip)){Toast.makeText(this, errorTip, Toast.LENGTH_SHORT).show(); return} BaseResponseresponse = (BaseResponse) result; data = (GeneralProjectResponse) response.data; //TODO }

四、进阶用法(暂列部分干货,整个架构还有很多细节上的干货需要自己去读代码,保证收获满满,记得star下)

  1. 网络库选型功能

随着业务的推进和公司的发展,底层网络通信交互肯定也有变化。例如初期选择了一套HttpClient, 发现更优的第三库如okhttp,最后随着安全自己实现一套c++的网络通信协议。所以网络库选型的封装是必用的。 所以提供高扩展性通信协议库的切换和更新接口

/** * [网络请求客户端] * @author zhonghu */publicinterfaceAbstractClient{/** * get请求 * @param request * @return * @throws Throwable */ResponsedoGet(Requestrequest) throwsThrowable; /** * post请求 * @param request * @return * @throws Throwable */ResponsedoPost(Requestrequest) throwsThrowable}

目前只提供get和post,如果自己需要实现其他的method请download改造之;

1.1. HttpUrlConnection库的扩展实现

publicclassHttpUrlConnectionClientimplementsAbstractClient{@OverridepublicResponsedoPost(Requestrequest) throwsThrowable{if (request == null || TextUtils.isEmpty(request.getRequestEntireUrl())){returnnull} Map<String, String> postData = newHashMap<String, String>(); Stringurl = getUrlAndParems(request, postData); //String url = requestInfo.getRequestEntireUrl();Log.i(TAG, "doPost:" + url); Map<String, String> httpHeader = newHashMap<String, String>(request.getHttpHead()); StringrequestData = getRequestData(getPostData(request, postData), httpHeader); byte[] postContent = requestData.getBytes(); returnconnection(url, Consts.METHOD_POST, postContent, httpHeader)} }

1.2. okhttp库的扩展实现

publicclassOkHttpClientimplementsAbstractClient{privatestaticfinalStringTAG = "OkHttpClient"; publicstaticfinalokhttp3.MediaTypeJSON = okhttp3.MediaType.parse(CONTENT_TYPE); privatestaticokhttp3.OkHttpClientmClient; publicOkHttpClient(){try{mClient = newokhttp3.OkHttpClient.Builder() .connectTimeout(ClientVariableManager.getInstance().getConnectionTimeout(), TimeUnit.MILLISECONDS) .readTimeout(ClientVariableManager.getInstance().getReadConnectionTimeout(), TimeUnit.MILLISECONDS) .writeTimeout(ClientVariableManager.getInstance().getReadConnectionTimeout(), TimeUnit.MILLISECONDS) .sslSocketFactory(JavaSSLSocketFactory.getSslSocketFactory(), JavaSSLSocketFactory.getVerifierTrustManager()) .hostnameVerifier(JavaSSLSocketFactory.getHostnameVerifier()) .addInterceptor(newOkHttpExceptionInterceptor()) .build()} catch (Exceptione){e.printStackTrace()} } /** * okhttp AsyncCall only catch the IOException, other exception will occur crash,this Interceptor impl can transform all exception to IOException */privatestaticclassOkHttpExceptionInterceptorimplementsokhttp3.Interceptor{@Overridepublicokhttp3.Responseintercept(Chainchain) throwsIOException{try{returnchain.proceed(chain.request())} catch (Throwablee){if (einstanceofIOException){throwe} else{thrownewIOException(e)} } } } /** * * @param request * @return * @throws Throwable */@OverridepublicResponsedoPost(Requestrequest) throwsThrowable{if (request == null || TextUtils.isEmpty(request.getRequestEntireUrl())){returnnull} Map<String, String> postData = newHashMap<String, String>(); Stringurl = getUrlAndParems(request, postData); // String url = requestInfo.getRequestEntireUrl();Log.i(TAG, "doPost:" + url); Map<String, String> httpHeader = newHashMap<String, String>(request.getHttpHead()); StringrequestData = getRequestData(getPostData(request, postData), httpHeader); returnconnection(url, Consts.METHOD_POST, requestData, httpHeader)} }

1.3. 自己实现的c++库的扩展实现

publicclassMyClientimplementsAbstractClient{@OverridepublicResponsedoPost(Requestrequest) throwsThrowable{if (request == null || TextUtils.isEmpty(request.getRequestEntireUrl())){returnnull} Map<String, String> postData = newHashMap<String, String>(); Stringurl = getUrlAndParems(request, postData); //String url = requestInfo.getRequestEntireUrl();Log.i(TAG, "doPost:" + url); Map<String, String> httpHeader = newHashMap<String, String>(request.getHttpHead()); StringrequestData = getRequestData(getPostData(request, postData), httpHeader); byte[] postContent = requestData.getBytes(); returnmyConnection(url, Consts.METHOD_POST, postContent, httpHeader)} }
  1. DNS功能

2.1. 开启DNS开关

其中true表示已经开启DNS解析功能,false表示关闭

builder.enableDns(true)

注意:默认DNS解析通过java的InetAddress.getByName实现

DnsResolverControllerDEFALUT = newDnsResolverController(){privateStringTAG = "DnsResolverController"; @OverridepublicStringdnsResolver(Stringdomain){try{InetAddressinetAddress = InetAddress.getByName(domain); returninetAddress.getHostAddress()} catch (UnknownHostExceptione){Log.e(TAG, "Inet Address Analyze fail exception : ", e)} catch (Exceptione){Log.e(TAG, "Inet Address Analyze fail exception : ", e)} catch (Errore){Log.e(TAG, "Inet Address Analyze fail exception : ", e)} returnnull} };

2.2. 满足市面第三方DNS解析的扩展

可以通过自定义方式,你只需要简单的实现DnsResolverController接口, 可参考demo例子的HttpDnsResolverController

//实现腾讯的httpdns+库builder.dnsResolverController(newHttpDnsResolverController())
  1. 响应数据的缓存

满足特定业务需求添加响应数据缓存。例如为了减少服务端访问一段时间使用上次缓存等

3.1. 提供缓存模式机制

目前提供三种模式,满足大幅业务需求:

1、不提供缓存(默认值);

2、提供全局缓存,缓存删除需要用户手动操作;

3、提供访问后删除缓存模式;

// 使用缓存方式request.setCacheData(RequestCacheType.ClearAndUpdate); //缓存模式/** * 请求缓存类型 */publicenumRequestCacheType{/** * 不缓存 */None(0), /** * 一直缓存,手工清空缓存 */Always(1), /** * 获取缓存后然后下次重新更新获取缓存 */ClearAndUpdate(2)}

3.2. 缓存数据的存储功能

缓存存储就是把响应数据进行存储在介质上,例如文件,数据库,sharepreference等

默认已经实现了数据库缓存介质

//启用缓存存储介质builder.cacheController(newDatabaseCacheController())

也可以通过自定义方式,你只需要简单的实现ICacheController接口,可参考

com.xiaoniu.finance.myhttp.cache.DatabaseCacheController
  1. Cookie的支持

我们在实现web业务功能可能需要给浏览器提供cookie机制, 例如当app登陆后获取到tokenid/sessionid,此时让浏览器感知已经登陆, 你可以通过把tokenid值存在cookie里面,浏览器后续可以把令牌值传递服务端实现web免登陆等功能。 本质上就是app层和web层共享一份cookie数据

4.1. cookie的同步和读取操作

请详细查看CookieHelper类

使用片段代码: <!-- CookieHelper类 --> CookieHelper.getInstance().synCookies// 同步cookie数据CookieHelper.getInstance().readCookie// 读取cookie数据CookieHelper.getInstance().removeCookie// 移除cookie数据

4.2. cookie的本地缓存操作

上面说过app层和web层共享一份cookie数据,但是其两种格式可以不一样,例如web层肯定是符合 android的CookieManager类的实现的格式,而app层则可以保存json格式等文本数据,所以提供接口 com.xiaoniu.finance.myhttp.http.cookie.CookieController进行缓存操作

publicinterfaceCookieController{CookieControllerNO_COOKIES = newCookieController(){@OverridepublicvoidsaveCookies(Contextcontext, List<SaveCookies> cookies){} @OverridepublicList<SaveCookies> loadCookies(Contextcontext){returnnull} }; /** * 缓存cookie数据 */voidsaveCookies(Contextcontext, List<SaveCookies> cookies); /** * 加载cookie数据 */List<SaveCookies> loadCookies(Contextcontext)}

具体实现可以查看demo下的AppCookieController类

  1. 通信数据解析的支持

不同的业务不同的数据格式,需要统一解析实现封装则必须实现一套通信数据解析机制, 其数据解析通过实现接口来完成统一解析功能

/** * 数据解析接口 */publicinterfaceIDataParser{/** * [解析数据]<br/> * * @param data 需要解析的数据 * @return Object 解析后的数据 */ObjectparseData(Stringdata)}

如果数据格式是json,则实现json格式的解析机制

publicclassJsonParserimplementsIDataParser{privatestaticGsonsGson; static{sGson = newGsonBuilder() // .excludeFieldsWithoutExposeAnnotation() //不导出实体中没有用@Expose注解的属性// .setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")//时间转化为特定格式// .setPrettyPrinting() //对json结果格式化.// .enableComplexMapKeySerialization() //支持Map的key为复杂对象的形式// .serializeNulls() .create()} java.lang.reflect.TypeparseType; publicJsonParser(java.lang.reflect.TypeparseType){this.parseType = parseType} @OverridepublicObjectparseData(Stringdata){returnsGson.fromJson(data, parseType)} }

一般网络通信数据的格式定义都是有规律的,java通过范性设计整体解析数据

publicclassBaseResponse<T> implementsSerializable{publicStringcode; publicStringmessage; publicTdata; publiclongserverTime}
  1. eventbus的进阶

为了防止ui和listener间的内存泄露,可通过eventbus解耦实现之

整体实现就是对eventbus进行简单的封装,具体实现和封装请参考demo项目

publicclassBaseActivityextendsAppCompatActivity{privateEventBusLifePorxymEventBusLifePorxy; @OverrideprotectedvoidonCreate(BundlesavedInstanceState){setIfUseEventBus(isUseEventBus()); super.onCreate(null); if (mEventBusLifePorxy != null){mEventBusLifePorxy.create(this)} } privatevoidsetIfUseEventBus(booleanuseEventBus){if (mEventBusLifePorxy == null){mEventBusLifePorxy = EventBusLifePorxy.getInstance()} mEventBusLifePorxy.setIfUseEventBus(useEventBus)} protectedbooleanisUseEventBus(){returnfalse} @OverrideprotectedvoidonDestroy(){super.onDestroy(); if (mEventBusLifePorxy == null){return} mEventBusLifePorxy.destory(this)} }
  1. 线程任务的进阶

项目很容易出现线程池满天飞,导致不规范不统一而且更容易出现问题,所以网络层最好统一对外提供并发服务; 但是多线程模式对使用者不友好,根据前辈实践,rx响应式编程非常利于多线程编程,我这边根据rxjava模拟一套简化版rx 为啥自己实现一套,因为本进阶仅仅就是为了让多线程并发使用方便简洁,没必要去学习复杂且陡度高的rxjava, 实现了操作符:map,flatMap,filter,taskOn,callbackOn。 其中taskOn和callbackOn就是线程间切换的操作符

7.1. 调用

AsyncJob.from(assetFile).map(newProcessor<String, InputStream>(){@OverridepublicInputStreamprocess(Strings){Log.e("w", Thread.currentThread().getName()); InputStreamis = null; AssetManageram = getAssets(); try{is = am.open(assetFile)} catch (IOExceptione){e.printStackTrace()} returnis} }) .taskOn(Schedulers.immediate()) .fliter(newProcessor<InputStream, Boolean>(){@OverridepublicBooleanprocess(InputStreaminputStream){Log.e("w", Thread.currentThread().getName()); returninputStream != null} }) .map(newProcessor<InputStream, String>(){@OverridepublicStringprocess(InputStreaminputStream){Log.e("w", Thread.currentThread().getName()); StringBuildersb = newStringBuilder(); try{BufferedReaderbr = newBufferedReader(newInputStreamReader(inputStream, "UTF-8")); Stringstr; while ((str = br.readLine()) != null){sb.append(str)} br.close()} catch (Exceptione){} returnsb.toString()} }) .taskOn(Schedulers.io()).callbackOn(Schedulers.main()) .callback(newCallback<String>(){@OverridepublicvoidonCompleted(){} @OverridepublicvoidonNext(Strings){Log.e("w", Thread.currentThread().getName()); tv.setText(s)} @OverridepublicvoidonError(Throwablet){} });

7.2. AsyncJob的实现

publicclassAsyncJob<T>{privateOperator<T> dataOperator; privateAsyncJob(Operator<T> operator){this.dataOperator = operator} publicstatic <T> AsyncJob<T> create(Operator<T> operator){returnnewAsyncJob<>(operator)} publicstatic <T> AsyncJob<T> from(finalT... datas){returnnewAsyncJob<>(newOperator<T>(){@Overridepublicvoidcall(Callback<T> callback){for (Tdata : datas){callback.onNext(data)} } })} /** * 实现回调操作 */publicfinalvoidcallback(Callback<T> callback){dataOperator.call(callback)} /** * 实现filter操作 */publicfinalAsyncJob<T> fliter(Processor<T, Boolean> processor){returnnewAsyncJob<>(newFilterOperator<>(this, processor))} /** * 实现map操作 */publicfinal <Result> AsyncJob<Result> map(Processor<T, Result> processor){returnnewAsyncJob<>(newMapOperator<>(this, processor))} /** * 实现flat map操作 */publicfinal <Result> AsyncJob<Result> flatMap(Processor<T, AsyncJob<Result>> processor){AsyncJob<AsyncJob<Result>> flat = map(processor); returnnewAsyncJob<>(newFlatMapOperator<>(flat.dataOperator))} /** * 实现工作线程调度器 */publicfinalAsyncJob<T> taskOn(Schedulerscheduler){returnnewAsyncJob<>(newTaskOnOperator<>(dataOperator, scheduler))} /** * 实现回调线程调度器 */publicfinalAsyncJob<T> callbackOn(Schedulerscheduler){returnnewAsyncJob<>(newCallbackOnOperator<>(this, scheduler))} }

7.3. 线程调度器的实现(Schedulers类提供主流的线程池调度器)

/** * 主线程的调度器 * 常用场景: * 回到主线程时的UI操作 */publicstaticSchedulermain(){returnMainSchedulerHolder.INSTANCE} /** * http请求的调度器 * 常用场景: * http请求 */publicstaticSchedulerhttp(){returnHttpSchedulerHolder.INSTANCE} /** * io请求的调度器 * 常用场景: * 访问大文件/流 */publicstaticSchedulerio(){returnIOSchedulerHolder.INSTANCE} /** * 后台守护者的调度器 * 常用场景: * 执行优先级很低的场景 */publicstaticSchedulerwatch(){returnWatchSchedulerHolder.INSTANCE} /** * 立即执行的调度器 * 常用场景: * 1/访问sharePrefrence * 2/访问assert * 3/访问很小的文件/流 */publicstaticSchedulerimmediate(){returnImmediateSchedulerHolder.INSTANCE}
  1. 特殊的业务请求的进阶

例如有些特殊的业务网络接口需要在网络一旦有的时候,任何网络请求后同时启动此类业务接口。 目前这边也根据这种特殊的业务也实现了扩展,例如配置类的更新接口,软件的更新接口。你只需要简单的注册监听器即可完美实现; 具体可以详细分析RetryRequestMonitor

/** * 响应监听器, * 实现当存在网络时则唤醒所有注册过此监听的网络请求 */publicinterfaceAbstractMonitor{/** * * @param url * @param state * @param result * @param type * @param request * @param response */voidonMonitor(Stringurl, intstate, Objectresult, inttype, Requestrequest, Responseresponse)}

使用:RetryRequestMonitor进行注册即可

/** * [注册重试请求器] * 注册进来的请求器会在每一次其他接口请求成功后自动触发它 * 如果注册后没有注销,在其他接口成功请求后总是会触发它被执行,它无法判断你需要的重试接口是否被成功处理 * 是否需要重试或不重试,需要你自己的regist和unRegist来告诉它 */publicsynchronizedvoidregistRetryApi(Stringkey, IRetryRequesterrequester)

例子:

//业务接口实现代码privatestaticclassConfigRequesterimplementsIRetryRequester{publicstaticfinalStringURL = CONFIG_URL; @OverridepublicbooleandoRequest(){ConfigFileRequestListenerlistener = newConfigFileRequestListener(URL); listener.setListener(newConfigFileRequestListener.ExtraRequestListener(){@Overridepublicvoidcall(Stringkey){LocalBroadcastManager.getInstance(BaseApplicationProxy.getApplicationContext()) .sendBroadcast(newIntent(KeyConstants.AppBoardcast.INTENT_ACTION_REMOTE_CONFIG))} }); API.requestXnTipConfig(listener); returntrue} } //注册此类的业务接口RetryRequestMonitor.registRetryApi(XNTipConfigRequester.URL, newConfigRequester());

五、未来规划

  1. 新增下载功能

  2. 补全后续的Http协议的method

  3. 根据热度新增响应式编程的操作符

  4. 根据腾讯mars那套重试请求理论实现优化

  5. 新增通用业务性需求的接口和功能

  6. 等待有缘人

六、Q&A

  1. "如何实现多个拦截器的操作"

    这个可以通过如下代码实现:

 .filter(newMultipleResponseFilter().addFilter(newLoginFilter()))
  1. "如何转换响应数据到我自己特定的业务数据格式"

    程序届,多加一层封装可以解决问题,如果再解决不了就继续加一层直到问题解决。

七、其他

  1. 沟通和交流
1. 邮箱 ([email protected]) 

About

Android平台提供强大稳健以及通用的网络通信服务层,我的目标是 —— 简单且完美

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java100.0%