【腾讯Bugly干货分享】Android ListView 与 RecyclerView 对比浅析—缓存机制
ConstraintLayout 完全解析 快来优化你的布局吧
【凯子哥带你学Framework】Activity启动过程全解析
Activity启动流程
一共涉及到了四个进程,这四个进程分别是
Launcher
进程system_server
进程zygote
进程app
进程类说明:
ActivityManagerServices
,简称AMS,服务端对象,负责系统中所有Activity的生命周期ActivityThread
,App的真正入口。当开启App之后,会调用main()开始运行,开启消息循环队列,这就是传说中的UI线程或者叫主线程。与ActivityManagerServices配合,一起完成Activity的管理工作ApplicationThread
,用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通讯。ApplicationThreadProxy
,是ApplicationThread在服务器端的代理,负责和客户端的ApplicationThread通讯。AMS就是通过该代理与ActivityThread进行通信的。Instrumentation
,每一个应用程序只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。ActivityStack
,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。ActivityRecord
,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。TaskRecord
,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。启动流程分析:
Launcher
进程中点击桌面App图标,onclick事件触发,调用startActivity
,在该方法中会通过instrumention
采用Binder
通信向system_server
(AMS)进程发起startActivity请求;
调用startActivity(intent)方法,该方法在Activity类实现。startActivity调用的还是startActivityForResult()
system_server
进程接收到请求后,向zygote
进程发送创建进程的请求;
Zygote
进程fork
出新的子进程,即App
进程;
App进程,通过Binder IPC向sytem_server
进程发起attachApplication
请求;
system_server
进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity
请求;
App进程的binder线程(ApplicationThread
)在收到请求后,通过handler
向主线程发送LAUNCH_ACTIVITY
消息;
主线程在收到Message
后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
参考文档
Android系统底层基于Linux Kernel
, 当Kernel启动过程会创建init
进程, 该进程是所有用户空间的鼻祖, init进程会启动ServiceManager
(Binder
服务管家)、 Zygote
进程(Java进程的鼻祖)。Zygote进程会创建 system_server
进程以及各种App
进程。
Zygote进程:是Android系统的首个Java进程
,Zygote是所有Java进程的父进程,包括 system_server进程以及所有的App进程都是Zygote的子进程,注意这里说的是子进程,而非子线程。
在zygote开启的时候,会调用ZygoteInit.main()进行初始化
public static void main(String argv[]) {
...ignore some code...
//在加载首个zygote的时候,会传入初始化参数,使得startSystemServer = true
boolean startSystemServer = false;
for (int i = 1; i < argv.length; i++) {
if ("start-system-server".equals(argv[i])) {
startSystemServer = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
socketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
...ignore some code...
//开始fork我们的SystemServer进程
if (startSystemServer) {
startSystemServer(abiList, socketName);
}
...ignore some code...
}
负责系统中所有Activity的生命周期
。SystemServer进程开启
的时候,就会初始化ActivityManagerService。从下面的代码中可以看到public final class SystemServer {
//zygote的主入口
public static void main(String[] args) {
new SystemServer().run();
}
public SystemServer() {
// Check for factory test mode.
mFactoryTestMode = FactoryTest.getMode();
}
private void run() {
...ignore some code...
//加载本地系统服务库,并进行初始化
System.loadLibrary("android_servers");
nativeInit();
// 创建系统上下文
createSystemContext();
//初始化SystemServiceManager对象,下面的系统服务开启都需要调用SystemServiceManager.startService(Class),这个方法通过反射来启动对应的服务
mSystemServiceManager = new SystemServiceManager(mSystemContext);
//开启服务
try {
startBootstrapServices();
startCoreServices();
startOtherServices();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
}
...ignore some code...
}
//初始化系统上下文对象mSystemContext,并设置默认的主题,mSystemContext实际上是一个ContextImpl对象。调用ActivityThread.systemMain()的时候,会调用ActivityThread.attach(true),而在attach()里面,则创建了Application对象,并调用了Application.onCreate()。
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
//在这里开启了几个核心的服务,因为这些服务之间相互依赖,所以都放在了这个方法里面。
private void startBootstrapServices() {
...ignore some code...
//初始化ActivityManagerService
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
//初始化PowerManagerService,因为其他服务需要依赖这个Service,因此需要尽快的初始化
mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
// 现在电源管理已经开启,ActivityManagerService负责电源管理功能
mActivityManagerService.initPowerManagement();
// 初始化DisplayManagerService
mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);
//初始化PackageManagerService
mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
...ignore some code...
}
}
createSystemContext
()创建系统上下文的时候,也已经完成了mSystemContext和ActivityThread的创建。注意,这是系统进程开启时的流程,在这之后,会开启系统的Launcher
程序,完成系统界面的加载与显示
。App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程,他们之间如何通信呢?(App与AMS通过Binder进行IPC通信,AMS(SystemServer进程)与zygote通过Socket进行IPC通信。
)
那么AMS有什么用呢?打开一个App的话,需要AMS去通知zygote进程,然后zygote会fork出一个子进程,除此之外,其实所有的Activity的开启、暂停、关闭都需要AMS来控制,所以我们说,AMS负责系统中所有Activity的生命周期
。
Launcher本质上也是一个应用程序,和我们的App一样,也是继承自Activity
public final class Launcher extends Activity
implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
View.OnTouchListener {
}
当我们点击快捷图标的时候:
public void onClick(View v) {
...ignore some code...
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
// Open shortcut
final Intent intent = ((ShortcutInfo) tag).intent;
int[] pos = new int[2];
v.getLocationOnScreen(pos);
intent.setSourceBounds(new Rect(pos[0], pos[1],
pos[0] + v.getWidth(), pos[1] + v.getHeight()));
//开始开启Activity咯~
boolean success = startActivitySafely(v, intent, tag);
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
mWaitingForResume.setStayPressed(true);
}
} else if (tag instanceof FolderInfo) {
//如果点击的是图标文件夹,就打开文件夹
if (v instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) v;
handleFolderClick(fi);
}
} else if (v == mAllAppsButton) {
...ignore some code...
}
}
会调用到:
Launcher.startActivitySafely()
boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
try {
success = startActivity(v, intent, tag);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
}
return success;
}
调用了startActivity(v, intent, tag)
boolean startActivity(View v, Intent intent, Object tag) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
boolean useLaunchAnimation = (v != null) &&
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
if (useLaunchAnimation) {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
startActivity(intent, opts.toBundle());
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(),
opts.toBundle());
}
} else {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
startActivity(intent);
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(), null);
}
}
return true;
} catch (SecurityException e) {
...
}
return false;
}
最后面就会调用:
Activity.startActivityForResult()
史上最详细的Android系统SystemUI 启动过程详细解析
当面试官问道:“Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?”你会怎么答呢?
先来一发灵魂拷问四连击:
addInterceptor
与addNetworkInterceptor
有什么区别?网络缓存
如何实现的?复用
?网络监控
?OkHttp的内部实现通过一个责任链模式
完成,将网络请求的各个阶段
封装到各个链条
中,实现了各层的解耦
。
OkHttp源码分析
核心类:
这个是整个OkHttp的核心管理类
,所有的内部逻辑和对象归OkHttpClient统一来管理,它通过Builder构造器生成,构造参数和类成员很多。
Request是我们发送请求封装类,内部有url
, header
, method
,body
等常见的参数
Response是请求的结果,包含code
, message
, header
,body
这两个类的定义是完全符合Http协议所定义的请求内容和响应内容。
OkHttp内部的线程池
进行);逻辑责任链
,并执行责任链相关的逻辑,直到获取结果。虽然OkHttpClient是整个OkHttp的核心管理类,但是真正发出请求并且组织逻辑的是RealCall类,它同时肩负了调度和责任链组织的两大重任
RealCall类并不复杂,有两个最重要的方法,execute
() 和 enqueue
(),一个是处理同步请求,一个是处理异步请求。跟进enqueue的源码后发现,它只是通过异步线程和callback做了一个异步调用的封装,最终逻辑还是会调用到execute()这个方法,然后调用了getResponseWithInterceptorChain
()获得请求结果。
#RealCall
fun getResponseWithInterceptorChain(): Response {
//创建拦截器数组
val interceptors = mutableListOf<Interceptor>()
//添加应用拦截器
interceptors += client.interceptors
//添加重试和重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
//添加桥接拦截器
interceptors += BridgeInterceptor(client.cookieJar)
//添加缓存拦截器
interceptors += CacheInterceptor(client.cache)
//添加连接拦截器
interceptors += ConnectInterceptor
if (!forWebSocket) {
//添加网络拦截器
interceptors += client.networkInterceptors
}
//添加请求拦截器
interceptors += CallServerInterceptor(forWebSocket)
//创建责任链
val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
...
try {
//启动责任链
val response = chain.proceed(originalRequest)
...
return response
} catch (e: IOException) {
...
}
}
不包括自定义的拦截器的话,系统默认存在五个拦截器。
重试规则:
retryOnConnectionFailure
参数设置为false,不进行重试ProtocolException
,SSLHandshakeException
等)BridageInterceptor 拦截器的功能如下:
长度
,内容编码
gzip
压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦cookie
Keep-Alive
是实现连接复用的必要步骤CacheInterceptor 拦截器的逻辑流程如下:
Request
尝试到Cache
中拿缓存,当然前提是OkHttpClient中配置了缓存,默认是不支持
的。禁止使用网络
,并且缓存又为空,则构建一个Response直接返回,注意返回码=504
304
,则使用缓存的Resposne。header
,再缓存body
。socket
连接和tls
连接
讲解太长,看这里吧
最终会获得 一个Exchange
的类,这个类有两个实现,一个是Http1ExchangeCodec
,一个是Http2Exchangecodec
,分别对应的是Http1协议和Http2协议。
传输http的头部
和body
数据
CallServerInterceptor由以下步骤组成:
request header
request body
,就向服务器发送response header
,先构造一个 Response
对象response body
,就在 3 的基础上加上 body 构造一个新的 Response 对象这里我们可以看到,核心工作都由 HttpCodec
对象完成,而 HttpCodec 实际上利用的是 Okio
,而 Okio 实际上还是用的 Socket
,只不过一层套一层,层数有点多。
二者通常的叫为应用拦截器
和网络拦截器
,从整个责任链路来看:
最先执行
的拦截器,也就是用户自己设置request属性后的原始请求ConnectInterceptor
和CallServerInterceptor
之间,此时网络链路已经准备好,只等待发送请求数据。首先,应用拦截器在
RetryAndFollowUpInterceptor
和CacheInterceptor
之前,所以一旦发生错误重试或者网络重定向,网络拦截器可能执行多次
,因为相当于进行了二次请求,但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了,因此会存在短路网络拦截器
的情况。
其次,除了CallServerInterceptor,每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法(
本地异常重试
)或者不调用proceed方法(中断),但是网络拦截器这层连接已经准备好,可且仅可调用一次proceed方法。
最后,从使用场景看,应用拦截器因为只会调用一次,通常用于
统计客户端的网络请求发起情况
;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据
。
这里涉及到HTTP缓存知识。
OKHttp内部使用Okio来实现缓存文件的读写
。
缓存文件分为CleanFiles和DirtyFiles,CleanFiles用于读,DirtyFiles用于写,他们都是数组,长度为2,表示两个文件,即缓存的请求头和请求体;同时记录了缓存的操作日志,记录在journalFile中。
开启缓存需要在OkHttpClient创建时设置一个Cache对象,并指定缓存目录和缓存大小,缓存系统内部使用LRU
作为缓存的淘汰算法。
最后需要注意的一点是,OKHttp默认只支持get请求的缓存
。
OkHttpClient, 可以通过 new OkHttpClient() 或 new OkHttpClient.Builder() 来创建对象, 但是—特别注意, OkHttpClient() 对象最好是共享的, 建议使用单例模式创建。 因为每个 OkHttpClient 对象都管理自己独有的线程池和连接池。
OKHttpClient 里面组合了很多的类对象。其实是将OKHttp的很多功能模块,全部包装进这个类中,让这个类单独提供对外的API,这种设计叫做外观模式(外观模式:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
)
OkHttpClient 比较复杂, 太多属性, 而且客户的组合需求多样化, 所以OKhttp使用建造者模式(Build模式:使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象
)
OkHttp3 的拦截器链中, 内置了5个默认的拦截器,分别用于重试、请求对象转换、缓存、链接、网络读写(责任链模式:为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。)
CacheInterceptor 实现了数据的选择策略, 来自网络还是来自本地? 这个场景也是比较契合策略模式场景, CacheInterceptor 需要一个策略提供者提供它一个策略(锦囊), CacheInterceptor 根据这个策略去选择走网络数据还是本地缓存。
EventBus核心其实就是三幅图,这三幅图涉及的是三个HashMap表,弄懂这三幅图那么EventBus就懂了。
onStart{
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent1(Event1 event) {
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent2(Event2 event) {
}
onStop{
EventBus.getDefault().register(this);
}
注册监听的是activity,称为subscriber
,在activity中监听了Event1和Event2两个事件.
一个Subscribe对应多个Event,Subsribe就是上面通过register方法注册的对象,比如activity。这幅图对应EventBus中一个Map结构:
private final Map
EventBus会在对象register时,使用反射
机制,遍历对象的方法,将带有@Subscribe
标签并且合法的方法加入到typesBySubscriber
。typesBySubscriber是HashMap形式,key是注册的对象本身,由于一个注册的对象中可能有多个监听事件,所以value是用list
保存的Event。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
}
上面的代码主要做:
要弄懂一个问题,EventBus是观察者模式,上面的activity也就是subscribe是订阅者,activity中的event是订阅事件,一个订阅者可以订阅多个事件
,移除一个订阅者的监听事件时,应该将其中所有的event的事件移除。 也就是说在反注册的时候,会通过Subsribe来查找到其中所有event
进行反注册。
这种表关系是event
和subsciption
的对应关系,比如在Android中多个activity可能会注册监听同一个event事件,所以在执行:
EventBus.getDefault().post(new Event1());
的时候所有注册监听了Event1的监听都会要会收到回调,看下subsciption的结构
subsciption中包含,订阅的事件
和订阅者本身
这是因为一个Event可能会有被多个subsribe订阅,所以有当执行post(Event)的时候会查找到所有订阅了Event事件的subscribe并调用其中的event方法。下面看下post方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
}
post和postSticky
主要都会调用到上面的方法,上面方法中subscriptionsByEventType.get(eventClass)就是通过event的类型找上面的表中找到对应的subscriptions进行通知的。
在看第三幅图之前思考一个问题,postSticky到底是怎么执行的?为什么先执行postSticky,后执行register还是可以监听到event事件? 先看postSticky代码:
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
原来执行postSticky的时候会将event.getclass和event保存起来,然后再看下subscribe代码:
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
POSTING
子线程发布->子线程接收,主线程发布->主线程接收
MAIN
子线程发布->主线程接收,主线程发布->主线程接收
BACKGROUND
主线程发布->子线程接收、子线程发布->子线程接收
ASYNC
主线程发布->创建新的子线程接收、子线程发布->创建新的子线程接收(两个子线程不同)
参考文档
Retrofit 是一个 RESTful
的 HTTP 网络请求框架的封装
。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp
,前者专注于接口的封装,后者专注于网络请求的高效,二者分工协作,宛如古人的『你耕地来我织布』,小日子别提多幸福了。
我们的应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url
等信息,之后由 OkHttp 完成后续的请求操作,在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,后者根据用户的需求对结果进行解析的过程。
Retrofitting
OkHttp
例子:
// 定义
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
// 构造
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
// 调用
Call<List<Repo>> repos = service.listRepos("octocat");
// 同步调用
List<Repo> data = repos.execute();
// 异步调用
repos.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
List<Repo> data = response.body();
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
t.printStackTrace();
}
});
支持 GET/POST/PUT/DELETE/HEAD/PATCH
发请求时,需要传入参数,Retrofit 通过注解的形式令 Http 请求的参数变得更加直接,而且类型安全。
@GET("/list")
Call<ResponseBody> list(@Query("page") int page);
Query 其实就是 Url 中 ‘?’ 后面的 key-value.
@GET("News")
Call<NewsBean> getItem(@QueryMap Map<String, String> map);
使用 POST 提交表单的场景是刚需了,怎么提呢?
@FormUrlEncoded
@POST("/")
Call<ResponseBody> example(
@Field("name") String name,
@Field("occupation") String occupation
);
@POST("/")
@FormUrlEncoded
Call<WeatherBeans> requestWeatherBeans(@FieldMap Map<String, String> fields);
这个是用来上传文件的。话说当年用 HttpClient 上传个文件老费劲了,一会儿编码不对,一会儿参数错误(也怪那时段位太低吧TT)。。。可是现在不同了,自从有了 Retrofit,妈妈再也不用担心文件上传费劲了~~~
public interface FileUploadService {
@Multipart
@POST("upload")
Call<ResponseBody> upload(@Part("description") RequestBody description,
@Part MultipartBody.Part file);
}
如果你需要上传文件,和我们前面的做法类似,定义一个接口方法,需要注意的是,这个方法不再有 @FormUrlEncoded 这个注解,而换成了 @Multipart,后面只需要在参数中增加 Part 就可以了。也许你会问,这里的 Part 和 Field 究竟有什么区别,其实从功能上讲,无非就是客户端向服务端发起请求携带参数的方式不同,并且前者可以携带的参数类型更加丰富,包括数据流。也正是因为这一点,我们可以通过这种方式来上传文件,下面我们就给出这个接口的使用方法:
//先创建 service
FileUploadService service = retrofit.create(FileUploadService.class);
//构建要上传的文件
File file = new File(filename);
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part body =
MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);
String descriptionString = "This is a description";
RequestBody description =
RequestBody.create(
MediaType.parse("multipart/form-data"), descriptionString);
Call<ResponseBody> call = service.upload(description, body);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call,
Response<ResponseBody> response) {
System.out.println("success");
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
public interface FileUploadService {
@Multipart
@POST("upload")
Call<ResponseBody> upload(@Part("description") RequestBody description,
//注意这里的参数 "aFile" 之前是在创建 MultipartBody.Part 的时候传入的
@Part("aFile") File file);
}
(默认使用 GsonRequestBodyConverter)接下来我们就自己实现一个 FileRequestBodyConverter:
static class FileRequestBodyConverterFactory extends Converter.Factory {
@Override
public Converter<File, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return new FileRequestBodyConverter();
}
}
static class FileRequestBodyConverter implements Converter<File, RequestBody> {
@Override
public RequestBody convert(File file) throws IOException {
return RequestBody.create(MediaType.parse("application/otcet-stream"), file);
}
}
addConverterFactory(new FileRequestBodyConverterFactory())
Retrofit 也支持自定义 ResponseBodyConverter(默认 GsonResponseBodyConverter
)。
问题来了,如果请求得到的 Json 字符串与返回值类型不对应,比如:
{
"err":0, "content":"This is a content.", "message":"OK"}
而实体类型为:
class Result{
int code;//等价于 err
String body;//等价于 content
String msg;//等价于 message
}
自定义Converter解决:
static class ArbitraryResponseBodyConverterFactory extends Converter.Factory{
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return super.responseBodyConverter(type, annotations, retrofit);
}
}
static class ArbitraryResponseBodyConverter implements Converter<ResponseBody, Result>{
@Override
public Result convert(ResponseBody value) throws IOException {
RawResult rawResult = new Gson().fromJson(value.string(), RawResult.class);
Result result = new Result();
result.body = rawResult.content;
result.code = rawResult.err;
result.msg = rawResult.message;
return result;
}
}
static class RawResult{
int err;
String content;
String message;
}
这里涉及到了动态代理,参考Java基础面试题整理。
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
//这里返回一个 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, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//DefaultMethod 是 Java 8 的概念,是定义在 interface 当中的有实现的方法
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//每一个接口最终实例化成一个 ServiceMethod,并且会缓存
ServiceMethod serviceMethod = loadServiceMethod(method);
//由此可见 Retrofit 与 OkHttp 完全耦合,不可分割
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
//下面这一句当中会发起请求,并解析服务端返回的结果
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
任意方法最终都会 实际上调用的是这里的 InvocationHandler.invoke 方法~~
实际上每一个 OkHttpCall 都对应于一个请求,它主要完成最基础的网络请求,而我们在接口的返回中看到的 Call 默认情况下就是 OkHttpCall
了,如果我们添加了自定义的 callAdapter
,那么它就会将 OkHttp 适配成我们需要的返回值,并返回给我们。
call接口定义:
public interface Call<T> extends Cloneable {
//同步发起请求
Response<T> execute() throws IOException;
//异步发起请求,结果通过回调返回
void enqueue(Callback<T> callback);
boolean isExecuted();
void cancel();
boolean isCanceled();
Call<T> clone();
//返回原始请求
Request request();
}
@Override public Response<T> execute() throws IOException {
//这个 call 是真正的 OkHttp 的 call,本质上 OkHttpCall 只是对它做了一层封装
okhttp3.Call call;
synchronized (this) {
//处理重复执行的逻辑
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else {
throw (RuntimeException) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException e) {
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
//发起请求,并解析结果
return parseResponse(call.execute());
}
OkHttpCall 其实也是封装了 okhttp3.Call,在这个方法中,我们通过 okhttp3.Call发起了请求。
parseResponse 主要完成了由 okhttp3.Response 向 retrofit.Response 的转换,同时也处理了对原始返回的解析:
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
//略掉一些代码
try {
//在这里完成了原始 Response 的解析,T 就是我们想要的结果,比如 GitHubService.listRepos 的 List
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
至此,我们就拿到了我们想要的数据~~
前面我们已经提到过 CallAdapter 的事儿,默认情况下,它并不会对 OkHttpCall 实例做任何处理:
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
... 毫不留情的省略一些代码 ...
return new CallAdapter<Call<?>>() {
... 省略一些代码 ...
@Override public <R> Call<R> adapt(Call<R> call) {
//看这里,直接把传入的 call 返回了
return call;
}
};
}
}
现在的需求是,我想要接入 RxJava,让接口的返回结果改为 Observable:
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
参考文献
Leakcanary原理浅析
相对于其他类型的性能指标,卡顿是能直接让用户产生视觉反馈的现象,比如App反应滞后于用户的操作,在严重的情况下会出现ANR。关乎用户体验的大事,是很容易遭到用户吐槽的
(真理真理)。因此,开发人员平时写代码时必须要时刻提醒自己不要落入卡顿的陷阱之中。
UI线程是基于queue中的message事件驱动的,事件 -> 执行 -> 下一个事件…,另一方面由于Android的帧率是60fps,也就是每16ms就会触发一次UI刷新,如果某个message的处理时间 > 16ms,就会导致接收到VSYNC信号的时候无法完成本次刷新操作,产生掉帧现象。
从本质上来讲,我们必须让UI线程的任何事件在16ms之内解决战斗
。
基于此,可能会导致卡顿的原因有三大类:
就是把一些耗时业务逻辑直接写在了UI线程中,比如计算密集型的复杂计算,庞大的MD5
计算,非对称RSA
解密等。一般情况下,开发人员都不会犯这种错误,因为能够直接意识到计算量很大,本身就有警醒的作用。
网络I/O 同步请求这种如果是在用以前比较老的网络库,比如URLConnection这种就需要开发人员自己来开启新的线程。开发者可能忘记开启子线程,又同时做了同步请求等待,导致卡顿的发生。但是现代网络库比如Okhttp,Retrofit已经帮我们准备好了线程池,一般不会再遇到
。
磁盘I/O 文件,数据库一般的文件和数据库操作,大家可能都会自觉的在子线程中操作。但是值得一提的是SharedPreference的存储和读取,根据sp的设计,创建的时候会开启子线程把整个文件全部加载进内存,加载完毕再通知主线程,如果读取尚未结束,此时想获取某个key的值,主线程就必须等待加载完毕为止
。
因此,如果你的sp文件比较大,那么会带来几个严重问题:
a)第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
b)解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。
c)这些key和value会永远存在于内存之中,不会被释放,占用大量内存。所以千万不要把庞大的key/value存在sp中,比如把复杂的json当value。
另外对于sp的存储,commit是同步操作,要在子线程中使用。而apply虽然是在子线程执行的,但是无节制地apply也会造成卡顿,原因是每次有系统消息发生的时候(handleStopActivity,handlePauseActivity)都会去检查已经提交的apply写操作是否完成,如果没有完成则阻塞主线程。
对于卡顿的分析手段,有很多工具可以使用,下面介绍几种。
1)TraceView
相比之下,TraceView是分析卡顿的神兵利器,它不仅能看出每个方法消耗的时间、发生次数,并且可以进行排序,直接从最耗时的方法开始处优化。
2)ANR-WatchDog
其原理简单来说就是开启一个子线程,设置tick = interval,然后每隔一个interval(可设置)就往UI线程queue中扔一个runnable,若UI线程没卡顿,则interval时间内会取出此runnable执行,即重置tick,那么下一个interval循环时根据检测此tick是否被重置来判断是否有卡顿发生。如果有,则打印此时的各个线程运行时的stack trace(可设置只打印主线程),以帮助定位。
3)AndroidPerformanceMonitor
是国人开发的一个检测卡顿的开源库,原名是BlockCanary
,可以设置卡顿检测时间,debug模式下检测到的卡顿可以通知展示(基本和LeakCanary一样),这个在开发自测时很有用。
利用系统在loop()方法里取出message前后进行了log打印这一特点,来重写Printer的println(String)方法,根据message处理前后的时间差,来判断是否发生了卡顿。
public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
而且这个工具在卡顿发生时,收集的信息还比较丰富,包括基本信息,耗时信息,CPU信息,堆栈信息等。
4)ANR trace.txt而对于ANR,每当测试跑monkey一晚下来,ANR必是log的重点关注对象,若存在ANR,测试肯定会开jira贴上log给开发解决。对于trace.txt的分析,有几个基本的点是需要重点关注的:
Android 内存优化总结&实践