浅谈Android开发系列网络篇之Retrofit

Retrofit是一个不错的网络请求库,用官方自己的介绍就是:

A type-safe REST client for Android and Java

看官网的介绍用起来很省事,不过如果不了解它是怎么实现的也不太敢用,不然出问题了就不知道怎么办了。这几天比较闲就下下来看了一下,了解一下大概实现方法,细节就不追究了。先来看一个官网的例子,详细说明去网官

简单示例

首先定义请求接口,即程序中都需要什么请求操作

public interface GitHubService {
 @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") String user);
}

然后通过RestAdapter生成一个刚才定义的接口的实现类,使用的是动态代理。

RestAdapter restAdapter = new RestAdapter.Builder()
  .setEndpoint("https://api.github.com")
  .build();

GitHubService service = restAdapter.create(GitHubService.class);

现在就可以调用接口进行请求了

List<Repo> repos = service.listRepos("octocat");

使用就是这么简单,请求时直接调用接口就行了,甚至不用封装参数,因为参数的信息已经在定义接口时通过Annotation定义好了。

从上面的例子可以看到接口直接返回了需要的Java类型,而不是byte[]或String,解析数据的地方就是Converter,这个是可以自定义的,默认是用Gson解析,也就是说默认认为服务器返回的是Json数据,可以通过指定不同的Convert使用不同的解析方法,如用Jackson解析Json,或自定义XmlConvert解析xml数据。

Retrofit的使用就是以下几步:

1.定义接口,参数声明,Url都通过Annotation指定

2.通过RestAdapter生成一个接口的实现类(动态代理)

3.调用接口请求数据

接口的定义要用用Rtrofit定义的一些Annotation,所以先看一下Annotation的。

Annotation

以上面的示例中的接口来看

@GET("/group/{id}/users")
List<User> groupList(@Path("id") int groupId);

先看@GET

/** Make a GET request to a REST path relative to base URL. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {
 String value();
}

@GET本身也被几个Anotation注解,@Target表示@GET注解是用于方法的,value方法就返回这个注解的value值,在上例中就是/group/{id}/users,然后就是@RestMethod

@Documented
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
public @interface RestMethod {
 String value();
 boolean hasBody() default false;
}

RestMethod是一个用于Annotation的Annotation,比如上面的例子中用来注解的@GET,value方法就返回GET,hasBody表示是否有Body,对于POST这个方法就返回true

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod(value = "POST", hasBody = true)
public @interface POST {
 String value();
}

Retrofit的Annotation包含请求方法相关的@GET、@POST、@HEAD、@PUT、@DELETA、@PATCH,和参数相关的@Path、@Field、@Multipart等。

定义了Annotation要就有解析它的方法,在Retrofit中解析的位置就是RestMethodInfo,但在这之前需要先看哪里使用了RestMethodInfo,前面说了Retrofit使用了动态代理生成了我们定义的接口的实现类,而这个实现类是通过RestAdapter.create返回的,所以使用动态代理的位置就是RestAdapter,接下来就看一下RestAdapter。

RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
  .setEndpoint("https://api.github.com")
  .build();

GitHubService service = restAdapter.create(GitHubService.class);

public RestAdapter build() {
 if (endpoint == null) {
  throw new IllegalArgumentException("Endpoint may not be null.");
 }

 ensureSaneDefaults();

 return new RestAdapter(endpoint, clientProvider, httpExecutor, callbackExecutor,
   requestInterceptor, converter, profiler, errorHandler, log, logLevel);
}

setEndPoint就不说了,接口中定义的都是相对Url,EndPoint就是域名,build方法调用ensureSaneDefaults()方法,然后就构造了一个RestAdapter对象,构造函数的参数中传入了EndPoint外的几个对象,这几个对象就是在ensureSaneDefaults()中初始化的。

private void ensureSaneDefaults() {
 if (converter == null) { converter = Platform.get().defaultConverter(); }
 if (clientProvider == null) { clientProvider = Platform.get().defaultClient(); }
 if (httpExecutor == null) { httpExecutor = Platform.get().defaultHttpExecutor(); }
 if (callbackExecutor == null) { callbackExecutor = Platform.get().defaultCallbackExecutor(); }
 if (errorHandler == null) { errorHandler = ErrorHandler.DEFAULT; }
 if (log == null) { log = Platform.get().defaultLog(); }
 if (requestInterceptor == null) { requestInterceptor = RequestInterceptor.NONE; }
}

ensureSaneDefaults()中初始化了很多成员,errorHandler、log就不看了,其他的除了requestInterceptor都是通过Platform对象获得的,所以要先看下Platform

Platform

private static final Platform PLATFORM = findPlatform();
 static final boolean HAS_RX_JAVA = hasRxJavaOnClasspath();

 static Platform get() {
  return PLATFORM;
 }

 private static Platform findPlatform() {
  try {
   Class.forName("android.os.Build");
   if (Build.VERSION.SDK_INT != 0) {
    return new Android();
   }
  } catch (ClassNotFoundException ignored) {
  }

  if (System.getProperty("com.google.appengine.runtime.version") != null) {
   return new AppEngine();
  }

  return new Base();
 }

使用了单例的PLATFORM,通过findPlatform()初始化实例,如果是Android平台就使用Platform.Android,如果是Google AppEngine就使用Platform.AppEngine,否则使用Platform.Base,这些都是Platform的子类,其中AppEngine又是Base的子类。

Platform是一个抽象类,定义了以下几个抽象方法,这几个方法的作用就是返回一些RestAdapter中需要要用到成员的默认实现

abstract Converter defaultConverter(); // 默认的Converter,用于将请求结果转化成需要的数据,如GsonConverter将JSON请求结果用Gson解析成Java对象
 abstract Client.Provider defaultClient(); // Http请求类,如果是AppEngine就使用`UrlFetchClient`,否则如果有OKHttp就使用OKHttp,如果是Android,2.3以后使用HttpURLConnection,2.3以前使用HttpClient
 abstract Executor defaultHttpExecutor(); // 用于执行Http请求的Executor
 abstract Executor defaultCallbackExecutor(); // Callback调用中用于执行Callback的Executor(可能是同步的)
 abstract RestAdapter.Log defaultLog(); // Log接口,用于输出Log

看完Platform的接口再看ensureSaneDefaults就清楚了,初始化转化数据的Converter、执行请求的Client、执行请求的Executor、执行Callback的Executor、Log输出类、错误处理类和用于在请求前添加额外处理的拦截请求的Interceptor。

Converter默认都是用的GsonConverter,就不看了,defaultClient返回执行网络请求的Client

Platform.Android

@Override Client.Provider defaultClient() {
 final Client client;
 if (hasOkHttpOnClasspath()) {
  client = OkClientInstantiator.instantiate();
 } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
  client = new AndroidApacheClient();
 } else {
  client = new UrlConnectionClient();
 }
 return new Client.Provider() {
  @Override public Client get() {
   return client;
  }
 };
}

Platform.Base

@Override Client.Provider defaultClient() {
 final Client client;
 if (hasOkHttpOnClasspath()) {
  client = OkClientInstantiator.instantiate();
 } else {
  client = new UrlConnectionClient();
 }
 return new Client.Provider() {
  @Override public Client get() {
   return client;
  }
 };
}

Platform.AppEngine

@Override Client.Provider defaultClient() {
 final UrlFetchClient client = new UrlFetchClient();
 return new Client.Provider() {
  @Override public Client get() {
   return client;
  }
 };
}

对于Android,优先使用OKHttp,否则2.3以后使用HttpUrlConnection,2.3以前使用HttpClient

defaultHttpExecutor就是返回一个Executor,执行请求的线程在这个Executor中执行,就做了一件事,把线程设置为后台线程

defaultCallbackExecutor用于执行Callback类型的请求时,提供一个Executor执行Callback的Runnable

Platform.Base

@Override Executor defaultCallbackExecutor() {
  return new Utils.SynchronousExecutor();
}

Platform.Android

@Override Executor defaultCallbackExecutor() {
  return new MainThreadExecutor();
}

SynchronousExecutor

static class SynchronousExecutor implements Executor {
  @Override public void execute(Runnable runnable) {
   runnable.run();
  }
}

MainThreadExecutor

public final class MainThreadExecutor implements Executor {
 private final Handler handler = new Handler(Looper.getMainLooper());

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

如果是Android,通过Handler将回调发送到主线程执行,如果非Android,直接同步执行。

Platform看完了,RestAdapter的成员初始化完成,就要看怎么通过RestAdapter.create生成我们定义的接口的实现类了

RestAdapter.create

 public <T> T create(Class<T> service) {
  Utils.validateServiceClass(service);
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new RestHandler(getMethodInfoCache(service)));
 }

 Map<Method, RestMethodInfo> getMethodInfoCache(Class<?> service) {
  synchronized (serviceMethodInfoCache) {
   Map<Method, RestMethodInfo> methodInfoCache = serviceMethodInfoCache.get(service);
   if (methodInfoCache == null) {
    methodInfoCache = new LinkedHashMap<Method, RestMethodInfo>();
    serviceMethodInfoCache.put(service, methodInfoCache);
   }
   return methodInfoCache;
  }
 }

使用了动态代理,InvocationHandler是RestHandler,RestHandler有一个参数,是Method->RestMethodInfo的映射,初始化时这个映射是空的。重点就是这两个了:RestHandler,RestMethodInfo,

@Override public Object invoke(Object proxy, Method method, final Object[] args)
  throws Throwable {
 // If the method is a method from Object then defer to normal invocation.
 if (method.getDeclaringClass() == Object.class) { // 1
  return method.invoke(this, args);
 }

 // Load or create the details cache for the current method.
 final RestMethodInfo methodInfo = getMethodInfo(methodDetailsCache, method); // 2

 if (methodInfo.isSynchronous) { // 3
  try {
   return invokeRequest(requestInterceptor, methodInfo, args);
  } catch (RetrofitError error) {
   Throwable newError = errorHandler.handleError(error);
   if (newError == null) {
    throw new IllegalStateException("Error handler returned null for wrapped exception.",
      error);
   }
   throw newError;
  }
 }

 if (httpExecutor == null || callbackExecutor == null) {
  throw new IllegalStateException("Asynchronous invocation requires calling setExecutors.");
 }

 // Apply the interceptor synchronously, recording the interception so we can replay it later.
 // This way we still defer argument serialization to the background thread.
 final RequestInterceptorTape interceptorTape = new RequestInterceptorTape();
 requestInterceptor.intercept(interceptorTape); // 4

 if (methodInfo.isObservable) { // 5
  if (rxSupport == null) {
   if (Platform.HAS_RX_JAVA) {
    rxSupport = new RxSupport(httpExecutor, errorHandler);
   } else {
    throw new IllegalStateException("Observable method found but no RxJava on classpath");
   }
  }

  return rxSupport.createRequestObservable(new Callable<ResponseWrapper>() {
   @Override public ResponseWrapper call() throws Exception {
    return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args);
   }
  });
 }

 Callback<?> callback = (Callback<?>) args[args.length - 1]; // 6
 httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor, errorHandler) {
  @Override public ResponseWrapper obtainResponse() {
   return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args);
  }
 });

 return null; // Asynchronous methods should have return type of void.
}

执行请求时会调用RestHandler的invoke方法,如上所示,主要是上面代码中标注有6点

1.如果调用的是Object的方法,不做处理直接调用。
2.通过getMethodInfo获取调用的Method对应的RestMethodInfo,前面说了,构造RestHandler对象时传进来了一个Method->RestMethodInfo的映射,初始时是空的。

static RestMethodInfo getMethodInfo(Map<Method, RestMethodInfo> cache, Method method) {
  synchronized (cache) {
   RestMethodInfo methodInfo = cache.get(method);
   if (methodInfo == null) {
    methodInfo = new RestMethodInfo(method);
    cache.put(method, methodInfo);
   }
   return methodInfo;
  }

在getMethodInfo中判断如果相应的映射不存在,就建立这个映射,并如名字所示缓存起来

3. 如果是同步调用(接口中直接返回数据,不通过Callback或Observe),直接调用invokeRequest

4. 如果是非同步调用,先通过RequestInterceptorTape记录拦截请求,记录后在后台线程做实际拦截,后面会提到。

5. 如果是Observe请求(RxJava),执行第5步,对RxJava不了解,略过

6. 如果是Callback形式,交由线程池执行

接口中的每一个Method有一个对应的RestMethodInfo,关于接口中Annotation信息的处理就都在这里了

RestMethodInfo

private enum ResponseType {
  VOID,
  OBSERVABLE,
  OBJECT
}
RestMethodInfo(Method method) {
  this.method = method;
  responseType = parseResponseType();
  isSynchronous = (responseType == ResponseType.OBJECT);
  isObservable = (responseType == ResponseType.OBSERVABLE);
}

在构造函数中调用了parseResponseType,parseResponseType解析了方法签名,根据方法的返回值类型及最后一个参数的类型判断方法的类型是哪种ResponseType

无论是哪种ResponseType,最终都是调用invokeRequest执行实际的请求,接下来依次看下invokeRequest的执行步骤

RestAdapter.invokeRequest

第一步是调用methodInfo.init()解析调用的方法,方法里有做判断,只在第一次调用时解析,因为处一次解析后这个对象就被缓存起来了,下次调同一个方法时可以直接使用

 synchronized void init() {
  if (loaded) return;

  parseMethodAnnotations();
  parseParameters();

  loaded = true;
 }

在RestMethodInfo.init中分别调用

  • parseMethodAnnotations():解析所有方法的Annotation
  • parseParameters():解析所有参数的Annotation
for (Annotation methodAnnotation : method.getAnnotations()) {
 Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
 RestMethod methodInfo = null;
 // Look for a @RestMethod annotation on the parameter annotation indicating request method.
 for (Annotation innerAnnotation : annotationType.getAnnotations()) {
  if (RestMethod.class == innerAnnotation.annotationType()) {
   methodInfo = (RestMethod) innerAnnotation;
   break;
  }
 }
 ...
}

在parseMethodAnnotations中,会获取方法所有的Annotation并遍历:

  • 对于每一个Annotation,也会获取它的Annotation,看它是否是被RestMethod注解的Annotation,如果是,说明是@GET,@POST类型的注解,就调用parsePath解析请求的Url,requestParam(URL中问号后的内容)及Url中需要替换的参数名(Url中大括号括起来的部分)
  • 寻找Headers Annotation解析Header参数
  • 解析RequestType:SIMPLE,MULTIPART,FORM_URL_ENCODED

parseParameters解析请求参数,即参数的Annotation,@PATH、@HEADER、@FIELD等

第二步是RequestBuilder和Interceptor,这两个是有关联的,所以一起看。

RequestBuilder requestBuilder = new RequestBuilder(serverUrl, methodInfo, converter);
requestBuilder.setArguments(args);
requestInterceptor.intercept(requestBuilder);
Request request = requestBuilder.build();

先说RequestInterceptor,作用很明显,当执行请求时拦截请求以做一些特殊处理,比如添加一些额外的请求参数。

/** Intercept every request before it is executed in order to add additional data. */
public interface RequestInterceptor {
 /** Called for every request. Add data using methods on the supplied {@link RequestFacade}. */
 void intercept(RequestFacade request);

 interface RequestFacade {
  void addHeader(String name, String value);
  void addPathParam(String name, String value);
  void addEncodedPathParam(String name, String value);
  void addQueryParam(String name, String value);
  void addEncodedQueryParam(String name, String value);
 }

 /** A {@link RequestInterceptor} which does no modification of requests. */
 RequestInterceptor NONE = new RequestInterceptor() {
  @Override public void intercept(RequestFacade request) {
   // Do nothing.
  }
 };
}

RequestInterceptor只有一个方法intercept,接收一个RequestFacade参数,RequestFacade是RequestInterceptor内部的一个接口,这个接口的方法就是添加请求参数,Query、Header什么的。大概可以看出RequestInterceptor的作用了,如果RequestFacade表示一个请求相关的数据,RequestInteceptor.intercept的作用就是向这个RequestFacade中添加额外Header,Param等参数。

RequestFacade的一个子类叫RequestBuilder,用来处理Request请求参数,在invokeRequest中会对RequestBuilder调用intercept方法向RequestBuilder添加额外的参数。

有一个叫RequestInterceptorTape的类,同时实现了RequestFacade与RequestInterceptor,它的作用是:

  • 当作为RequestFacade使用时作为参数传给一个RequestInteceptor,这个RequestInterceptor调用它的addHeader等方法时,它把这些调用及参数记录下来
  • 然后作为RequestInterceptor使用时,将之前记录的方法调用及参数重新应用到它的intercept参数RequestFacade中

在RestHandler.invoke中,如果判断方法的调用不是同步调用,就通过下面的两行代码将用户设置的interceptor需要添加的参数记录到RequestInterceptorTape,然后在invokeRequest中再实际执行参数的添加。

// Apply the interceptor synchronously, recording the interception so we can replay it later.
// This way we still defer argument serialization to the background thread.
final RequestInterceptorTape interceptorTape = new RequestInterceptorTape();
requestInterceptor.intercept(interceptorTape);

RequestBuilder.setArguments()解析调用接口时的实际参数。然后通过build()方法生成一个Request对象

第三步执行请求,Response response = clientProvider.get().execute(request);

第四步就是解析并分发请求结果了,成功请求时返回结果,解析失败调用ErrorHandler给用户一个自定义异常的机会,但最终都是通过异常抛出到invoke()中的,如果是同步调用,直接抛异常,如果是Callback调用,会回调Callback.failure

CallbackRunnable

请求类型有同步请求,Callback请求,Observable请求,来看下Callback请求:

Callback<?> callback = (Callback<?>) args[args.length - 1];
httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor, errorHandler) {
  @Override public ResponseWrapper obtainResponse() {
   return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args);
  }
});

Callback请求中函数最后一个参数是一个Callback的实例,httpExecutor是一个Executor,用于执行Runnable请求,我们看到,这里new了一个CallbackRunnable执行,并实现了它的obtainResponse方法,看实现:

abstract class CallbackRunnable<T> implements Runnable {
 private final Callback<T> callback;
 private final Executor callbackExecutor;
 private final ErrorHandler errorHandler;

 CallbackRunnable(Callback<T> callback, Executor callbackExecutor, ErrorHandler errorHandler) {
  this.callback = callback;
  this.callbackExecutor = callbackExecutor;
  this.errorHandler = errorHandler;
 }

 @SuppressWarnings("unchecked")
 @Override public final void run() {
  try {
   final ResponseWrapper wrapper = obtainResponse();
   callbackExecutor.execute(new Runnable() {
    @Override public void run() {
     callback.success((T) wrapper.responseBody, wrapper.response);
    }
   });
  } catch (RetrofitError e) {
   Throwable cause = errorHandler.handleError(e);
   final RetrofitError handled = cause == e ? e : unexpectedError(e.getUrl(), cause);
   callbackExecutor.execute(new Runnable() {
    @Override public void run() {
     callback.failure(handled);
    }
   });
  }
 }

 public abstract ResponseWrapper obtainResponse();
}

就是一个普通的Runnable,在run方法中首先执行obtailResponse,从名字可以看到是执行请求返回Response,这个从前面可以看到执行了invokeRequest,和同步调用中一样执行请求。

紧接着就提交了一个Runnable至callbackExecutor,在看Platform时看到了callbackExecotor是通过Platform.get().defaultCallbackExecutor()返回的,Android中是向主线程的一个Handler发消息

值得注意的事,对于同步调用,如果遇到错误是直接抛异常,而对于异步调用,是调用Callback.failure()

Mime

执行网络请求,需要向服务端发送请求参数,如表单数据,上传的文件等,同样需要解析服务端返回的数据,在Retrofit中对这些做了封装,位于Mime包中,也只有封装了,才好统一由指定的Converter执行数据的转换

TypedInput和TypedOutput表示输入输出的数据,都包含mimeType,并分别支持读入一个InputStream或写到一个OutputStrem

/**
 * Binary data with an associated mime type.
 *
 * @author Jake Wharton (jw@squareup.com)
 */
public interface TypedInput {

 /** Returns the mime type. */
 String mimeType();

 /** Length in bytes. Returns {@code -1} if length is unknown. */
 long length();

 /**
  * Read bytes as stream. Unless otherwise specified, this method may only be called once. It is
  * the responsibility of the caller to close the stream.
  */
 InputStream in() throws IOException;
}

/**
 * Binary data with an associated mime type.
 *
 * @author Bob Lee (bob@squareup.com)
 */
public interface TypedOutput {
 /** Original filename.
  *
  * Used only for multipart requests, may be null. */
 String fileName();

 /** Returns the mime type. */
 String mimeType();

 /** Length in bytes or -1 if unknown. */
 long length();

 /** Writes these bytes to the given output stream. */
 void writeTo(OutputStream out) throws IOException;
}

TypedByteArray,内部数据是一个Byte数组

private final byte[] bytes;

 @Override public long length() {
  return bytes.length;
 }

 @Override public void writeTo(OutputStream out) throws IOException {
  out.write(bytes);
 }

 @Override public InputStream in() throws IOException {
  return new ByteArrayInputStream(bytes);
 }

TypedString,继承自TypedByteArray,内部表示是一样的

public TypedString(String string) {
  super("text/plain; charset=UTF-8", convertToBytes(string));
 }

 private static byte[] convertToBytes(String string) {
  try {
   return string.getBytes("UTF-8");
  } catch (UnsupportedEncodingException e) {
   throw new RuntimeException(e);
  }
 }

其他的也一样,从名字很好理解:TypedFile,MultipartTypedOutput,FormEncodedTypedOutput。

其他

Retrofit对输入和输出做了封装,通过TypedOutput向服务器发送数据,通过TypedInput读取服务器返回的数据。

通过MultipartTypedOutput支持文件上传,读取服务器数据时,如果要求直接返回未解析的Response,Restonse会被转换为TypedByteArray,所以不能是大文件类的

Retrofit支持不同的Log等级,当为LogLevel.Full时会把Request及Response的Body打印出来,所以如果包含文件就不行了。

Retrofit默认使用GsonConverter,所以要想获取原始数据不要Retrofit解析,要么自定义Conveter,要么直接返回Response了,返回Response也比较麻烦

总体来说Retrofit看起来很好用,不过要求服务端返回数据最好要规范,不然如果请求成功返回一种数据结构,请求失败返回另一种数据结构,不好用Converter解析,接口的定义也不好定义,除非都返回Response,或自定义Converter所有接口都返回String

在Twitter上JakeWharton这么说:

Gearing up towards a Retrofit 1.6.0 release and then branching 1.x so we can push master towards a 2.0 and fix long-standing design issues.

要出2.0了,内部API会改,接口应该不怎么变

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 简略分析Android的Retrofit应用开发框架源码

    面对一个项目,对于Android应用开发框架的选择,我想过三种方案: 1.使用Loader + HttpClient + GreenDao + Gson + Fragment,优点是可定制性强,由于使用Google家自己的Loader和LoaderManager,代码健壮性强. 缺点是整套代码学习成本较高,使用过程中样板代码较多,(比如每一个Request都需要产生一个新类) 2.Volley,作为Google在IO大会上得瑟过的一个网络库,其实不算什么新东西(2013 IO发布),使用较为简单

  • Android中Retrofit+OkHttp进行HTTP网络编程的使用指南

    Retrofit介绍: Retrofit(GitHub主页https://github.com/square/okhttp)和OkHttp师出同门,也是Square的开源库,它是一个类型安全的网络请求库,Retrofit简化了网络请求流程,基于OkHtttp做了封装,解耦的更彻底:比方说通过注解来配置请求参数,通过工厂来生成CallAdapter,Converter,你可以使用不同的请求适配器(CallAdapter), 比方说RxJava,Java8, Guava.你可以使用不同的反序列化工具

  • Android Retrofit 2.0框架上传图片解决方案

    本文为大家分享了 Android Retrofit 2.0框架上传图片解决方案,具体内容如下 1.单张图片的上传 /** * 上传一张图片 * @param description * @param imgs * @return */ @Multipart @POST("/upload") Call<String> uploadImage(@Part("fileName") String description, @Part("file\&qu

  • Android中的Retrofit+OkHttp+RxJava缓存架构使用

    RxJava如何与Retrofit结合 先扔出build.gradle文件的内容 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.0' compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxand

  • Android使用Retrofit2.0技术仿微信发说说

    最近项目做完了,有闲暇时间,一直想做一个类似微信中微信发说说,既能实现拍照,选图库,多图案上传的案例,目前好多App都有类似微信朋友圈的功能,能过发表说说等附带图片上传.下面的就是实现该功能的过程:大家还没有看过Android Retrofit 2.0框架上传图片解决方案(一张与多张的处理)这篇文章,在看今天的就很容易,接在本项目中用到了一个library:photopicker,封装了图片的选择功能,是否选相机,还有选中图片后可以查看图片的功能. 一. 首先:将photopicker到工程中

  • Android中okhttp3.4.1+retrofit2.1.0实现离线缓存

    关于Retrofit+OkHttp的强大这里就不多说了,还没了解的同学可以自行去百度.这篇文章主要讲如何利用Retrofit+OkHttp来实现一个较为简单的缓存策略: 即有网环境下我们请求数据时,如果没有缓存或者缓存过期了,就去服务器拿数据,并且将新缓存保存下来,如果有缓存而且没有过期,则直接使用缓存.无网环境下我们请求数据时,缓存没过期则直接使用缓存,缓存过期了则无法使用,需要重新联网获取服务器数据. 缓存处理还是很有必要的,它有效的减少服务器负荷,降低延迟提升用户体验,同时也方便用户即使在

  • Android app开发中Retrofit框架的初步上手使用

    Retrofit 2.0 先来说一下Retrofit 2.0版本中一些引人注意的地方. 在Retrofit 2.0中,最大的改动莫过于减小库的体积,首先,Retrofit 2.0去掉了对所有的HTTP客户端的兼容,而钟情于OkHttpClient一个,极大地减少了各种适配代码,原因一会儿说;其次,拆库,比如将对RxJava的支持设置为可选(需要额外引入库):再比如将各个序列化反序列化转换器支持设置为可选(需要额外引入库).于2.0抛弃HttpClient和HttpURLConnection,为了

  • Android Retrofit文件下载进度显示问题的解决方法

    综述 在Retrofit2.0使用详解这篇文章中详细介绍了retrofit的用法.并且在retrofit中我们可以通过ResponseBody进行对文件的下载.但是在retrofit中并没有为我们提供显示下载进度的接口.在项目中,若是用户下载一个文件,无法实时给用户显示下载进度,这样用户的体验也是非常差的.那么下面就介绍一下在retrofit用于文件的下载如何实时跟踪下载进度. 演示 Retrofit文件下载进度更新的实现 在retrofit2.0中他依赖于Okhttp,所以如果我们需要解决这个

  • Android使用Retrofit仿微信多张图片拍照上传

    Android 仿照微信发说说,既能实现拍照,选图库,多图案上传,使用Retrofit技术. 使用方法:详见http://www.jb51.net/article/103009.htm 项目的运行效果: 服务器端接收文件的action UploadFile.java @Controller public class UploadFile extends ActionSupport { /** * */ private static final long serialVersionUID = 1L

  • 浅谈Android开发系列网络篇之Retrofit

    Retrofit是一个不错的网络请求库,用官方自己的介绍就是: A type-safe REST client for Android and Java 看官网的介绍用起来很省事,不过如果不了解它是怎么实现的也不太敢用,不然出问题了就不知道怎么办了.这几天比较闲就下下来看了一下,了解一下大概实现方法,细节就不追究了.先来看一个官网的例子,详细说明去网官看 简单示例 首先定义请求接口,即程序中都需要什么请求操作 public interface GitHubService { @GET("/use

  • 浅谈Android开发中ListView控件性能的一些优化方法

    ListView优化一直是一个老生常谈的问题,不管是面试还是平常的开发中,ListView永远不会被忽略掉,那么这篇文章我们来看看如何最大化的优化ListView的性能. 1.在adapter中的getView方法中尽量少使用逻辑 2.尽最大可能避免GC 3.滑动的时候不加载图片 4.将ListView的scrollingCache和animateCache设置为false 5.item的布局层级越少越好 6.使用ViewHolder 下面就具体来看一些 1.在adapter中的getView方

  • 浅谈Android开发中项目的文件结构及规范化部署建议

    一.几句话 使用Gradle及其推荐的项目框架 把密码等敏感数据放入gradle.properties 不要自己写Http客户端,使用Volley或OkHttp库 使用Jackson库来解析JSON数据 避免Guava并出于Dalvik 65K methods limit不要使用过多的库 使用Fragment来绘制UI界面 Activity主要用来管理Fragment 布局文件XML也是代码,好好组织它们 在布局文件里,使用styles以避免重复的属性 使用多个style文件而不是一个巨大的st

  • 浅谈Android Studio3.6 更新功能

    前言 下载google CodeLab的程序时,提示要更新3.6版本才能运行程序,于是更新了一下,看看有什么新功能. 界面设计工具 这次更新了一些设计工具,比如Layout Editor 和 Resource Manager. 现在,在XML或设计工具的颜色选择器中,Android Studio会在您的应用程序中填充颜色资源,以便您快速选择和替换颜色资源值. 拆分视图并放大设计编辑器 设计编辑器(例如,布局编辑器和导航编辑器)现在提供一个拆分视图,使您可以同时查看UI的"设计"视图和&

  • 浅谈Android View绘制三大流程探索及常见问题

    View绘制的三大流程,指的是measure(测量).layout(布局).draw(绘制) measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/高和四个顶点在手机界面上的位置,等通过measure和layout过程确定了View的宽高和要显示的位置后,就会执行draw绘制View的内容到手机屏幕上. 在详细介绍这三大流程之前,需要简单了解一下ViewRootImpl,View绘制的三大步骤

  • 浅谈Android中Service的注册方式及使用

    Service通常总是称之为"后台服务",其中"后台"一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件: 1.并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的): 2.具有较长时间的运行特性. 1.Service AndroidManifest.xml 声明 一般而言,从Service的启动方式上

  • 浅谈Android Studio 4.1 更新内容

    概览 Android Studio 4.1 目前已经发布,该版本共修复了2370 个 bug 以及 275 个 issue,主要包含如下新增功能: 设计 Material Design 组件库的更新 开发 Database Inspector 功能 直接在 Android Studio 中运行模拟器 Dagger 导航支持 使用 TensorFlow Lite 模型 构建与测试 Android 模拟器支持折叠屏 Apply Changes 更新 从 AAR 中导出 C/C++ 中的依赖 Nati

  • 浅谈Java开发中的安全编码问题

    1 - 输入校验 编码原则:针对各种语言本身的保留字符,做到数据与代码相分离. 1.1 SQL 注入防范 严重性高,可能性低. (1) 参数校验,拦截非法参数(推荐白名单): public String sanitizeUser(String username) { return Pattern.matches("[A-Za-z0-9_]+", username) ? username : "unauthorized user"; } (2) 使用预编译: Stri

  • 浅谈Android中适配器的notifyDataSetChanged()为何有时不刷新

    学过Android开发的人都知道,ListView控件在开发中经常遇到,并且ListView通常结合Adapter适配器来进行数据显示和数据更新操作.姑且假设数据存储在名为dataList的成员变量中.数据操作无非是增加数据.删除数据这两种主要的操作,而当数据有所变化时,为了及时向用户提供更新后的数据,我们知道需要在数据更新后调用适配器的notifyDataSetChanged()方法,来显示更新后的数据.殊不知,该方法并非百试不爽,在此我们便来讨论下具体的原因,其实本质是关注内存的分配情况.

  • 浅谈Android插件化

    目录 一.认识插件化 1.1 插件化起源 1.2 插件化优点 1.3 与组件化的区别 二.插件化的技术难点 三.ClassLoader Injection 3.1 java 中的 ClassLoader 3.2 android 中的 ClassLoader 3.3 双亲委派机制 3.4 如何加载插件中的类 3.5 执行插件类的方法 四.Runtime Container 4.1 为什么没有注册的 Activity 不能和系统交互 4.2 运行时容器技术 4.3 字节码替换 五.Resource

随机推荐