Retrofi2源码解析——动态代理和注解的实战应用

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Retrofit2源码解析——动态代理和注解的实战应用!本想年前搞定这篇文章,接下来进入面试题的整理。但是年前确实有些心不在焉,拖拖拉拉就到了现在才完成。

Retrofit2源码解析——动态代理和注解的实战应用!
本想年前搞定这篇文章,接下来进入面试题的整理。但是年前确实有些心不在焉,拖拖拉拉就到了现在才完成。唉,这该死的拖延症~
前面讲到了代理和注解的基本应用,感觉有一点枯燥无味。空洞的理论脱离了实际应用就会变得毫无意义。So,今天要写的就是将动态代理和注解完美应用的Android网络请求框架的分析——Retrofit2。

关于OkHttp

稍微了解Retrofit的就会知道Retrofit也是使用的OkHttp作为请求框架,Retrofit只是对其进行封装,更加方便使用。我们看下OkHttp的请求方法:

OkHttpClient client = new OkHttpClient();
//创建一个Request
Request request = new Request.Builder()
        .get()
        .url(url)
        .build();
//通过client发起请求
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
        // String str = response.body().string();
        }

    }
});

这是关于OkHttp的简单使用,这里面执行了一个get请求。前面也说了Retrofit是对OkHttp的封装,那么Retrofit最后也会调用这些方法。不信?咱往下看。

1. Retrofit的创建

Retrofit通过Builder来创建,下面是简单创建:

new Retrofit.Builder()
    .baseUrl(Constant.BASE_URL)
    .addConverterFactory(GsonConverterFactory.create()) // 添加Gosn转换器
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 添加RxJava2支持
    .build();

来看下build()过程:

public Builder() {
    // 获取当前的平台,我们这里是Android
    this(Platform.get());
}
Builder(Platform platform) {
    this.platform = platform;
    // Add the built-in converter factory first. This prevents overriding its behavior but also
    // ensures correct behavior when using converters that consume all types.
    // 默认的转换器
    converterFactories.add(new BuiltInConverters());
}
public Retrofit build() {
    // 检查baseUrl,这里并没有对""这种情况判断
    if (baseUrl == null) {
      throw new IllegalStateException("Base URL required.");
    }
    
    // 如果没有传入OkHttpClient,设置默认的OkHttpClient
    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
      callFactory = new OkHttpClient();
    }

    // 也是默认的执行器,根据平台来的,这里的是Android的平台
    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
      // MainThreadExecutor
      callbackExecutor = platform.defaultCallbackExecutor();
    }
    
    // Make a defensive copy of the adapters and add the default Call adapter.
    // 添加Call adapter,这个使用RxJava的时候会添加
    List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
    adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

    // Make a defensive copy of the converters.
    // 转换器列表
    List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

    return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
        callbackExecutor, validateEagerly);
}

Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
    List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,
    @Nullable Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    // 这个是通过Collections.unmodifiableList创建一个只可读的List
    this.converterFactories = unmodifiableList(converterFactories); // Defensive copy at call site.
    this.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site.
    this.callbackExecutor = callbackExecutor;
    // 默认为false
    this.validateEagerly = validateEagerly;
}

2. 动态代理的使用

OK,创建完成。接着我们需要写一个接口:

public interface Api {
    @GET
    Call<ResponseBody> get(@Url String url, @QueryMap Map<String, String> params);
}

// 接口创建
Api api = retrofit.create(Api.class)

使用我们刚刚创建的Retrofit来创建我们的Api,会调用Retrofitcreate方法:

Retrofit.java:
public <T> T create(final Class<T> service) {
    // 验证是否是接口并且接口数量是不是一个
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
        eagerlyValidateMethods(service);
    }
    // 动态代理的使用
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            private final Platform platform = Platform.get();
            
            @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                // 该方法是在Object中定义,直接调用
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                // 这里返回值一直为false
                if (platform.isDefaultMethod(method)) {
                    return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                
                ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                // 返回的值为ExecutorCallbackCall,后面会写
                return serviceMethod.callAdapter.adapt(okHttpCall);
            }
        });
}

创建我们的Api使用了动态代理,如果方法是Object中定义的,那么直接调用该方法;否则,需要将该方法所用到的注解值提取:

Retrofit.java:
// 加载方法放入缓存中
ServiceMethod<?, ?> loadServiceMethod(Method method) {
    // 先去缓存中获取
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;
    // 同步块
    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
            // 没有直接创建
            result = new ServiceMethod.Builder<>(this, method).build();
            // 放入缓存
            serviceMethodCache.put(method, result);
        }
    }
    return result;
}

ServiceMethod.java:
Builder(Retrofit retrofit, Method method) {
    this.retrofit = retrofit;
    this.method = method;
    // 方法的注解
    this.methodAnnotations = method.getAnnotations();
    // 方法参数类型
    this.parameterTypes = method.getGenericParameterTypes();
    // 方法参数注解
    this.parameterAnnotationsArray = method.getParameterAnnotations();
}

public ServiceMethod build() {
    // 创建CallAdapter
    callAdapter = createCallAdapter();
    // 获得返回值类型
    responseType = callAdapter.responseType();
    // 返回值类型不能为Response或者okhttp3.Response
    if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
            + Utils.getRawType(responseType).getName()
            + "' is not a valid response body type. Did you mean ResponseBody?");
    }
    // 返回值转换器
    responseConverter = createResponseConverter();
    
    // 对方法的注解转换
    for (Annotation annotation : methodAnnotations) {
        // 比较重要的一个方法,用于将方法的注解提取并对成员变量赋值
        parseMethodAnnotation(annotation);
    }
    
    //注解使用错误的抛出异常
    ......
    
    // 参数长度(参数注解的长度)
    int parameterCount = parameterAnnotationsArray.length;
    // 创建ParameterHandler数组,该类是个抽象类,主要用于将参数对应的值获取,并设置到RequestBuilder中
    parameterHandlers = new ParameterHandler<?>[parameterCount];
    for (int p = 0; p < parameterCount; p++) {
        // 获得参数的类型
        Type parameterType = parameterTypes[p];
        if (Utils.hasUnresolvableType(parameterType)) {
            throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                parameterType);
        }
        
        // 该参数的注解数组
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        if (parameterAnnotations == null) {
            throw parameterError(p, "No Retrofit annotation found.");
        }
        // 根据注解创建对应的ParameterHandler的子类
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
    }
    
    //注解使用错误的抛出异常
    ......
    return new ServiceMethod<>(this);
}

// 创建CallAdapter
private CallAdapter<T, R> createCallAdapter() {
    // 获得方法的返回值类型,并检查
    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
        throw methodError(
                "Method return type must not include a type variable or wildcard: %s", returnType);
    }
    // 返回值为void
    if (returnType == void.class) {
        throw methodError("Service methods cannot return void.");
    }
    Annotation[] annotations = method.getAnnotations();
    try {
        //noinspection unchecked
        // 调用retrofit的callAdapter方法,我们在构建Retrofit是如果没有传入自定义的CallAdapter,那么则使用当前平台的CallAdapter
        // 这里返回的是Android平台默认的CallAdapter(通过ExecutorCallAdapterFactory.get(参数)获得)
        //  new CallAdapter<Object, Call<?>>() {
        //    @Override public Type responseType() {
        //      return responseType;
        //  }
        //
        //    @Override public Call<Object> adapt(Call<Object> call) {
        //      return new ExecutorCallbackCall<>(callbackExecutor, call);
        //  }
        //}
        return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create call adapter for %s", returnType);
    }
}

上面我们看到了关于ServiceMethod对象的创建,其中parseMethodAnnotation()方法是对将方法的注解提取并对ServiceMetohd的成员变量赋值,主要用于对http的请求方式设置、url以及注解正确性的检查,parseParameter()方法用于生成一个ParameterHandler类的子类,该子类需要实现apply()方法,用于将注解中以及参数的值设置到请求参数中,这里是后话了。先看下parseMethodAnnotation()方法:

//方法中主要调用了parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);这个方法,只是参数不同。看下该方法实现:
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
    if (this.httpMethod != null) {
        throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                this.httpMethod, httpMethod);
    }
    // 设置请求方法,以GET为例
    this.httpMethod = httpMethod;
    // get方法没有body
    this.hasBody = hasBody;

    if (value.isEmpty()) {
        return;
    }

    // Get the relative URL path and existing query string, if present.
    // 如果存在查询,并且查询中使用了{}这种形式,那么抛出异常
    int question = value.indexOf('?');
    if (question != -1 && question < value.length() - 1) {
        // Ensure the query string does not have any named parameters.
        String queryParams = value.substring(question + 1);
        Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
        if (queryParamMatcher.find()) {
            throw methodError("URL query string \"%s\" must not have replace block. "
                    + "For dynamic query parameters use @Query.", queryParams);
        }
    }
    // 相对rul设置
    this.relativeUrl = value;
    // 将{}作为相对url的参数放入Set中
    this.relativeUrlParamNames = parsePathParameters(value);
}

上面的方法主要是对Method的注解进行处理,通过注解来获得该方法的请求方式以及url。接着我们来看下parseParameter()方法:

private ParameterHandler<?> parseParameter(
        int p, Type parameterType, Annotation[] annotations) {
    ParameterHandler<?> result = null;
    // 遍历参数注解
    for (Annotation annotation : annotations) {
        // 根据注解类型真正创建ParameterHandler对应的子类
        ParameterHandler<?> annotationAction = parseParameterAnnotation(
                p, parameterType, annotations, annotation);

        if (annotationAction == null) {
            continue;
        }

        if (result != null) { // 只会创建一次,如果已经创建了,那么抛出异常
            throw parameterError(p, "Multiple Retrofit annotations found, only one allowed.");
        }

        result = annotationAction;
    }
    
    // result为null,说明该参数没有被注解修饰,抛出异常
    if (result == null) {
        throw parameterError(p, "No Retrofit annotation found.");
    }

    return result;
}

private ParameterHandler<?> parseParameterAnnotation(
        int p, Type type, Annotation[] annotations, Annotation annotation) {
    if (annotation instanceof Url) {
        ......

    } else if (annotation instanceof Path) {
        ......

    } else if (annotation instanceof Query) { 
        ......

    } else if (annotation instanceof QueryName) {
        ......

    } else if (annotation instanceof QueryMap) { //我们比较常见的@QueryMap
        Class<?> rawParameterType = Utils.getRawType(type);
        if (!Map.class.isAssignableFrom(rawParameterType)) {
            throw parameterError(p, "@QueryMap parameter type must be Map.");
        }
        Type mapType = Utils.getSupertype(type, rawParameterType, Map.class);
        if (!(mapType instanceof ParameterizedType)) {
            throw parameterError(p, "Map must include generic types (e.g., Map<String, String>)");
        }
        ParameterizedType parameterizedType = (ParameterizedType) mapType;
        Type keyType = Utils.getParameterUpperBound(0, parameterizedType);
        if (String.class != keyType) {
            throw parameterError(p, "@QueryMap keys must be of type String: " + keyType);
        }
        Type valueType = Utils.getParameterUpperBound(1, parameterizedType);
        // 这段代码直接返回了(Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE,
        // 最终是ToStringConverter对象
        Converter<?, String> valueConverter =
                retrofit.stringConverter(valueType, annotations);
                
        
        // 创建了QueryMap对象,QueryMap是继承ParameterHandler,并且实现了其抽象方法apply
        return new ParameterHandler.QueryMap<>(valueConverter, ((QueryMap) annotation).encoded());

    } else if (annotation instanceof Header) {
        ......

    } else if (annotation instanceof HeaderMap) {
        ......

    } else if (annotation instanceof Field) {
        ......

    } else if (annotation instanceof FieldMap) {
        ......
        
    } else if (annotation instanceof Part) {
        ......

    } else if (annotation instanceof PartMap) {
        ......

    } else if (annotation instanceof Body) {
        ......
    }

    return null; // Not a Retrofit annotation.
}

通过遍历参数和该参数的注解可以生成ParameterHandler的子类,用于对注解的类型的存储。当最后需要组装参数时,只需要遍历ParameterHandler数组并且调用其apply方法即可。

3. 调用OkHttp的过程

写到这里差点忘记到底该写哪里了。。往上翻一翻就可以知道,我们才写完loadServiceMethod这个方法,继续往下看OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);,这里创建了一个OkHttpCall的对象,将我们刚才创建的ServiceMethod和执行该方法的参数全部传入。最后调用return serviceMethod.callAdapter.adapt(okHttpCall);返回对应的值(我们这里是Call<ResponseBody>)。来看下callAdapter.adapt(okHttpCall)方法:

创建过程讲过,我们会有一个默认的Executor和CallAdapter.Factory,这两个类在Android平台分别为Platform.MainThreadExecutor和ExecutorCallAdapterFactory。
ExecutorCallAdapterFactory.java:
// 将Executor传入
ExecutorCallAdapterFactory(Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
}
// 上面已经说了,这里功过get方法获得CallAdapter对象,并且调用其adapt方法,返回Call<Object>对象
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
        return null;
    }
    final Type responseType = Utils.getCallResponseType(returnType);
    return new CallAdapter<Object, Call<?>>() {
        @Override
        public Type responseType() {
            return responseType;
        }

        @Override
        public Call<Object> adapt(Call<Object> call) {
            return new ExecutorCallbackCall<>(callbackExecutor, call);
        }
    };
}

现在我们通过我们的接口Call<ResponseBody> call = api.get(xxx,xxx)的方式构建出一个Call对象,通过我们上面的分析,这里返回的为new ExecutorCallAdapterFactory.ExecutorCallbackCall<>(callbackExecutor, call),当我们请求接口时,通常是这样call.enqueue(),来看下实现原理吧:

ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
    this.callbackExecutor = callbackExecutor;
    this.delegate = delegate;
}

@Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");
    // 执行调用了传入的call对象的enqueue方法
    delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
            // Android平台这里是通过Handler来进行执行
            callbackExecutor.execute(new Runnable() {
                @Override public void run() {
                    if (delegate.isCanceled()) {
                        // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                        callback.onFailure(ExecutorCallAdapterFactory.ExecutorCallbackCall.this, new IOException("Canceled"));
                    } else {
                        callback.onResponse(ExecutorCallAdapterFactory.ExecutorCallbackCall.this, response);
                    }
                }
            });
        }

        @Override public void onFailure(Call<T> call, final Throwable t) {
            callbackExecutor.execute(new Runnable() {
                @Override public void run() {
                    callback.onFailure(ExecutorCallAdapterFactory.ExecutorCallbackCall.this, t);
                }
            });
        }
    });
}

// 这里面的callbackExecutor就是该Executor
static class MainThreadExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());

    @Override public void execute(Runnable r) {
        handler.post(r);
    }
}

通过构造方法以及enqueue方法可以知道,最终的方法是调用了传入的Call对象的enqueue方法。这个传入的对象又是谁呢?就是上面写到的OkHttpCallOkHttpCall构造需要将我们创建的ServiceMethod和方法执行的参数传入。其enqueue()如下:

@Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");
    // 这里就是okhttp3的Call
    okhttp3.Call call;
    Throwable failure;
    
    synchronized (this) {
        // 正在执行抛出异常
        if (executed) throw new IllegalStateException("Already executed.");
        executed = true;

        call = rawCall;
        failure = creationFailure;
        if (call == null && failure == null) {
            try {
                // call对象为null,则需要创建
                call = rawCall = createRawCall();
            } catch (Throwable t) {
                failure = creationFailure = t;
            }
        }
    }
    
    // 创建失败了。。。
    if (failure != null) {
        callback.onFailure(this, failure);
        return;
    }
    
    // 取消
    if (canceled) {
        call.cancel();
    }

    // 执行,传入了okhttp的回调,在其中调用Retrofi的回调
    call.enqueue(new okhttp3.Callback() {
        @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
                throws IOException {
            Response<T> response;
            try {
                response = parseResponse(rawResponse);
            } catch (Throwable e) {
                callFailure(e);
                return;
            }
            callSuccess(response);
        }

        @Override public void onFailure(okhttp3.Call call, IOException e) {
            try {
                callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }

        private void callFailure(Throwable e) {
            try {
                callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }

        private void callSuccess(Response<T> response) {
            try {
                callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    });
}

OK,现在已经可以看到调用了okhttpCallenqueue方法,那么我们的参数呢?这里是怎么设置的?我们看下createRawCall()方法:

OkHttpCall.java:
private okhttp3.Call createRawCall() throws IOException {
    // 又调用了serviceMethod的toRequest方法,将参数传入
    Request request = serviceMethod.toRequest(args);
    // 生成新的Call callFactory为OkhttpClient
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    if (call == null) {
        throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
}

ServiceMethod.java:
Request toRequest(@Nullable Object... args) throws IOException {
    // 穿件RequestBuilder对象
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
            contentType, hasBody, isFormEncoded, isMultipart);

    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    // 这个是我们之前讲过的根据参数注解生成的ParameterHandler子类
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    // 参数长度和ParameterHandler的长度一致
    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
        throw new IllegalArgumentException("Argument count (" + argumentCount
                + ") doesn't match expected count (" + handlers.length + ")");
    }
    
    // 遍历ParameterHandler数组,分别调用apply方法对requestBuilder进行组装
    for (int p = 0; p < argumentCount; p++) {
        handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.build();
}
RequestBuilder.java:
// build方法是创建一个Request对象
Request build() {
    HttpUrl url;
    HttpUrl.Builder urlBuilder = this.urlBuilder;
    if (urlBuilder != null) {
        url = urlBuilder.build();
    } else {
        // No query parameters triggered builder creation, just combine the relative URL and base URL.
        //noinspection ConstantConditions Non-null if urlBuilder is null.
        url = baseUrl.resolve(relativeUrl);
        if (url == null) {
            throw new IllegalArgumentException(
                    "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl);
        }
    }

    // 根据是否有form或者multipartBuilder来判断请求Body部分如何创建
    RequestBody body = this.body;
    if (body == null) {
        // Try to pull from one of the builders.
        if (formBuilder != null) {
            body = formBuilder.build();
        } else if (multipartBuilder != null) {
            body = multipartBuilder.build();
        } else if (hasBody) {
            // Body is absent, make an empty body.
            body = RequestBody.create(null, new byte[0]);
        }
    }

    MediaType contentType = this.contentType;
    if (contentType != null) {
        if (body != null) {
            body = new RequestBuilder.ContentTypeOverridingRequestBody(body, contentType);
        } else {
            requestBuilder.addHeader("Content-Type", contentType.toString());
        }
    }

    // 调用Request的build方法创建Request对象
    return requestBuilder
            .url(url)
            .method(method, body)
            .build();
}

写到这里我们往回看,先通过Request.Builder创建Request对象,接着使用OkHttpClient创建Call对象,最后通过call.enqueue(callback)请求接口,并将数据通过callback进行成功或失败的回调。
和我们最开始写的OkHttp请求接口方式一毛一样!!

没有总结

源码的话可能要暂停一段时间了,这段时间需要整理下面试题。希望能在今年卖个好价钱吧,哈哈。


img_b45d683f65e3165c689fee4bc5c3e407.png
目录
相关文章
|
6天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
37 13
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术深度解析:从基础到应用的全面介绍
人工智能(AI)技术的迅猛发展,正在深刻改变着我们的生活和工作方式。从自然语言处理(NLP)到机器学习,从神经网络到大型语言模型(LLM),AI技术的每一次进步都带来了前所未有的机遇和挑战。本文将从背景、历史、业务场景、Python代码示例、流程图以及如何上手等多个方面,对AI技术中的关键组件进行深度解析,为读者呈现一个全面而深入的AI技术世界。
61 10
|
2天前
|
物联网 调度 vr&ar
鸿蒙HarmonyOS应用开发 |鸿蒙技术分享HarmonyOS Next 深度解析:分布式能力与跨设备协作实战
鸿蒙技术分享:HarmonyOS Next 深度解析 随着万物互联时代的到来,华为发布的 HarmonyOS Next 在技术架构和生态体验上实现了重大升级。本文从技术架构、生态优势和开发实践三方面深入探讨其特点,并通过跨设备笔记应用实战案例,展示其强大的分布式能力和多设备协作功能。核心亮点包括新一代微内核架构、统一开发语言 ArkTS 和多模态交互支持。开发者可借助 DevEco Studio 4.0 快速上手,体验高效、灵活的开发过程。 239个字符
136 13
鸿蒙HarmonyOS应用开发 |鸿蒙技术分享HarmonyOS Next 深度解析:分布式能力与跨设备协作实战
|
1天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
24 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
14天前
|
数据采集 DataWorks 搜索推荐
阿里云DataWorks深度评测:实战视角下的全方位解析
在数字化转型的大潮中,高效的数据处理与分析成为企业竞争的关键。本文深入评测阿里云DataWorks,从用户画像分析最佳实践、产品体验、与竞品对比及Data Studio公测体验等多角度,全面解析其功能优势与优化空间,为企业提供宝贵参考。
84 13
|
11天前
|
数据采集 存储 JavaScript
网页爬虫技术全解析:从基础到实战
在信息爆炸的时代,网页爬虫作为数据采集的重要工具,已成为数据科学家、研究人员和开发者不可或缺的技术。本文全面解析网页爬虫的基础概念、工作原理、技术栈与工具,以及实战案例,探讨其合法性与道德问题,分享爬虫设计与实现的详细步骤,介绍优化与维护的方法,应对反爬虫机制、动态内容加载等挑战,旨在帮助读者深入理解并合理运用网页爬虫技术。
|
17天前
|
存储 监控 调度
云服务器成本优化深度解析与实战案例
本文深入探讨了云服务器成本优化的策略与实践,涵盖基本原则、具体策略及案例分析。基本原则包括以实际需求为导向、动态调整资源、成本控制为核心。具体策略涉及选择合适计费模式、优化资源配置、存储与网络配置、实施资源监控与审计、应用性能优化、利用优惠政策及考虑多云策略。文章还通过电商、制造企业和初创团队的实际案例,展示了云服务器成本优化的有效性,最后展望了未来的发展趋势,包括智能化优化、多云管理和绿色节能。
|
18天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
23天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
51 12
|
17天前
|
数据采集
动态代理与静态代理在爬虫解析的优缺点
随着科技和互联网的发展,越来越多企业需要使用代理进行数据抓取。本文介绍了HTTP动态代理与静态代理的区别,帮助您根据具体需求选择最佳方案。动态代理适合大规模、高效率的爬取任务,但稳定性较差;静态代理则适用于小规模、高稳定性和速度要求的场景。选择时需考虑目标、数据量及网站策略。
38 4

推荐镜像

更多