当前位置: 首页 > news >正文

Android-OkHttp与Retrofit学习总结

OkHttp核心机制与工作流程

面试官​:能简单介绍一下OkHttp的工作流程吗?
候选人​:
好的,OkHttp的工作流程大致可以分为几个步骤。首先,我们需要创建一个OkHttpClient实例,通常会用建造者模式来配置参数,比如设置连接超时、读取超时,或者添加全局的拦截器。这一步相当于准备好了一个“网络请求工具箱”。

接下来,我们会用Request.Builder来构建具体的请求对象Request,设置URL、请求方法(比如GET或POST)、请求头等信息。这就像填写一张快递单,告诉OkHttp要把请求发到哪里、用什么方式发送。

然后,通过client.newCall(request)得到一个Call对象,它代表一个准备好的请求。这时候可以选择同步或异步执行。

  • 同步请求​:直接调用call.execute(),这个方法会阻塞当前线程,直到拿到服务器的响应,适合在子线程中使用。
  • 异步请求​:调用call.enqueue(callback),把请求交给后台线程池处理,通过回调返回结果,这样可以避免阻塞主线程,适合在Android中做网络请求。

面试官​:听说OkHttp的拦截器链很重要,能讲讲它的作用吗?
候选人​:
拦截器链是OkHttp最核心的设计之一,有点像流水线上的工人,每个拦截器负责处理一个特定任务。当请求发起时,这些拦截器会按顺序对请求进行处理,最后再逆序处理响应。例如:

  1. 重试拦截器​(RetryAndFollowUpInterceptor):如果请求失败了,它会自动重试或者处理重定向(比如遇到301/302状态码)。
  2. 桥接拦截器​(BridgeInterceptor):负责补充一些必要的请求头,比如Content-TypeCookie,让请求更符合HTTP协议规范。
  3. 缓存拦截器​(CacheInterceptor):根据HTTP缓存头判断是否使用本地缓存,减少重复请求。
  4. 连接拦截器​(ConnectInterceptor):管理TCP连接,复用连接池里的空闲连接,避免每次请求都重新握手。
  5. 网络拦截器​(自定义的addNetworkInterceptor):可以在这里打印请求日志,或者修改原始请求数据。
  6. 请求服务拦截器​(CallServerInterceptor):真正发送请求到服务器并读取响应数据。

面试官​:OkHttp是怎么优化性能的?比如连接复用?
候选人​:
OkHttp通过连接池​(ConnectionPool)来复用连接,这有点像“共享单车”的概念。当一个请求完成后,连接并不会立即关闭,而是被保留在池子里一段时间(默认5分钟),如果接下来有相同目标地址的请求,就可以直接复用这个连接,省去了TCP握手和TLS协商的时间,这对高频请求的场景性能提升非常明显。

另外,Dispatcher负责调度异步请求的线程池,默认支持最多64个并发请求,同时每个Host最多允许5个并发,这样既保证了效率,又防止了资源被耗尽。

面试官​:如果要添加一个日志拦截器记录请求耗时,该怎么做?
候选人​:
可以自定义一个拦截器,在intercept方法里记录请求开始和结束的时间。比如:

public class LoggingInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();long startTime = System.nanoTime();System.out.println("发送请求: " + request.url());Response response = chain.proceed(request);long duration = (System.nanoTime() - startTime) / 1e6;System.out.println("收到响应: " + response.code() + ", 耗时: " + duration + "ms");return response;}
}

然后通过OkHttpClient.Builder添加这个拦截器:

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();

这样每次请求都会输出日志,方便调试耗时和问题排查。

面试官​:同步和异步请求在底层是怎么处理的?
候选人​:

  • 同步请求​:直接在当前线程执行,由Dispatcher标记为正在运行的任务,执行完成后移除。
  • 异步请求​:会被封装成AsyncCall(本质是一个Runnable),交给Dispatcher的线程池执行。线程池默认大小是64,所以即使有大量请求,也不会无限制创建线程,避免资源竞争。

另外,OkHttp内部会优先复用空闲的线程,而不是频繁创建和销毁,这也能减少性能开销。

面试官​:如果让你设计一个网络库,会参考OkHttp的哪些设计?
候选人​:
我会借鉴它的拦截器链机制,把不同职责的功能模块化,比如日志、缓存、重试等,每个模块只关注自己的逻辑,方便扩展和维护。其次是连接池的设计,复用连接能显著提升性能。最后是建造者模式,用链式调用配置参数,代码更清晰,比如:

new MyHttpClient.Builder().timeout(10, TimeUnit.SECONDS).enableCache(true).build();

这种设计对开发者更友好,避免了冗长的构造函数参数列表。


OkHttp核心机制通俗解析

面试官​:你对OkHttp的拦截器链了解吗?能说说它的作用吗?
候选人​:
OkHttp的拦截器链就像一条流水线,每个环节(拦截器)负责处理特定任务。比如,第一个工人(拦截器)检查包裹是否需要重试发货(比如网络断了自动重试),第二个工人给包裹贴上快递单(补充请求头),第三个工人检查仓库有没有现成的包裹(缓存拦截器),第四个工人直接调用快递公司发件(连接服务器)。

这样设计的好处是每个环节分工明确,比如我要加一个功能(比如打印日志),只需要在流水线里插一个“日志工人”,不用动其他代码,扩展性特别好。


面试官​:连接池听起来很高大上,实际有什么用?
候选人​:
连接池其实就像“共享单车”。第一次访问服务器时,OkHttp会“扫码开锁”(TCP三次握手+TLS握手),用完后不立即关锁,而是把车放回停车场(连接池),保留5分钟。如果5分钟内有人要访问同一个服务器(比如同一个域名),就直接骑这辆车,省去了重复开锁的时间,特别适合高频请求的场景,比如APP里反复调用同一个API接口。


面试官​:建造者模式在OkHttp里是怎么体现的?
候选人​:
举个例子,就像去奶茶店点单。如果奶茶有20种配料(超时时间、拦截器、代理等),用传统方式点单得说:“我要一杯奶茶,加10秒超时、30秒读取超时、加珍珠、加椰果...” 听着就头疼。而建造者模式就像店员给你一张菜单,你只需要勾选需要的选项:

OkHttpClient client = new OkHttpClient.Builder()  // 拿到菜单.connectTimeout(10, TimeUnit.SECONDS)         // 勾选“连接超时10秒”.addInterceptor(new LoggingInterceptor())     // 勾选“加日志”.build();                                     // 下单

这样代码清晰,避免参数爆炸,也方便后期维护(比如调整超时时间不用翻构造函数文档)。


面试官​:如果让你设计一个拦截器统计网络请求耗时,你会怎么做?
候选人​:
我会在拦截器里记录请求开始的时间戳,等拿到服务器响应后,用当前时间减去开始时间,就是耗时。比如:

public class TimerInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {long startTime = System.currentTimeMillis();  // 开始时间Request request = chain.request();Response response = chain.proceed(request);   // 放行请求long cost = System.currentTimeMillis() - startTime;Log.d("网络耗时", request.url() + " : " + cost + "ms");return response;}
}

然后把这个拦截器加到OkHttpClient里,就像给所有网络请求装了个计时器,上线后能快速发现哪些接口性能差。


面试官​:连接池的默认配置是什么?哪些情况需要调整?
候选人​:
默认最多保留5个空闲连接,存活5分钟。如果我们的APP需要频繁访问多个不同域名(比如同时调支付接口、地图接口、用户中心接口),可以适当调大连接数,比如改成10个:

ConnectionPool pool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
OkHttpClient client = new OkHttpClient.Builder().connectionPool(pool).build();

但也不能无脑调大,连接数太多会占用内存,一般要根据实际场景测试。


面试官​:为什么OkHttpClient建议全局单例?
候选人​:
主要有两个原因:

  1. 连接池复用​:如果每个请求都new一个OkHttpClient,连接池就形同虚设,每次都要重新建连,性能大打折扣。
  2. 资源开销​:每个Client都有自己的线程池、连接池,多实例会导致内存和CPU浪费,甚至引发线程数爆炸的问题。

这就像公司里每个部门都自己买打印机,不仅贵,还浪费电。不如整个公司共用几台打印机(全局单例),既省钱又高效。


基础知识扩展:

OkHttp三大核心机制

一、拦截器链机制:责任链模式的完美实践

1. 设计思想与工作原理

OkHttp的拦截器链是其架构的“灵魂”,采用责任链模式将复杂网络请求拆解为多个独立模块,每个拦截器(Interceptor)专注单一职责,通过链式传递实现高效协作。

  • 请求处理流程​:从第一个拦截器开始,依次对请求进行加工(如添加Header、压缩数据),最终由CallServerInterceptor发送到服务器。
  • 响应处理流程​:响应从最后一个拦截器反向传递,每个拦截器可对响应进行后处理(如解压数据、缓存结果)。

源码流程示例​:

// RealCall.java
Response getResponseWithInterceptorChain() {List<Interceptor> interceptors = new ArrayList<>();interceptors.add(new RetryAndFollowUpInterceptor()); // 重试与重定向interceptors.add(new BridgeInterceptor());         // 补充协议头interceptors.add(new CacheInterceptor());          // 缓存处理interceptors.add(new ConnectInterceptor());        // 连接管理interceptors.add(new CallServerInterceptor());     // 发送请求// 构建责任链Interceptor.Chain chain = new RealInterceptorChain(interceptors, ...);return chain.proceed(request);
}
2. 关键拦截器详解
拦截器核心职责应用场景示例
RetryAndFollowUpInterceptor处理请求失败后的重试逻辑(如网络波动),自动处理3xx重定向响应网络不稳定时自动重试,避免手动处理
BridgeInterceptor补充协议头(如Content-LengthUser-Agent),处理Cookie将开发者友好的Request转为HTTP标准请求
CacheInterceptor根据缓存策略(如Cache-Control)返回缓存或写入新缓存实现离线缓存,减少重复请求
ConnectInterceptor复用连接池中的TCP连接或创建新连接,完成TLS握手提升高频请求性能
CallServerInterceptor通过Socket发送请求数据,读取服务器响应实际网络IO操作
3. 自定义拦截器实战

场景​:需要全局监控请求耗时和错误日志。
实现代码​:

public class MetricsInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();long startNs = System.nanoTime();try {Response response = chain.proceed(request);long costMs = (System.nanoTime() - startNs) / 1_000_000;logSuccess(request.url(), response.code(), costMs);return response;} catch (IOException e) {long costMs = (System.nanoTime() - startNs) / 1_000_000;logError(request.url(), e, costMs);throw e;}}private void logSuccess(HttpUrl url, int code, long costMs) {System.out.printf("请求成功: %s | 状态码: %d | 耗时: %dms\n", url, code, costMs);}private void logError(HttpUrl url, Exception e, long costMs) {System.err.printf("请求失败: %s | 错误: %s | 耗时: %dms\n", url, e.getMessage(), costMs);}
}

使用方式​:

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new MetricsInterceptor()) // 全局生效.build();
4. 高频面试问题

Q1: 应用拦截器(addInterceptor)和网络拦截器(addNetworkInterceptor)有何区别?
A:

  • 应用拦截器​:在CacheInterceptor之前执行,只触发一次,无法感知重定向和重试。
    适用场景​:添加全局Header、请求日志统计。
  • 网络拦截器​:在ConnectInterceptor之后执行,可能因重试触发多次。
    适用场景​:修改原始请求数据(如加密Body)、网络层日志。

Q2: 拦截器执行顺序是怎样的?
A: 顺序为:用户自定义应用拦截器 → Retry → Bridge → Cache → Connect → 用户自定义网络拦截器 → CallServer。


二、连接池:性能优化的基石

1. 核心机制解析
  • 复用原理​:对相同Address(相同域名、端口、代理等)的请求复用TCP连接,跳过三次握手(节省约200ms)和TLS握手(节省约300ms)。
  • 默认配置​:
    • 最大空闲连接数:5
    • 空闲连接存活时间:5分钟
  • 配置调优​:
    // 调大连接池参数(适合高频请求场景)
    ConnectionPool pool = new ConnectionPool(10,     // 最大空闲连接数2,      // 存活时间TimeUnit.MINUTES
    );
    OkHttpClient client = new OkHttpClient.Builder().connectionPool(pool).build();
2. 连接复用流程图解
   Request 1 → 创建连接 → 完成请求 → 放入连接池(保持5分钟)Request 2 → 检查连接池 → 找到相同Address连接 → 复用Request 3 → 检查连接池 → 无可用连接 → 创建新连接
3. 维护机制
  • 清理策略​:后台线程每隔cleanupInterval(默认100ms)检查一次,关闭超过最大空闲数或超时的连接。
  • 核心源码片段​:
    // ConnectionPool.java
    void cleanup(long now) {// 遍历所有连接,标记过期连接for (RealConnection connection : connections) {if (connection.idleAtNs + keepAliveDurationNs < now) {expiredConnections.add(connection);}}// 关闭过期连接for (RealConnection connection : expiredConnections) {connections.remove(connection);}
    }
4. 高频面试问题

Q: OkHttp如何判断两个请求可以复用同一个TCP连接?
A: 通过Address的五个维度匹配:

  1. 域名(host)
  2. 端口(port)
  3. 代理配置(Proxy)
  4. TLS配置(如证书、TLS版本)
  5. 路由信息(如DNS解析后的IP地址)

三、建造者模式:灵活配置的利器

1. 设计动机
  • 问题​:传统构造方法参数爆炸(OkHttpClient有20+配置项),难以维护。
  • 解决​:通过链式调用逐步设置参数,提升可读性和扩展性。
2. OkHttp中的建造者模式
  • OkHttpClient配置示例​:
    OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS)   // 连接超时.readTimeout(30, TimeUnit.SECONDS)      // 读取超时.writeTimeout(30, TimeUnit.SECONDS)     // 写入超时.addInterceptor(new LoggingInterceptor()) // 拦截器.connectionPool(customPool)             // 自定义连接池.build();
  • Request构建示例​:
    Request request = new Request.Builder().url("https://api.example.com/data").header("Authorization", "Bearer token").post(RequestBody.create("{\"key\":\"value\"}", JSON)).build();
3. 源码实现剖析
  • Builder类结构​:
    public class OkHttpClient {public static class Builder {private int connectTimeout;private int readTimeout;private List<Interceptor> interceptors = new ArrayList<>();public Builder connectTimeout(long timeout, TimeUnit unit) {this.connectTimeout = unit.toMillis(timeout);return this; // 返回this实现链式调用}public OkHttpClient build() {return new OkHttpClient(this);}}
    }
4. 高频面试问题

Q: 为什么OkHttpClient要设计成不可变对象?
A:

  • 线程安全​:配置一旦创建无法修改,多线程环境下无需同步。
  • 明确语义​:避免运行时动态修改配置导致的意外行为。
  • 性能优化​:可缓存已构建的Client实例,减少重复初始化开销。

Retrofit核心技术解析

1. 注解机制:用“标签”描述请求

面试官​:能说说Retrofit的注解是怎么用的吗?
候选人​:
Retrofit的注解就像给网络请求贴标签。比如我想定义一个获取用户信息的接口,可以这样写:
@GET("users/{username}")” 这个标签告诉Retrofit:“这是个GET请求,路径是users/用户名”。而方法参数上的“@Path("username")”就像把用户名填到路径的空格里。
这些标签让代码变得像说明书一样清晰,Retrofit看到这些标签就知道怎么组装请求,开发者只需要关心业务逻辑,不用操心网络请求的细节。


2. 动态代理:背后的“智能秘书”​

面试官​:听说Retrofit用了动态代理,这是什么意思?
候选人​:
动态代理就像雇了一个聪明的秘书。当调用retrofit.create(UserService.class)时,Retrofit会悄悄生成一个实现了接口的代理对象。
比如我调用userService.getProfile("Alice"),秘书会立刻行动:

  1. 查看方法上的标签(比如@GET确定请求方式)
  2. 把参数"Alice"塞到路径的对应位置
  3. 把完整的请求交给OkHttp去执行
    整个过程完全自动化,开发者就像老板一样,只需要下指令,秘书会处理所有杂事。

3. 与OkHttp合作:黄金搭档的分工

面试官​:Retrofit和OkHttp是什么关系?
候选人​:
它们就像设计师和工程师的完美组合。Retrofit负责设计蓝图——用注解定义请求结构,OkHttp负责实际施工——处理网络连接、数据收发这些脏活累活。
比如要监控网络请求,可以给OkHttp装个“行车记录仪”(添加日志拦截器):
client.addInterceptor(new HttpLoggingInterceptor())
这样所有经过Retrofit的请求都会自动记录日志,就像给每个网络请求配了个黑匣子,调试的时候一目了然。


4. 数据转换:自动翻译官

面试官​:怎么把服务器返回的JSON转成对象?
候选人​:
Retrofit有个“翻译官团队”(转换器工厂)。比如用Gson转换器时:
addConverterFactory(GsonConverterFactory.create())
服务器返回的JSON数据会被自动解析成Java对象,就像有个翻译实时把外语合同翻成中文。如果哪天要换XML格式,只需要换个翻译官(换个转换器),业务代码完全不用改。


5. 生命周期管理:避免“僵尸请求”​

面试官​:怎么防止Activity销毁后请求还在跑?
候选人​:
这就像订外卖后突然要出门,得记得取消订单。用Retrofit时,可以在Activity的onDestroy里调用:
call.cancel()
如果是用RxJava,可以用一个“收纳盒”(CompositeDisposable)管理所有请求:
disposables.add(request)
当页面销毁时清空收纳盒:“disposables.clear()”,这样所有进行中的请求都会自动取消,避免浪费资源和内存泄漏。


6. 缓存机制:省流量神器

面试官​:怎么让APP没网时也能显示数据?
候选人​:
Retrofit通过OkHttp支持智能缓存。就像给APP配了个临时储物柜,配置好缓存大小后:
client.cache(new Cache(directory, size))
服务器响应如果带着“Cache-Control: max-age=60”这样的头,OkHttp就会把数据存1分钟。下次同样请求会优先从储物柜取数据,既省流量又让APP更流畅,特别是在网络不稳定时体验更好。


7. 适配RxJava:异步流水线

面试官​:怎么用Retrofit配合RxJava?
候选人​:
加个“适配插座”就行:
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
这样接口方法可以直接返回RxJava的Observable,用操作符处理异步请求就像组装流水线:
.subscribeOn(Schedulers.io())”在后台线程发请求
.observeOn(AndroidSchedulers.mainThread())”在主线程更新UI
配合retryWhen()还能实现自动重试,像给网络请求加了保险机制。


OkHttp与Retrofit的黄金搭档关系

面试官​:能说说OkHttp和Retrofit是怎么配合工作的吗?它们各自负责什么?
候选人​:
可以啊!这俩库的关系就像“厨师”和“服务员”的合作。

Retrofit是服务员​:
它的核心工作是“把复杂的点餐流程标准化”。比如你走进餐厅,服务员会给你菜单(定义接口),你只需要说“我要一份牛排七分熟”(用@GET注解描述请求),服务员就能把你的需求翻译成厨房能理解的指令(生成HTTP请求)。

  • 服务员做的事​:
    • 用注解解析你的需求(@GET@POST@Path等)。
    • 把Java对象转成网络请求参数(比如把User对象转成JSON)。
    • 把服务器返回的数据转回Java对象(比如JSON转成List<User>)。

OkHttp是后厨的厨师​:
它负责实际“烹饪”和“送餐”。服务员把订单交给厨师后,厨师会处理所有底层细节:

  • 厨师做的事​:
    • 管理火候(TCP连接池复用,省去重复握手)。
    • 处理突发情况(比如网络断了自动重试)。
    • 控制上菜速度(超时设置、流量控制)。
    • 记录每道菜的日志(通过拦截器打印请求详情)。

面试官​:那它们具体是怎么“传纸条”的?比如一个网络请求的流程?
候选人​:
举个实际例子吧!假设我们调用一个获取用户信息的接口:

// Retrofit接口定义(服务员记下订单)
@GET("users/{id}")
Call<User> getUser(@Path("id") String userId);// 调用代码(顾客下单)
Call<User> call = service.getUser("123");
call.enqueue(...);

具体流程​:

  1. 服务员接单​:
    Retrofit看到@GET("users/{id}"),知道这是一个GET请求,路径是users/123(把{id}替换成"123")。

  2. 服务员写菜单​:
    Retrofit把方法参数、注解信息打包成一个“订单”(构建OkHttp的Request对象)。

  3. 订单递到后厨​:
    Retrofit调用OkHttpClient(厨师)来执行这个Request

  4. 厨师开始烹饪​:
    OkHttp检查是否有现成的连接(连接池复用),如果没有就建立新连接。
    发送请求,处理重试、缓存(比如服务器返回304就用本地缓存)。

  5. 出菜和回传​:
    厨师把做好的菜(服务器响应)交给服务员,Retrofit用GsonConverter把JSON数据转成User对象(就像把生牛排煎成七分熟)。

  6. 服务员上菜​:
    Retrofit通过Callback把最终结果返回给调用方,或者抛异常提示“牛排煎糊了”(网络错误)。


面试官​:如果我需要定制化功能,比如加请求头、打印日志,该用谁?
候选人​:
这得看需求属于哪个环节:

  • Retrofit的领域​(服务员能处理的):

    • 全局添加请求头(比如@Headers("Authorization: token"))。
    • 统一处理响应(比如用CallAdapterCall<User>转成LiveData<User>)。
  • OkHttp的领域​(需要厨师配合的):

    • 打印网络请求日志(用HttpLoggingInterceptor,这是OkHttp的拦截器)。
    • 缓存策略(配置OkHttp的Cache对象)。
    • 模拟网络延迟(写个拦截器故意延迟响应,方便测试)。

举个例子,如果要给所有请求加一个User-Agent头:

// 用OkHttp的拦截器(厨师的工具)
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(chain -> {Request request = chain.request().newBuilder().header("User-Agent", "MyApp/1.0").build();return chain.proceed(request);}).build();// 把这个厨师交给服务员
Retrofit retrofit = new Retrofit.Builder().client(client).baseUrl("https://api.example.com/").build();

这相当于告诉厨师:“每道菜出锅前,记得贴个‘MyApp’的标签”。


面试官​:为什么Retrofit不自己处理网络请求,非要依赖OkHttp?
候选人​:
这就像餐厅为什么要分服务员和厨师——专业的人做专业的事

  • Retrofit的强项是简化接口定义数据转换,让代码更优雅。
  • OkHttp的强项是高性能网络通信,它解决了连接池、缓存、拦截器这些复杂问题。

如果Retrofit自己实现网络层,就得重复造轮子,而且很难做到OkHttp多年的优化积累(比如HTTP/2支持、WebSocket等)。两者的分工让开发者既能享受Retrofit的简洁,又能用OkHttp的强悍性能。


面试官​:能举个比喻总结它们的关系吗?
候选人​:
可以!Retrofit就像“外卖平台”,OkHttp就像“快递小哥”。

  • 你通过外卖平台(Retrofit)下单,选好餐厅、菜品(定义接口)。
  • 平台把订单打包成标准化格式(生成Request)。
  • 快递小哥(OkHttp)根据订单内容,选择最优路线(连接复用),处理突发问题(重试、超时),最后把餐送到你手里(返回Response)。

没有快递小哥,外卖平台就是个空壳;没有平台,小哥得手动接电话记地址。两者结合,才能让你“躺着点外卖”。

相关文章:

  • 事务基础概念
  • HarmonyOS优化应用内存占用问题性能优化四
  • 奇好 PDF安全加密 + 自由拆分合并批量处理 OCR 识别
  • 常见嵌入式软件架构
  • JavaSE常用API之Runtime类:掌控JVM运行时环境
  • UDP和TCP示例程序
  • 机器学习算法-sklearn源起
  • 【JVM 05-JVM内存结构之-堆】
  • RabbitMQ 应用
  • Python知识图谱工具全解析
  • SDL2常用函数:SDL_LoadBMP 数据结构详解与示例
  • ABP VNext + Orleans:Actor 模型下的分布式状态管理最佳实践
  • 基于Java+MySQL 实现(Web)网络考试系统
  • C++篇——C++11的更新内容
  • github开源版pymol安装(ubuntu22.04实战版)
  • 最宽温度范围文本格式PT1000分度表-200~850度及PT1000铂电阻温度传感器计算公式
  • BLIP论文笔记
  • 软件名称:系统日志监听工具 v1.0
  • 二、ZooKeeper 集群部署搭建
  • HTMLUnknownElement的使用
  • 临夏金属装饰网站建设/怎么发帖子做推广
  • php wap网站实现滑动式数据分页/数字营销服务商seo
  • 网站建设 2018/百度搜索推广收费标准
  • 化州手机网站建设公司/哪个网站百度收录快
  • html5网站开发书籍/东莞网站推广优化网站
  • 网站建设建站网易互客/武汉网站seo服务