Android]如何做一个崩溃率少于千分之三噶应用app(31)-组件化网络请求

Android组件化架构
Android组件化架构

以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。

相信很多人都会用过顶顶大名的Retrofit2框架,本篇就介绍组件化网络请求问题。
先说一下重点原理吧

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  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 (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            //可忽略
            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);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }

其使用了动态代理的方式来代理接口调用,通过serviceMethod来缓存接口对应的信息,然后通过okHttpCall来启动请求。

当工程有多个业务存在,你有一些业务可能访问的地址头会有多个,即baseUrl的切换问题。
解决方案1
@Get , @Post 这些标注到每个接口方法上的注解不仅可以传相对路径,还可以传全路径,全路径可以简单解决,但是这样只能固定死地址,如果服务器宕机,想换个后备服务器你要怎么办?你要么只能多备一份后备服务器地址,如果你非常不走运,都被黑客攻爆了,我只能同情你了。

解决方案2
官方出了@Url的注解用于改变路径地址的方案,这个还好是传进去的,是可以解决方案1中的问题,如果业务模块多了,每次都要单独传入地址,有点麻烦。

public interface UserService {  
    @GET
    public Call<ResponseBody> profilePicture(@Url String url);
}

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("https://s3.amazon.com/profile-picture/path");
实际只会请求@Url中的全地址

解决方案3
在Base做一个公用的,特别业务自身一个Retrofit实例然后创建ApiService,这样就能够间隔不同的业务,其Base底层提供一个复用OkHttpClient。如果模块中有其他特殊地质,使用第二种方案解决。
缺点是Retrofit实例变多。

解决方案4
研究过OkHttp的同学,应该知道OkHttp的拦截器机制,自定义一个拦截器,不清楚Okttp拦截器机制的,可以看这篇文章okhttp3 拦截器源码分析。在其请求的时候拦截掉请求,再替换掉Url,这样有点破坏上层的封装,你们看到的RetrofitUrlManager就是使用这个方案来完成的,在请求方法加入@Header的注解来标注使用哪个访问基类地址。

//声明请求对应的Headers
 public interface ApiService {
     @Headers({"Domain-Name: douban"}) // Add the Domain-Name header
     @GET("/v2/book/{id}")
     Observable<ResponseBody> getBook(@Path("id") int id);
}

// 注册基类baseUrl
 RetrofitUrlManager.getInstance().putDomain("douban", "https://api.douban.com");

// 切换基类baseUrl
 RetrofitUrlManager.getInstance().setGlobalDomain("your BaseUrl");

    private RetrofitUrlManager() {
        if (!DEPENDENCY_OKHTTP) { //使用本管理器必须依赖 Okhttp
            throw new IllegalStateException("Must be dependency Okhttp");
        }
        setUrlParser(new DefaultUrlParser()); 
        //拦截器
        this.mInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                if (!isRun()) // 可以在 App 运行时, 随时通过 setRun(false) 来结束本管理器的运行
                    return chain.proceed(chain.request());
                return chain.proceed(processRequest(chain.request()));
            }
        };
    }

   //挂接拦截器
    public OkHttpClient.Builder with(OkHttpClient.Builder builder) {
        return builder
                .addInterceptor(mInterceptor);
    }

    //挂接到OkHttp的拦截器   
     this.mOkHttpClient = RetrofitUrlManager.getInstance().with(new OkHttpClient.Builder()) //RetrofitUrlManager 初始化
                .readTimeout(5, TimeUnit.SECONDS)
                .connectTimeout(5, TimeUnit.SECONDS)
                .build();

 public Request processRequest(Request request) {

        Request.Builder newBuilder = request.newBuilder();

        String url = request.url().toString();
        //如果 Url 地址中包含 IDENTIFICATION_IGNORE 标识符, 管理器将不会对此 Url 进行任何切换 BaseUrl 的操作
        if (url.contains(IDENTIFICATION_IGNORE)) {
            return pruneIdentification(newBuilder, url);
        }
        //通过请求注解获取
        String domainName = obtainDomainNameFromHeaders(request);

        HttpUrl httpUrl;

        Object[] listeners = listenersToArray();

        // 如果有 header,获取 header 中 domainName 所映射的 url,若没有,则检查全局的 BaseUrl,未找到则为null
        if (!TextUtils.isEmpty(domainName)) {
            notifyListener(request, domainName, listeners);
            httpUrl = fetchDomain(domainName);
            newBuilder.removeHeader(DOMAIN_NAME);
        } else {
            notifyListener(request, GLOBAL_DOMAIN_NAME, listeners);
            httpUrl = getGlobalDomain();
        }
        //获取新的baseUrl地址
        if (null != httpUrl) {
            HttpUrl newUrl = mUrlParser.parseUrl(httpUrl, request.url());
            if (debug)
                Log.d(RetrofitUrlManager.TAG, "The new url is { " + newUrl.toString() + " }, old url is { " + request.url().toString() + " }");
          
            if (listeners != null) {
                for (int i = 0; i < listeners.length; i++) {
                    ((onUrlChangeListener) listeners[i]).onUrlChanged(newUrl, request.url()); // 通知监听器此 Url 的 BaseUrl 已被切换
                }
            }
           //返回链式调用
            return newBuilder
                    .url(newUrl)
                    .build();
        }

        return newBuilder.build();

    }

缺点是当极端情况,模块间交互请求Http的时候,伴随每次都要每个请求都需要先切换Url再请求,以保障url是正确的。

解决方案5
修改Retrofit的源码,添加一个HashMap的保存多个访问地址头,封装接口,需要的时候再取出配置。
缺点是无法跟随Retrofit版本更新。当然外部去维护一个HashMap,然后可以试着使用Hook的方法来hook掉baseUrl参数(坏笑)

这里并不存在最好的方案,相对于组件化使用,第三种方案相对解耦程度会高一点,第二种方案会灵活性上可以服务器动态配置,第四五中方案Retrofit都是单例,内存消耗会低一点。


群1已经满了,学习组件化可以进群2 763094035



猜你喜欢

转载自juejin.im/post/5af3c03af265da0b7b35eb85