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

优质视频素材网站网站域名注册商标

优质视频素材网站,网站域名注册商标,wordpress 图片超链接,做网站一般用什么配置的电脑Retrofit源码分析小结 简介 Retrofit是对Okhttp网络请求的二次封装#xff0c;通过注解动态代理的方式#xff0c;简化了Okhttp的使用#xff0c;使得通过简单的配置就可以像调用接口一样去请求网络接口#xff1b;除此之外Retrofit还支持RxJava和kotlin的协程 基本…Retrofit源码分析小结 简介 Retrofit是对Okhttp网络请求的二次封装通过注解动态代理的方式简化了Okhttp的使用使得通过简单的配置就可以像调用接口一样去请求网络接口除此之外Retrofit还支持RxJava和kotlin的协程 基本使用 引入依赖库 //网络请求 implementation com.squareup.retrofit2:retrofit:2.9.0//Retrofit基本库 implementation com.squareup.retrofit2:converter-gson:2.1.0//用于将结果转换成对象 implementation com.squareup.retrofit2:adapter-rxjava2:2.3.0//用于转换成RxJava支持的Observer对象 implementation io.reactivex.rxjava2:rxandroid:2.0.1//RxJava对Android的支持 implementation com.squareup.okhttp3:logging-interceptor:3.9.0//Okhttp日志拦截器用于打印接口请求相关log //协程 implementation org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4//Retrofit对Kotlin协程的支持 implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4//协程对Android的支持 implementation(androidx.lifecycle:lifecycle-runtime-ktx:2.6.0-alpha02)//协程对LifeCycle的支持定义一个用于网络请求的服务接口 package com.example.myapplication.retrofitimport android.service.autofill.UserData import io.reactivex.Observable import retrofit2.Call import retrofit2.http.*interface IUserServer {/*** 以GET方式请求*/GET(getUserInfo/)fun getUserInfo(Query(userId) userId: String): CallUserData/*** 以POST方式请求并结合RxJava*/POST(getUserInfo/)FormUrlEncodedfun getUserInfoByRxJava(Field(userId) userId: String): ObservableUserData/*** 结合kotlin协程完成线程切换*/GET(banner/json)suspend fun getUserInfoBySuspend2(Query(userId) userId: String): UserData}创建Retrofit对象和自定义接口代理实现对象 val retrofit Retrofit.Builder().baseUrl(http://www.xxx.com/).addConverterFactory(GsonConverterFactory.create())//支持Gson.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//支持Rxjava.build() val server retrofit.create(IUserServer::class.java)普通发起网络请求 val userInfo server.getUserInfo(1).execute()//同步执行 server.getUserInfo(1).enqueue(object : CallbackUserData {//异步执行override fun onResponse(call: CallUserData, response: ResponseUserData) {//网络请求返回结果}override fun onFailure(call: CallUserData, t: Throwable) {//网络请求错误} })结合RxJava server.getUserInfoByRxJava(1).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object : ObserverUserData {override fun onSubscribe(d: Disposable?) {//网络请求前}override fun onNext(value: UserData?) {//网络请求返回结果}override fun onError(e: Throwable?) {//网络请求错误}override fun onComplete() {//网络请求结束} })结合Kotlin协程 lifecycleScope.launch {val userInfo withContext(Dispatchers.IO) {val userInfo server.getUserInfoBySuspend2(1)//子线程中请求网络}//回到主线程Log.i(testRetrofit, userInfo:$userInfo) }核心源码分析 1. Retrofit.create方法根据注解为我们声明的接口创建动态代理 public T T create(final ClassT service) { validateServiceInterface(service);//检验接口中的方法是否符合要求 return (T)Proxy.newProxyInstance(service.getClassLoader(),new Class?[] {service},new InvocationHandler() {private final Platform platform Platform.get();private final Object[] emptyArgs new Object[0];Overridepublic Nullable Object invoke(Object proxy, Method method, Nullable Object[] args)throws Throwable {// If the method is a method from Object then defer to normal invocation.if (method.getDeclaringClass() Object.class) {return method.invoke(this, args);}args args ! null ? args : emptyArgs;return platform.isDefaultMethod(method)//判断该方法是否用户自定义的? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);//开始解析该方法并返回接口请求对象比如Call、Observable(需结合Rxjava)}}); }在validateServiceInterface方法中如果配置了Retrofit.Builder().validateEagerly(true)会立刻根据注解解析接口中定义的所有方法如果是false则会等待方法调用时才会解析接口中的方法 设置为true的好处是在创建接口代理时就能检查出各个方法配置的注解、返回值等是否正确如果为默认的false则只有在方法调用时才能发现问题 所以建议在debug阶段设置为true便于及早发现问题release阶段设置false以提高性能 private void validateServiceInterface(Class? service) {... ...if (validateEagerly) {Platform platform Platform.get();for (Method method : service.getDeclaredMethods()) {//遍历所有方法根据注解进行解析if (!platform.isDefaultMethod(method) !Modifier.isStatic(method.getModifiers())) {loadServiceMethod(method);}}} }默认实现中接口返回的Call实际上是一个Retrofit中定义的一个接口定义了同步请求和异步请求的方法 public interface CallT extends Cloneable {... .../*** Synchronously send the request and return its response.** throws IOException if a problem occurred talking to the server.* throws RuntimeException (and subclasses) if an unexpected error occurs creating the request or* decoding the response.*/ResponseT execute() throws IOException;/*** Asynchronously send the request and notify {code callback} of its response or if an error* occurred talking to the server, creating the request, or processing the response.*/void enqueue(CallbackT callback);... ... }Call的实现类根据是否有注解SkipCallbackExecutor来决定当有该注解时Call的实现类是OkHttpCall.java里面封装了OkHttp的网络请求如果没有该注解则Call实现类是DefaultCallAdapterFactory.ExecutorCallbackCall这个类仅仅是个代理类真正实现网络请求的还是OkHttpCall 代理类存在的意义是Okhttp进行异步请求返回结果后会先通过Handler将线程切换到主线程再返回结果 final class OkHttpCallT implements CallT {Overridepublic void enqueue(final CallbackT callback) {...okhttp3.Call call;call.enqueue(new okhttp3.Callback() {Overridepublic void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {}Overridepublic void onFailure(okhttp3.Call call, IOException e) {callFailure(e);}}...}Overridepublic ResponseT execute() throws IOException {okhttp3.Call call;...return parseResponse(call.execute());} }static final class ExecutorCallbackCallT implements CallT {final Executor callbackExecutor;//在主线程执行实现类是Platform.Android.MainThreadExecutorfinal CallT delegate;//这个实现类是OkHttpCallOverridepublic void enqueue(final CallbackT callback) {delegate.enqueue(new CallbackT() {Overridepublic void onResponse(CallT call, final ResponseT response) {callbackExecutor.execute(() - {//切换到主线程执行callback.onResponse(ExecutorCallbackCall.this, response);}});}Overridepublic void onFailure(CallT call, final Throwable t) {//切换到主线程执行callbackExecutor.execute(() - callback.onFailure(ExecutorCallbackCall.this, t));}});}Overridepublic ResponseT execute() throws IOException {return delegate.execute();//在当前所在线程执行} }2. 注解的解析过程和使用 在Retrofit.create方法中loadServiceMethod方法中会先从缓存中查找ServiceMethod对象如果之前有解析过则直接返回如果没有则调用ServiceMethod.parseAnnotations方法返回一个ServiceMethod对象 ServiceMethod? loadServiceMethod(Method method) {ServiceMethod? result serviceMethodCache.get(method);if (result ! null) return result;synchronized (serviceMethodCache) {result serviceMethodCache.get(method);if (result null) {result ServiceMethod.parseAnnotations(this, method);serviceMethodCache.put(method, result);}}return result; }接着会调用RequestFactory.parseAnnotations方法真正进行注解的解析并构建一个HttpServiceMethod对象返回 abstract class ServiceMethodT {static T ServiceMethodT parseAnnotations(Retrofit retrofit, Method method) {//真正解析方法上的注解入口RequestFactory requestFactory RequestFactory.parseAnnotations(retrofit, method);//检查方法返回类型是否符合规范Type returnType method.getGenericReturnType();if (Utils.hasUnresolvableType(returnType)) {throw methodError(method,Method return type must not include a type variable or wildcard: %s,returnType);}if (returnType void.class) {throw methodError(method, Service methods cannot return void.);}return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); }RequestFactory.Builder中解析方法中的注解,可以理解为RequestFactory对象保存着所有请求的数据 final class RequestFactory {static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {return new Builder(retrofit, method).build();}static final class Builder {final Annotation[] methodAnnotations;//方法上的注解final Annotation[][] parameterAnnotationsArray;//方法参数上的注解final Type[] parameterTypes;//参数类型Nullable String httpMethod;//请求的方式get、post等Nullable String relativeUrl;//解析处理后的请求接口Nullable Headers headers;//封装的请求头信息boolean isKotlinSuspendFunction; //用于标记是否kotlin协程中suspend方法...private Headers parseHeaders(String[] headers) {}//解析请求头信息private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) //解析请求方式和接口路径private void parseMethodAnnotation(Annotation annotation) {//解析方法上的注解if (annotation instanceof DELETE) {parseHttpMethodAndPath(DELETE, ((DELETE) annotation).value(), false);} else if (annotation instanceof GET) {parseHttpMethodAndPath(GET, ((GET) annotation).value(), false);} else if (annotation instanceof HEAD) {parseHttpMethodAndPath(HEAD, ((HEAD) annotation).value(), false);} else if (annotation instanceof PATCH) {parseHttpMethodAndPath(PATCH, ((PATCH) annotation).value(), true);} else if (annotation instanceof POST) {parseHttpMethodAndPath(POST, ((POST) annotation).value(), true);} else if (annotation instanceof PUT) {parseHttpMethodAndPath(PUT, ((PUT) annotation).value(), true);}...}private ParameterHandler? parseParameterAnnotation(){//解析参数上注解if (annotation instanceof Path) {} else if (annotation instanceof Query) {} else if (annotation instanceof QueryName) {} else if (annotation instanceof QueryMap) {} else if (annotation instanceof Header) {} else if (annotation instanceof HeaderMap) {} else if (annotation instanceof Field) {} else if (annotation instanceof FieldMap) {} else if (annotation instanceof Body) {}...}} }请求参数的使用是在真正发起网络请求OkhttpCall.createRawCall()方法中创建Okhttp Call对象的时候通过RequestFactory.create方法将所有请求参数封装成OkHttp的Request对象 class OkHttpCall{private okhttp3.Call createRawCall() throws IOException {okhttp3.Call call callFactory.newCall(requestFactory.create(args));return call;} } class RequestFactory{okhttp3.Request create(Object[] args) throws IOException {RequestBuilder requestBuilder new RequestBuilder(httpMethod,baseUrl,relativeUrl,headers,contentType,hasBody,isFormEncoded,isMultipart);if (isKotlinSuspendFunction) {//协程suspen方法会自动在最后面加一个Continuation对象类型参数所以实际请求时要去掉// The Continuation is the last parameter and the handlers array contains null at that index.argumentCount--;}ListObject argumentList new ArrayList(argumentCount);for (int p 0; p argumentCount; p) {argumentList.add(args[p]);handlers[p].apply(requestBuilder, args[p]);}//构建okhttp3.Request对象return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();} }3. RxJava2CallAdapterFactory对Rxjava的支持,将默认返回的的Call对象转换成Observer对象 当调用接口方法的时候通过动态代理默认会调用HttpServiceMethod.CallAdapted的invoke方法而CallAdapted继承自HttpServiceMethod并重写了adapt方法; adapt方法用于封装Call对象并将结果转化成我们定义的接口方法所声明的返回对象 class HttpServiceMethod{Overridefinal Nullable ReturnT invoke(Object[] args) {CallResponseT call new OkHttpCall(requestFactory, args, callFactory, responseConverter);return adapt(call, args);}protected abstract Nullable ReturnT adapt(CallResponseT call, Object[] args);static final class CallAdaptedResponseT, ReturnT extends HttpServiceMethodResponseT, ReturnT {private final CallAdapterResponseT, ReturnT callAdapter;//默认的适配器是通过DefaultCallAdapterFactory动态生成的CallAdapter实现类CallAdapted(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,ConverterResponseBody, ResponseT responseConverter,CallAdapterResponseT, ReturnT callAdapter) {super(requestFactory, callFactory, responseConverter);this.callAdapter callAdapter;//通过RxJava2CallAdapterFactory生成的Rxjava2适配器RxJava2CallAdapter}Overrideprotected ReturnT adapt(CallResponseT call, Object[] args) {return callAdapter.adapt(call);//调用适配器将返回结果转换成我们定义的返回类型比如默认是返回CallRxjava返回的是Observer对象}} }RxJava2CallAdapterFactory在创建时可以选择调用create、createAsync、createWithScheduler(Scheduler)方法进行创建他们的主要区别是create是调用Call.execute方法请求网络也就是在当前线程执行 而createAsync则是调用Call.enqueue方法请求网络也就是异步请求;createWithScheduler可以传入一个Scheduler指定网络请求在哪个线程上执行通过observable.subscribeOn(scheduler)实现 public final class RxJava2CallAdapterFactory extends CallAdapter.Factory {/*** Returns an instance which creates synchronous observables that do not operate on any scheduler* by default.*/public static RxJava2CallAdapterFactory create() {return new RxJava2CallAdapterFactory(null, false);}/*** Returns an instance which creates asynchronous observables. Applying* {link Observable#subscribeOn} has no effect on stream types created by this factory.*/public static RxJava2CallAdapterFactory createAsync() {return new RxJava2CallAdapterFactory(null, true);}public static RxJava2CallAdapterFactory createWithScheduler(Scheduler scheduler) {if (scheduler null) throw new NullPointerException(scheduler null);return new RxJava2CallAdapterFactory(scheduler, false);}Overridepublic CallAdapter?, ? get(Type returnType, Annotation[] annotations, Retrofit retrofit) {...return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,isSingle, isMaybe, false);} }调用RxJava2CallAdapter.adapt方法将Call传入并返回Observer final class RxJava2CallAdapterR implements CallAdapterR, Object {Override public Object adapt(CallR call) {ObservableResponseR responseObservable isAsync? new CallEnqueueObservable(call): new CallExecuteObservable(call);//真正网络请求包装在在这里面...Observable? observable;if (isResult) {observable new ResultObservable(responseObservable);} else if (isBody) {observable new BodyObservable(responseObservable);} else {observable responseObservable;}if (scheduler ! null) {observable observable.subscribeOn(scheduler);}...return observable;} }当外部调用Observer的subscribe方法时后就会立刻执行CallExecuteObservable/CallExecuteObservable里的subscribeActual方法从而调用Call的execute或者enqueue方法实现网络请求 class Observable{public final void subscribe(Observer? super T observer) {subscribeActual(observer);} }final class CallExecuteObservableT extends ObservableResponseT {Override protected void subscribeActual(Observer? super ResponseT observer) {CallT call originalCall.clone();ResponseT response call.execute();//发起网络请求并返回结果if (!call.isCanceled()) {//传递给onNext方法observer.onNext(response);}if (!call.isCanceled()) {terminated true;observer.onComplete();}} }4. GsonConverterFactory网络请求返回结果的处理 在OkHttpCall发起网络请求后会调用parseResponse方法解析返回结果然后通过结果转换器进行转换默认转换器是BuiltInConverters如果配置了GsonConverterFactory则支持转换成我们自定义对象实现类是GsonResponseBodyConverter class OkHttpCall{Overridepublic ResponseT execute() throws IOException {okhttp3.Call call;call getRawCall();...return parseResponse(call.execute());}ResponseT parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody rawResponse.body();...ExceptionCatchingResponseBody catchingBody new ExceptionCatchingResponseBody(rawBody);try {//调用结果转换器进行转换这里会根据声明方法返回值类型选择不同转换器比如返回默认的ResponseBody则使用自带的BuiltInConverters转换返回自定义对象则会使用GsonResponseBodyConverter进行转换T body responseConverter.convert(catchingBody);return Response.success(body, rawResponse);} catch (RuntimeException e) {// If the underlying source threw an exception, propagate that rather than indicating it was// a runtime exception.catchingBody.throwIfCaught();throw e;}} }Retrofit自带的默认转换器BuiltInConverters支持处理返回类型是ResponseBody/Void 或者kotlin的Unit final class BuiltInConverters extends Converter.Factory {/** Not volatile because we dont mind multiple threads discovering this. */private boolean checkForKotlinUnit true;Overridepublic Nullable ConverterResponseBody, ? responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {if (type ResponseBody.class) {return Utils.isAnnotationPresent(annotations, Streaming.class)? StreamingResponseBodyConverter.INSTANCE: BufferingResponseBodyConverter.INSTANCE;}if (type Void.class) {return VoidResponseBodyConverter.INSTANCE;}if (checkForKotlinUnit) {try {if (type Unit.class) {return UnitResponseBodyConverter.INSTANCE;}} catch (NoClassDefFoundError ignored) {checkForKotlinUnit false;}}return null;} }Gson转换器GsonResponseBodyConverter支持自定义对象类型的转换 final class GsonResponseBodyConverterT implements ConverterResponseBody, T {private final Gson gson;private final TypeAdapterT adapter;GsonResponseBodyConverter(Gson gson, TypeAdapterT adapter) {this.gson gson;this.adapter adapter;}Override public T convert(ResponseBody value) throws IOException {JsonReader jsonReader gson.newJsonReader(value.charStream());try {return adapter.read(jsonReader);} finally {value.close();}} }5. 对kotlin协程的支持 判断是否kotlin suspend方法在kotlin中suspend修饰的方法在编译成字节码并反编译成java代码后会发现方法参数里最后会多一个Continuation类型的参数用于对协程的支持 kotlin字节码反编译后的java代码 public interface IUserServer {... ...GET(banner/json)NullableObject getUserInfoBySuspend2(Query(userId) NotNull String var1, NotNull Continuation var2); }Retrofit在RequestFactory解析方法参数时做判断 class RequestFactory{class Builder{boolean isKotlinSuspendFunction;private Nullable ParameterHandler? parseParameter(int p, Type parameterType, Nullable Annotation[] annotations, boolean allowContinuation) {try {if (Utils.getRawType(parameterType) Continuation.class) {//判断是否suspend协程方法isKotlinSuspendFunction true;return null;}} catch (NoClassDefFoundError ignored) {}}} }根据方法返回类型返回不同的HttpServiceMethod当suspend方法直接返回自定义数据类型时(比如 UserData)返回的是SuspendForBody对象; 如果方法返回的是Response,则返回SuspendForResponse对象,这两个返回对象其实差不多只是返回类型不一样 class HttpServiceMethod{static ResponseT, ReturnT HttpServiceMethodResponseT, ReturnT parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {boolean isKotlinSuspendFunction requestFactory.isKotlinSuspendFunction;boolean continuationWantsResponse false;if (getRawType(responseType) Response.class responseType instanceof ParameterizedType) {// Unwrap the actual body type from ResponseT.responseType Utils.getParameterUpperBound(0, (ParameterizedType) responseType);continuationWantsResponse true;}if (continuationWantsResponse) {//返回ResponseUserData类型结果return (HttpServiceMethodResponseT, ReturnT)new SuspendForResponse(requestFactory,callFactory,responseConverter,(CallAdapterResponseT, CallResponseT) callAdapter);} else {//返回UserData类型结果return (HttpServiceMethodResponseT, ReturnT)new SuspendForBody(requestFactory,callFactory,responseConverter,(CallAdapterResponseT, CallResponseT) callAdapter,continuationBodyNullable);}} }继续以SuspendForBody分析,在adapt方法被调用时Retrofit会调用kotlin的扩展方法await/awaitNullable static final class SuspendForBodyResponseT extends HttpServiceMethodResponseT, Object {private final CallAdapterResponseT, CallResponseT callAdapter;private final boolean isNullable;Overrideprotected Object adapt(CallResponseT call, Object[] args) {call callAdapter.adapt(call);//这里是默认的DefaultCallAdapterFactorys产生的Adapter//获取suspend最后一个参数用于协程ContinuationResponseT continuation (ContinuationResponseT) args[args.length - 1];try {//调用Retrofit使用kotlin对Call类的扩展方法return isNullable //返回的对象是否可为空? KotlinExtensions.awaitNullable(call, continuation): KotlinExtensions.await(call, continuation);} catch (Exception e) {return KotlinExtensions.suspendAndThrow(e, continuation);}} }kotlin对Call方法扩展解析协程方法中会异步发起网络请求并返回结果当结束后会恢复到父协程地方继续执行 //这里是suspend方法执行时协程会挂起 suspend fun T : Any CallT.await(): T {return suspendCancellableCoroutine { continuation -continuation.invokeOnCancellation {cancel()//设置协程取消时的回调调用Call的cancel方法}enqueue(object : CallbackT {//调用Call方法异步请求网络override fun onResponse(call: CallT, response: ResponseT) {if (response.isSuccessful) {val body response.body()if (body null) {val invocation call.request().tag(Invocation::class.java)!!val method invocation.method()val e KotlinNullPointerException(Response from method.declaringClass.name . method.name was null but response body type was declared as non-null)continuation.resumeWithException(e)//恢复协程并抛出异常} else {continuation.resume(body)//恢复协程并返回结果}} else {//恢复协程并抛出异常continuation.resumeWithException(HttpException(response))}}override fun onFailure(call: CallT, t: Throwable) {continuation.resumeWithException(t)//恢复协程并抛出异常}})} }小结 Retrofit实现原理 答Retrofit通过Create方法创建我们自定义接口实例时会为我们接口创建一个动态代理当调用接口方法的时候会先根据配置的注解解析这个方法的信息包括请求路径、请求参数、返回值等等并把解析后的信息封装成对象并缓存起来 然后根据方法的信息封装成一个Call对象这个Call对象的默认实现类是OkHttpCall内部是通过Okhttp发起同步或者异步网络请求 然后调用Call的execute同步或者enqueue异步方法进行网络请求返回结果后会调用转换器对结果进行转换默认是返回ResponseBody也可以通过配置Gson转换器转成我们自定义类型 如果需要对RxJava支持返回Observer对象则是需要配置一个Rxjava的CallAdapter在适配器中将Call对象封装到Observer对象中并返回当Observer的subscribe方法调用时会触发Call的网络请求操作最后通过RxJava对结果分发 如果需要对kotlin协程支持Retrofit在对方法解析时会判断是否suspend方法如果是的话会执行Call的kotlin扩展方法扩展方法也是suspend类型在扩展方法中会挂起协程通过Call对象执行网络请求操作最后通过Continuation.resume方法恢复协程到父协程调用的地方 静态代理和动态代理的区别 答静态代理是指在代码编写的时候代理类和被代理类的关系就确定了编译后也会生成代理类字节码文件而动态代理是指在运行时期动态的通过反射方式为被代理的接口生成一个代理类当调用这个接口的方法时都会进入InvokeHandler的invoke方法中从而实现对这个被代理接口的统一处理 静态代理适合被代理的类比较少的情况如果代理的类比较多而且不需要做统一处理则动态代理方便很多 Retrofit返回结果是如何切换到主线程的 答Retrofit会通过系统属性来判断是否Android平台如果是Android平台则会创建一个执行器内部会创建一个主线程Looper的Handler当网络请求结束返回结果时会封装一个Runnable通过这个主线程Handler去执行从而切换到主线程 或者通过RxJava或者协程进行线程切换 对Kotlin协程(suspend方法)的支持 判断协程方法条件判断方法最后一个参数是否是Continuation.class类型Retrofit使用kotlin对Call类扩展了suspend方法在suspend方法中执行Call的网络请求然后通过Continuation的resume方法恢复协程挂起并返回结果什么时候切回主线程的在协程中调用continuation.resume方法后会自动回到它父协程所在线程也就是主线程继续执行
http://www.w-s-a.com/news/497555/

相关文章:

  • 宣传电脑的网站开发运动网站建设教程
  • 网站建设公司都会有哪些花销做网站公司商丘
  • 网站风格有哪些软件定制和开发
  • 公司网络维护具体做什么河南网站推广优化公司哪家好
  • 中学生制作的网站常平哪里有招计算机网站开发的
  • 原创网站模版苏州响应式网站建设
  • 做海报在哪个网站可以找素材网址申请注册方法
  • 网站建设分哪些类别别人做的网站不能用
  • 做网站网站会怎么样全国高校校园网站联盟建设
  • 整站下载器 做网站地图地产项目网站设计
  • 创意设计网站公司手机wap网站建设多少钱
  • 甘肃省第八建设集团公司网站seo高级优化方法
  • 精美的商城网站介绍最多人用的wordpress子主题
  • 检察门户网站建设情况俄外长抵达北京
  • 老电脑做网站服务器网站在线留言如何做
  • 南宁广告公司网站建设小程序源码破解
  • 沛县做网站xlec网站建设开发方式包括哪些方面
  • 山西网站建设 哪家好四川城乡和建设厅网站
  • 有瀑布流的网站小型商城网站
  • 百石网怎么做网站二次开发软件
  • 网站域名是什么东西制作网页哪家好
  • 合肥网站建设团队简述网站内容管理流程
  • 网站广告是内容营销吗wordpress增加背景图片
  • 网站建设技术jsp课程设计响应式布局网站开发
  • 东莞网站排名优化seo套路网站怎么做的
  • 我做网站网络建站一般多少钱
  • 如何快速提升网站关键词排名房地产网站开发毕业设计
  • 做网站 提交源码 论坛sem分析是什么意思
  • 网站建设与部署阿里云大学百度付费推广有几种方式
  • 作品集怎么做网站个人简历模板免费下