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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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
目录
相关文章
|
3天前
|
编译器 PHP 开发者
PHP 8新特性解析与应用实践
PHP 8作为PHP语言的最新版本,带来了许多令人兴奋的新特性和性能改进。本文将深入探讨PHP 8中的JIT编译器、联合类型、匹配表达式等关键更新,并通过实例演示如何在项目中有效利用这些新工具,帮助开发者提升代码质量和执行效率。
|
2天前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
|
6天前
|
C# Android开发 开发者
Uno Platform 高级定制秘籍:深度解析与实践样式和模板应用,助你打造统一且高效的跨平台UI设计
【9月更文挑战第7天】Uno Platform 是一个强大的框架,支持使用 C# 和 XAML 创建跨平台 UI 应用,覆盖 Windows、iOS、Android、macOS 和 WebAssembly。本文介绍 Uno Platform 中样式和模板的应用,助力开发者提升界面一致性与开发效率。样式定义控件外观,如颜色和字体;模板则详细定制控件布局。通过 XAML 定义样式和模板,并可在资源字典中全局应用或嵌套扩展。合理利用样式和模板能简化代码、保持设计一致性和提高维护性,帮助开发者构建美观高效的跨平台应用。
15 1
|
12天前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
29 1
|
12天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
62 0
|
12天前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
32 0
|
12天前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
25 0
|
12天前
|
定位技术
|
12天前
|
C# 开发者 Windows
勇敢迈出第一步:手把手教你如何在WPF开源项目中贡献你的第一行代码,从选择项目到提交PR的全过程解析与实战技巧分享
【8月更文挑战第31天】本文指导您如何在Windows Presentation Foundation(WPF)相关的开源项目中贡献代码。无论您是初学者还是有经验的开发者,参与这类项目都能加深对WPF框架的理解并拓展职业履历。文章推荐了一些适合入门的项目如MvvmLight和MahApps.Metro,并详细介绍了从选择项目、设置开发环境到提交代码的全过程。通过具体示例,如添加按钮点击事件处理程序,帮助您迈出第一步。此外,还强调了提交Pull Request时保持专业沟通的重要性。参与开源不仅能提升技能,还能促进社区交流。
23 0
|
12天前
|
存储 C# 关系型数据库
“云端融合:WPF应用无缝对接Azure与AWS——从Blob存储到RDS数据库,全面解析跨平台云服务集成的最佳实践”
【8月更文挑战第31天】本文探讨了如何将Windows Presentation Foundation(WPF)应用与Microsoft Azure和Amazon Web Services(AWS)两大主流云平台无缝集成。通过具体示例代码展示了如何利用Azure Blob Storage存储非结构化数据、Azure Cosmos DB进行分布式数据库操作;同时介绍了如何借助Amazon S3实现大规模数据存储及通过Amazon RDS简化数据库管理。这不仅提升了WPF应用的可扩展性和可用性,还降低了基础设施成本。
30 0

热门文章

最新文章

推荐镜像

更多