美文网首页android开发技巧Android开发经验谈Android开发
[Android]如何做一个崩溃率少于千分之三噶应用app(31

[Android]如何做一个崩溃率少于千分之三噶应用app(31

作者: CangWang | 来源:发表于2018-05-09 22:32 被阅读747次
    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学习组件化


    组件化架构2群.png

    相关文章

      网友评论

      本文标题:[Android]如何做一个崩溃率少于千分之三噶应用app(31

      本文链接:https://www.haomeiwen.com/subject/duworftx.html