Android-Retrofit

概述

  • Retrofit 通過注解將 Java Interface API 調(diào)用轉化為 HTTP Call 誉察,注解提供了 HTTP Call 的參數(shù);
  • 使用者可以適配請求參數(shù)轉化地回,響應數(shù)據(jù)轉化,HTTP Call 對象轉化汽烦,OkHttpClient 定制等泻轰;
  • 源碼基于retrofit-2.7.1;

Converter

  • Converter
    public interface Converter<F, T> {
      @Nullable T convert(F value) throws IOException;
    }
    
    • 用于將 F 類型的對象轉化為 T 類型的對象琢感;
  • Converter.Factory
    abstract class Factory {
      //生成 Converter 對象丢间,將 ResponseBody 類型轉化為 Type 類型探熔;
      public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
          Annotation[] annotations, Retrofit retrofit) {
        return null;
      }
    
      //生成 Converter 對象驹针,將 Type 類型轉化為 RequestBody 類型;
      public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        return null;
      }
    
     //生成 Converter 對象诀艰,將 Type 類型轉化為 String 類型
      public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
          Retrofit retrofit) {
        return null;
      }
    
      //返回參數(shù)化類型中的類型引元的上界類型
      protected static Type getParameterUpperBound(int index, ParameterizedType type) {
        return Utils.getParameterUpperBound(index, type);
      }
    
      //返回原生類型
      protected static Class<?> getRawType(Type type) {
        return Utils.getRawType(type);
      }
    }
    
    • responseBodyConverter 生成 Converter柬甥,將 ResponseBody 轉化為 Type 饮六;
    • requestBodyConverter 生成 Converter ,將 Type 轉化為 RequestBody 苛蒲;用于轉化用 Body Part PartMap 注解的參數(shù)卤橄;
    • stringConverter 生成 Converter ,將 Type 轉化為 String 臂外;用于轉化用 Field FieldMap Header HeaderMap Path Query QueryMap 注解的參數(shù)窟扑;

CallAdapter

  • CallAdapter
    //將 Call<R> 轉化為 T 的適配器 
    public interface CallAdapter<R, T> {
      //返回 response type
      Type responseType();
      //將 Call<R> 轉化為T
      T adapt(Call<R> call);
    }
    
  • CallAdapter.Factory
    abstract class Factory {
      //生成 CallAdapter
      public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
          Retrofit retrofit);
    
      //返回參數(shù)化類型的類型引元的上界類型
      protected static Type getParameterUpperBound(int index, ParameterizedType type) {
        return Utils.getParameterUpperBound(index, type);
      }
    
      //返回原生類型
      protected static Class<?> getRawType(Type type) {
        return Utils.getRawType(type);
      }
    }
    
    • 返回匹配的 CallAdapter

ServiceMethod

  • ServiceMethod 可以理解為 Java Interface API 的靜態(tài)映射;
  • 生成 ServiceMethod
    abstract class ServiceMethod<T> {
      static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
        //解析 Annotation 漏健,生成RequestFactory
        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);
        }
        //返回類型不支持 void 類型
        if (returnType == void.class) {
          throw methodError(method, "Service methods cannot return void.");
        }
        //生成 ServiceMethod
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
      }
    
      abstract @Nullable T invoke(Object[] args);
    }
    
    static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
      //通過 Method 生成 RequestFactory嚎货,RequestFactory#create 可以生成最終發(fā)起請求的 Request
      return new Builder(retrofit, method).build();
    }
    
    static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
        Retrofit retrofit, Method method, RequestFactory requestFactory) {
      boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
      boolean continuationWantsResponse = false;
      boolean continuationBodyNullable = false;
    
      Annotation[] annotations = method.getAnnotations();
      Type adapterType;
      if (isKotlinSuspendFunction) {
        //kotlin 掛起函數(shù) 相關邏輯
      } else {
        // Method 返回類型(包括類型引元)
        adapterType = method.getGenericReturnType();
      }
    
      //根據(jù) adapterType 找到匹配的 CallAdapter
      CallAdapter<ResponseT, ReturnT> callAdapter =
          createCallAdapter(retrofit, method, adapterType, annotations);
      //返回 CallAdapter 指定的 responseType
      Type responseType = callAdapter.responseType();
      //根據(jù) responseType 找到匹配的Converter
      Converter<ResponseBody, ResponseT> responseConverter =
          createResponseConverter(retrofit, method, responseType);
    
      okhttp3.Call.Factory callFactory = retrofit.callFactory;
      if (!isKotlinSuspendFunction) {
        //生成ServiceMethod
        return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
      } else if (continuationWantsResponse) {
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
            callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
      } else {
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
            callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
            continuationBodyNullable);
      }
    }
    
    • RequestFactory#parseAnnotations 通過 Method 生成 RequestFactoryRequestFactory#create 生成最終發(fā)起請求的 Request 蔫浆;
    • RequestFactory.Builder 中關于各種注解的解析殖属,可自行查看源碼;
    • ServiceMethod#parseAnnotations
      • 先通過 Method 的返回類型(包括類型引元)找到匹配的 CallAdapter瓦盛;
      • 再通過 CallAdapter#responseAdapter 找到匹配的響應數(shù)據(jù)的 Converter 洗显;
      • 通過 RequestFactory Call.Factory Converter CallAdapter 生成 CallAdaptedCallAdaptedServiceMethod 的子類原环;
  • ServiceMethod 的調(diào)用
    ReturnT invoke(Object[] args) {
      Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
      return adapt(call, args);
    }
    
    @Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
        return callAdapter.adapt(call);
      }
    
    • Java Interface API 的調(diào)用就是 ServiceMethod 的調(diào)用挠唆,即調(diào)用 ServiceMethod#invoke
    • 根據(jù) RequestFactory Call.Factory Converter 生成 OkHttpCall 嘱吗,內(nèi)部持有 OkhttpCall 损搬,所以 Retrofit 的網(wǎng)絡請求是由 OkHttp 處理的,并且無法更改柜与;
    • 根據(jù) CallAdapter 返回 Java Interface API 的返回類型的對象巧勤,并把 OkHttpCall 通過參數(shù)傳遞給 CallAdapter

Retrofit

  • Retrofit/Builder
    • Retrofit 對象是通過 Retrofit.Builder 生成的弄匕,有各種配置項颅悉;
    //緩存接口方法對應的 ServiceMethod
    private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
    //通過 Factory 生成的 Call 對象發(fā)起網(wǎng)絡請求
    final okhttp3.Call.Factory callFactory;
    //用于拼接url
    final HttpUrl baseUrl;
    //請求數(shù)據(jù)/響應數(shù)據(jù)
    final List<Converter.Factory> converterFactories;
    // HTTP Call 對象轉化器
    final List<CallAdapter.Factory> callAdapterFactories;
    //異步回調(diào)分發(fā)器
    final @Nullable Executor callbackExecutor;
    //ServiceMethod預加載
    final boolean validateEagerly;
    
    • serviceMethodCache 用于緩存 Java Interface API 對應的 ServiceMethod
    • callFactory 用于生成真正發(fā)情網(wǎng)絡請求的 HTTP Call 迁匠;默認會創(chuàng)建一個 OkHttpClient 剩瓶;
    • converterFactories 用于生成 請求數(shù)據(jù)/響應數(shù)據(jù) 轉化器;要注意添加 Converter.Factory 的順序城丧;
    • callAdapterFactories 用于生成 HTTP Call 適配器延曙,將 Retrofit 內(nèi)置的 HTTP Call 轉化為 Java Interface API 返回的 HTTP Call ;要注意添加 CallAdapter 的順序亡哄;
    • callbackExecutor 表示異步回調(diào)分發(fā)器枝缔;默認是主線程;
    • validateEagerly 表示是否預加載 ServiceMethod ;如果為 true 愿卸,則在創(chuàng)建 Java Interfae 對應的動態(tài)代理對象時灵临,預加載該 Java Interface所有 API 對應的 ServiceMethod
  • Retrofit#create
    public <T> T create(final Class<T> service) {
      // Java Interface 校驗趴荸,ServiceMethod 預加載
      validateServiceInterface(service);
      //生成動態(tài)代理對象
      return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
          new InvocationHandler() {
            private final Platform platform = Platform.get();
            private final Object[] emptyArgs = new Object[0];
    
            @Override public @Nullable Object invoke(Object proxy, Method method,
                @Nullable Object[] args) throws Throwable {
              // 如果是 Object 的 API 調(diào)用儒溉,直接調(diào)用
              if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
              }
              // 如果是 Java Interface 中的默認方法調(diào)用
              if (platform.isDefaultMethod(method)) {
                return platform.invokeDefaultMethod(method, service, proxy, args);
              }
              // 通過 ServiceMethod 代理調(diào)用
              return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
            }
          });
    }
    
    private void validateServiceInterface(Class<?> service) {
      // 指定的類必須為 Interface
      if (!service.isInterface()) {
        throw new IllegalArgumentException("API declarations must be interfaces.");
      }
    
      Deque<Class<?>> check = new ArrayDeque<>(1);
      check.add(service);
      while (!check.isEmpty()) {
        Class<?> candidate = check.removeFirst();
        if (candidate.getTypeParameters().length != 0) {
          //不支持參數(shù)化類型
          StringBuilder message = new StringBuilder("Type parameters are unsupported on ")
              .append(candidate.getName());
          if (candidate != service) {
            message.append(" which is an interface of ")
                .append(service.getName());
          }
          throw new IllegalArgumentException(message.toString());
        }
        Collections.addAll(check, candidate.getInterfaces());
      }
    
      // ServiceMethod 預加載
      if (validateEagerly) {
        Platform platform = Platform.get();
        for (Method method : service.getDeclaredMethods()) {
          if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
            //加載 ServiceMethod
            loadServiceMethod(method);
          }
        }
      }
    }
    
    ServiceMethod<?> loadServiceMethod(Method method) {
      //如果有緩存,直接返回
      ServiceMethod<?> result = serviceMethodCache.get(method);
      if (result != null) return result;
    
      synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
          //通過 Method 及相關注解发钝,生成 ServiceMethod 對象顿涣,并緩存
          result = ServiceMethod.parseAnnotations(this, method);
          serviceMethodCache.put(method, result);
        }
      }
      return result;
    }
    
    • 先校驗指定的 Class ,必須為 Java Interface 酝豪,并且不是參數(shù)化類型园骆;如果要預加載 ServiceMethod,則通過 Method 及相關的 Annotation 生成 ServiceMethod 并緩存寓调;
    • 通過動態(tài)代理為指定的 Java Interface 生成動態(tài)代理對象锌唾,動態(tài)代理可以查看 Java-反射-動態(tài)代理
    • 動態(tài)代理對象的方法調(diào)用都會轉發(fā)給綁定的 InvocationHandler#invoke 方法夺英;具體調(diào)用流程后面會有詳細介紹晌涕;
  • Retrofit#callAdapter
    //返回匹配 Java Interface API 返回類型的 CallAdapter
    public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
      return nextCallAdapter(null, returnType, annotations);
    }
    
    //返回匹配 Java Interface API 返回類型的 CallAdapter
    public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
        Annotation[] annotations) {
      Objects.requireNonNull(returnType, "returnType == null");
      Objects.requireNonNull(annotations, "annotations == null");
    
      int start = callAdapterFactories.indexOf(skipPast) + 1;
      for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
        CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
        if (adapter != null) {
          //CallAdapter.Factory 返回不為null,則匹配成功
          return adapter;
        }
      }
    }
    
    • callAdapterFactories 中遍歷尋找能匹配 returnType 的CallAdapter痛悯;
  • Retrofit#requestBodyConverter
    // 返回 requestBody 的轉化器
    public <T> Converter<T, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
      return nextRequestBodyConverter(null, type, parameterAnnotations, methodAnnotations);
    }
    
    // 返回 requestBody 的轉化器
    public <T> Converter<T, RequestBody> nextRequestBodyConverter(
        @Nullable Converter.Factory skipPast, Type type, Annotation[] parameterAnnotations,
        Annotation[] methodAnnotations) {
      int start = converterFactories.indexOf(skipPast) + 1;
      for (int i = start, count = converterFactories.size(); i < count; i++) {
        Converter.Factory factory = converterFactories.get(i);
        Converter<?, RequestBody> converter =
            factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
        if (converter != null) {
          //如果返回的 Converter 不為null余黎,則匹配成功
          return (Converter<T, RequestBody>) converter;
        }
      }
    }
    
    • 返回轉化 requestBody 的 Converter
    • 用于轉化用 Body Part PartMap 注解的方法參數(shù)载萌;
  • Retrofit#responseBodyConverter
    //返回
    public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
      return nextResponseBodyConverter(null, type, annotations);
    }
    
    public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
        @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
      int start = converterFactories.indexOf(skipPast) + 1;
      for (int i = start, count = converterFactories.size(); i < count; i++) {
        Converter<ResponseBody, ?> converter =
            converterFactories.get(i).responseBodyConverter(type, annotations, this);
        if (converter != null) {
          //如果返回 Converter 不為null惧财,則匹配成功
          return (Converter<ResponseBody, T>) converter;
        }
      }
    }
    
    • 返回轉化 responseBody 的 Converter
  • Retrofit#stringConverter
    //返回 `Conterver` 扭仁,用于將 `Type` 轉化為 `String` 垮衷;
    public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
      for (int i = 0, count = converterFactories.size(); i < count; i++) {
        Converter<?, String> converter =
            converterFactories.get(i).stringConverter(type, annotations, this);
        if (converter != null) {
          //noinspection unchecked
          return (Converter<T, String>) converter;
        }
      }
    
      //如果沒有匹配到,則返回內(nèi)置的 Converter
      return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
    }
    
    • 返回 Converter 乖坠,用于將 Type 轉化為 String 搀突;
    • 用于轉化用 Field FieldMap Header HeaderMap Path Query QueryMap 注解的方法參數(shù);

總結

  • Retrofit 整體分為兩部分:注解和代碼熊泵;
    • 注解部分
      • Java Interfae API 中說明該網(wǎng)絡請求的參數(shù)仰迁,包括請求方法(GET,POST等)顽分,請求參數(shù)徐许,URL等;
    • 代碼部分
      • Java Interface API 轉換成對應的 ServiceMethod :根據(jù)設置的 Converter.Factory CallAdapter.Factory 找到匹配的 CallAdapter Converter卒蘸,生成 RequestFactory 雌隅;
      • Java Interface API 調(diào)用時,根據(jù) ServiceMethod 中的 RequestFactory Converter 以及傳入的參數(shù),生成 OkHttpCall 澄步,并將 CallAdapter 轉換后的結果返回冰蘑;
  • Converter.Factory 中的 requestBodyConverter 比較獨立和泌;Converter.Factory 中的 responseBodyConverterCallAdapter.Factory 中的 responseType 是關聯(lián)的村缸,表示的是響應數(shù)據(jù)要轉換的類型;
  • 配置 CallAdapterFactory ConverterFactory 時武氓,需要注意前后順序梯皿,具有特殊性的要排在前面,防止前面的通用性的 Factory 覆蓋了 后面的 Factory 县恕;
  • 關于解析注解的那部分东羹,感興趣的可以自行查看源碼 RequestFactory#parseAnnotations,里面涉及 HTTP 協(xié)議的知識忠烛;

其他

  • 多個 baseUrl 配置
    • 如果涉及 OkHttpClient 配置不一樣的属提,或者不同module之間,可以多個 Retrofit 美尸;如果只是少數(shù)幾個接口不一樣冤议,可以通過 Url 注解第一個參數(shù)動態(tài)設置 或者 注解方法時指定絕對路徑靜態(tài)設置;
    • 關于不同環(huán)境下的 baseUrl 切換师坎,可以放在 OkHttpClientInterceptor 中動態(tài)切換恕酸;
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胯陋,隨后出現(xiàn)的幾起案子蕊温,更是在濱河造成了極大的恐慌,老刑警劉巖遏乔,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件义矛,死亡現(xiàn)場離奇詭異,居然都是意外死亡盟萨,警方通過查閱死者的電腦和手機症革,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸯旁,“玉大人噪矛,你說我怎么就攤上這事∑贪眨” “怎么了艇挨?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長韭赘。 經(jīng)常有香客問我缩滨,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任脉漏,我火速辦了婚禮苞冯,結果婚禮上,老公的妹妹穿的比我還像新娘侧巨。我一直安慰自己舅锄,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布司忱。 她就那樣靜靜地躺著皇忿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坦仍。 梳的紋絲不亂的頭發(fā)上鳍烁,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音繁扎,去河邊找鬼幔荒。 笑死,一個胖子當著我的面吹牛梳玫,可吹牛的內(nèi)容都是我干的爹梁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼汽纠,長吁一口氣:“原來是場噩夢啊……” “哼卫键!你這毒婦竟也來了?” 一聲冷哼從身側響起虱朵,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤莉炉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碴犬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體絮宁,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年服协,在試婚紗的時候發(fā)現(xiàn)自己被綠了绍昂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡偿荷,死狀恐怖窘游,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跳纳,我是刑警寧澤忍饰,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站寺庄,受9級特大地震影響艾蓝,放射性物質發(fā)生泄漏力崇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一赢织、第九天 我趴在偏房一處隱蔽的房頂上張望亮靴。 院中可真熱鬧,春花似錦于置、人聲如沸茧吊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饱狂。三九已至曹步,卻和暖如春宪彩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讲婚。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工尿孔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筹麸。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓活合,卻偏偏與公主長得像,于是被迫代替她去往敵國和親物赶。 傳聞我的和親對象是個殘疾皇子白指,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一、什么是Retrofit A type-safe HTTP client for Android and Jav...
    andcoder閱讀 744評論 2 3
  • 本文將順著構建請求對象->構建請求接口->發(fā)起同步/異步請求的流程酵紫,分析Retrofit是如何實現(xiàn)的告嘲。 開始之前,...
    zhuhf閱讀 1,606評論 0 10
  • Retrofit 源碼解析 簡單用法 Retrofit最簡單的用法就是定義一個接口奖地,創(chuàng)建Retrofit對象橄唬,調(diào)用...
    Kingty閱讀 782評論 3 14
  • 安卓開發(fā)領域中,很多重要的問題都有很好的開源解決方案参歹,例如Square公司提供網(wǎng)絡請求 OkHttp , Retr...
    aaron688閱讀 1,902評論 1 20
  • Retrofit這個開源庫出來也有一定年頭了仰楚,記得之前還是在V1.0的版本的時候,之前在三月份也寫過一個Retro...
    lovejjfg閱讀 1,427評論 0 5