糖葫芦


  • Startseite

  • Tags

  • Archiv

  • Suche

Activity启动总结

Veröffentlicht am 2020-10-20 |

在上一篇文章中,我们通过跟踪源码了解了 Activity 大致的启动流程 ,相信你也肯定看的晕头转向的,没关系,今天这篇文章是放松篇,帮你即使不看源码也能掌握。

其实就是一个跨进程通信的过程,从客户端进程到服务端进程,再最后转到客户端进程中去,来一张流程图更清晰。

tanghulu_img.png

Activity启动流程

Veröffentlicht am 2020-10-20 |

最近在研究启动优化方面的知识,那么对于 Activity 的启动过程这方面的知识自然是逃不掉的,那么废话不多说,我们开始梳理。

(文中基于sdk28, 不少有代码片段的地方,可能比较枯燥,我们只要把握主要流程即可~)

1. 启动

启动包括冷启动和热启动,本篇我们主要围绕冷启动做简述。(还不清楚冷启动和热启动的区别,就自行Google哦)

2. 点击一个应用图标

当我们在手机桌面开始点击一个应用图标的时候,此时应用就启动起来了,那么背后究竟发生了什么事,我们一起来看一下:

2.1 startActivity

Activity#startActivity

1
2
3
4
5
6
7
8
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {//不传 options为null
startActivityForResult(intent, -1, options);
} else {
startActivityForResult(intent, -1);
}
}

Activity#startActivityForResult

1
2
3
4
5
6
7
8
9
10
11
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar = -----------//注释1
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
....
}
}

注释1处: mInstrumentation 为 Instrumentation 类型,调用了 execStartActivity 方法,其中 mMainThread.getApplicationThread() 需要我们重点关注。

mMainThread —–> ActivityThread 类型
getApplicationThread() 返回 ApplicationThread 类型, 而 ApplicationThread 则是 ActivityThread 的一个内部类,大致如下:

1
2
3
4
5
6
7
8
9
10
11
class ActivityThread{
...
public ApplicationThread getApplicationThread()
{
return mAppThread;
}
private class ApplicationThread extends IApplicationThread.Stub {
.....
}
...
}

OK ,直到了 ApplicationThread 我们继续,调用了 Instrumentation.execStartActivity 我们需要点进去看一下:

2.2 Instrumentation#execStartActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread; //----->注释1
...

intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService() //------->注释2
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
...
return null;
}

注释1: 其中我们上面说的 ApplicationThread 被转化成 IApplicationThread 是一个 IBinder 类型 , 因为ApplicationThread继承了 IApplicationThread.Stub 是不是很眼熟,类似于我们那个aidl 自动生成的那个代码,你可以认为是代理类。

接着往下,注释2: ActivityManager.getService 返回 IActivityManager 类型 ,看下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

ServiceManager 获取到服务,其实就是我们的 ActivityManagerService 对象,它继承了IActivityManager.Stub 为具体实现类

1
2
3
public class ActivityManagerService extends IActivityManager.Stub..{
....
}

最终调用实际是 ActivityManagerService 的 startActiviy方法,通过获取远程服务获取到代理类,调用了远程服务,此时由客户端进程转换到远程服务端进程。

2.3 ActivityManagerService#startActivity

ActivityManagerService 这里简称 AMS,这里代码就比较多了,我们只看一些对流程上比较重要的方法:

1
2
3
4
5
6
7
8
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}

里面可以看到又调用了 startActivityAsUser() 方法,继续走:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,
boolean validateIncomingUser) {
...
// TODO: Switch to user app stacks here.
return mActivityStartController.obtainStarter(intent, "startActivityAsUser")
.setCaller(caller)
.setCallingPackage(callingPackage)
.setResolvedType(resolvedType)
.setResultTo(resultTo)
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setStartFlags(startFlags)
.setProfilerInfo(profilerInfo)
.setActivityOptions(bOptions)
.setMayWait(userId)
.execute();
}

只看到最后调用了一个 execute() 方法,那么看是谁的 execute() 方法, obtainStarter() 方法返回 ActivityStarter 类型的对象,那么就是它的,走进去看看:
ActivityStarter#execute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int execute() {
try {
if (mRequest.mayWait) {
return startActivityMayWait(mRequest.caller, mRequest.callingUid,
mRequest.callingPackage, mRequest.intent, mRequest.resolvedType,
mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
mRequest.resultWho, mRequest.requestCode, mRequest.startFlags,
mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig,
mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId,
mRequest.inTask, mRequest.reason,
mRequest.allowPendingRemoteAnimationRegistryLookup);
} else {
return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent,
mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo,
mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
mRequest.resultWho, mRequest.requestCode, mRequest.callingPid,
mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid,
mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions,
mRequest.ignoreTargetSecurity, mRequest.componentSpecified,
mRequest.outActivity, mRequest.inTask, mRequest.reason,
mRequest.allowPendingRemoteAnimationRegistryLookup);
}
} finally {
onExecutionComplete();
}
}

这个 mRequest.mayWait 为 true ,因为前面我们在启动的时候设置了:
image.png
image.png
所以我们就可以直接看 startActivityMayWait 这个方法了。

这个方法就比较长了,我看了好长时间,看到最后方法返回 res,自然而然就比较关注这个 res 赋值是什么,赋值我拿了出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 private int startActivityMayWait(IApplicationThread caller, int callingUid,
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, WaitResult outResult,
Configuration globalConfig, SafeActivityOptions options, boolean ignoreTargetSecurity,
int userId, TaskRecord inTask, String reason,
boolean allowPendingRemoteAnimationRegistryLookup) {
....
final ActivityRecord[] outRecord = new ActivityRecord[1];
int res = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo,
voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid,
callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options,
ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason,
allowPendingRemoteAnimationRegistryLookup);
return res;
....
}

这个 startActivity 继续调用调用:
image.png
方法里最终 return 又还是继续调用 startActivity 方法 :
image.png
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
int result = START_CANCELED;
try {
mService.mWindowManager.deferSurfaceLayout();
result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, doResume, options, inTask, outActivity);
} finally {
// If we are not able to proceed, disassociate the activity from the task. Leaving an
// activity in an incomplete state can lead to issues, such as performing operations
// without a window container.
final ActivityStack stack = mStartActivity.getStack();
if (!ActivityManager.isStartResultSuccessful(result) && stack != null) {
stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
null /* intentResultData */, "startActivity", true /* oomAdj */);
}
mService.mWindowManager.continueSurfaceLayout();
}

postStartActivityProcessing(r, result, mTargetStack);

return result;
}

因为最后返回 result 所以我们还是看 result 的赋值, 调用了 startActivityUnchecked 方法 最后返回 START_SUCCESS 应该就是最后的方法了吧,目测感觉应该要到头了,看了一下这个方法有点长:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
....
//把目标栈移动到前台
reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);

if (dontStart) {

topStack.mLastPausedActivity = null;
if (mDoResume) {
mSupervisor.resumeFocusedStackTopActivityLocked();//--------注释1
}
...
result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);//-------注释2
}
....



return START_SUCCESS;
}

注释1:ActivityStackSupervisor 负责将目标 Activity 放于栈顶 ;
注释2:复用或者创建新的任务栈 ;

2.4 ActivityStackSupervisor#resumeFocusedStackTopActivityLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
boolean resumeFocusedStackTopActivityLocked() {
return resumeFocusedStackTopActivityLocked(null, null, null);
}

boolean resumeFocusedStackTopActivityLocked(
ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

if (!readyToResume()) {
return false;
}

if (targetStack != null && isFocusedStack(targetStack)) {
return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
}

final ActivityRecord r = mFocusedStack.topRunningActivityLocked();
if (r == null || !r.isState(RESUMED)) {
mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
} else if (r.isState(RESUMED)) {
// Kick off any lingering app transitions form the MoveTaskToFront operation.
mFocusedStack.executeAppTransition(targetOptions);
}

return false;
}

result = resumeTopActivityInnerLocked(prev, options)
这个方法就比较长,参考其它资料,里面比较重要的就是:

1
2
3
4
5
6
7
8
9
10
11
12
.....
if (next.app != null && next.app.thread != null) {
//把要启动Activity进程信息封装在一个 ClientTransaction 对象中了
final ClientTransaction transaction = ClientTransaction.obtain(next.app.thread,
next.appToken);
//如果要启动的activity已经存在,就该resume了
mStackSupervisor.scheduleResumeTopActivities();
}else{
//如果进程不存在,就要启动新的Activity
mStackSupervisor.startSpecificActivityLocked(next, true, true);
}
....

假如是第一次启动 Activity 就执行 mStackSupervisor.startSpecificActivityLocked() 方法,继续跟进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);

getLaunchTimeTracker().setLaunchTime(r);

if (app != null && app.thread != null) {
try {
if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
|| !"android".equals(r.info.packageName)) {
// Don't add this if it is a platform component that is marked
// to run in multiple processes, because this is actually
// part of the framework so doesn't make sense to track as a
// separate apk in the process.
app.addPackage(r.info.packageName, r.info.applicationInfo.longVersionCode,
mService.mProcessStats);
}
realStartActivityLocked(r, app, andResume, checkConfig);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting activity "
+ r.intent.getComponent().flattenToShortString(), e);
}

// If a dead object exception was thrown -- fall through to
// restart the application.
}

mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false, true);
}

继续执行那就是 realStartActivityLocked() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {

....
// Create activity launch transaction. 创建启动Activity的事务
final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
r.appToken);
//回调callback 是 LaunchActivityItem
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, mService.isNextTransitionForward(),
profilerInfo));
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
clientTransaction.setLifecycleStateRequest(lifecycleItem);

// Schedule transaction. 最后开始执行了
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
....
}

这里可以看到生命周期的回调被封装成一个一个的 Item ,例如 : LaunchActivityItem , ResumeActivityItem , PauseActivityItem 光看名字也能大概猜出一点来。

这里 mService 就是 AMS , getLifecycleManager() 获取到 ClientLifecycleManager 对象 , 调用 scheduleTransaction 方法 ;

2.5 ClientLifecycleManager#scheduleTransaction

1
2
3
4
5
6
7
8
9
10
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
final IApplicationThread client = transaction.getClient();
transaction.schedule();
if (!(client instanceof Binder)) {
// If client is not an instance of Binder - it's a remote call and at this point it is
// safe to recycle the object. All objects used for local calls will be recycled after
// the transaction is executed on client in ActivityThread.
transaction.recycle();
}
}

2.6 ClientTransaction#schedule

1
2
3
public void schedule() throws RemoteException {
mClient.scheduleTransaction(this);
}

mClient 是 IApplicationThread 就是 ApplicationThread , 而 ApplicationThread 是 ActivityThread 的一个内部类,执行了 scheduleTransaction 方法。

此时你会发现,由原来的 Client 进程 ———> AMS进程 ,现在又回到了 客户端进程了。

Client 进程 ———> AMS进程 ———> Client 进程

2.7 ActivityThread#ApplicationThread # scheduleTransaction

1
2
3
4
5
6
public final class ActivityThread extends ClientTransactionHandler {
@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
}

因为 ActivityThread 继承了 ClientTransactionHandler ,在父类中实现了这个方法:

2.8 ClientTransactionHandler#scheduleTransaction

1
2
3
4
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}

终于看到点能看懂的代码了,开始切换主线程了,因为看到了 sendMessage , 哈哈。
发送了一个 ActivityThread.H.EXECUTE_TRANSACTION 消息 ,obj 为 transaction , 继续到 ActivityThread 中看下:

2.9 ActivityThread#mH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class ActivityThread extends ClientTransactionHandler{

//内部类H
class H extends Handler {
//很多消息类型,找到我们刚才发的消息类型 为159
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;

public static final int EXECUTE_TRANSACTION = 159;
........
//处理消息
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);

break;
}
}

找到对应的消息类型,开始处理消息,取出之前的 ClientTransaction , 并调用 其 execute 方法,继续跟进:

然后你会发现 在 ClientTransaction 类中并没有 execute 方法,因为前面我们说过把生命周期都封装成一个一个的 Item 了, 启动 Activity 是 LauncheActivityItem
所以我们应该去看 LaunchActivityItem

3.0 LaunchActivityItem#execute

该类继承了 ClientTransactionItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LaunchActivityItem extends ClientTransactionItem {

@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}

啊哈 ,好熟悉的代码,调用 handleLaunchActivity 方法,就是 ActivityThread 中的,继续走:

3.1 ActivityThread#handleLaunchActivity

1
2
3
4
5
6
7
8
9
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {

...
final Activity a = performLaunchActivity(r, customIntent);
....
return a;
}

3.2 ActivityThread#performLaunchActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

//创建ContextImpl
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;

java.lang.ClassLoader cl = appContext.getClassLoader();
//根据类名反射出要启动的Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
//创建Application对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
//创建Window对象
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
//调用 attach方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);

//调用Activity 的 onCreate() 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
}
1
2
3
4
5
6
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}

3.3 Activity#performCreate

1
2
3
4
5
6
7
8
9
10
11
12
13
final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
mCanEnterPictureInPicture = true;
restoreHasCurrentPermissionRequest(icicle);
if (persistentState != null) {
onCreate(icicle, persistentState); // 调用其 onCreate 方法
} else {
onCreate(icicle);
}
}

终于走到头了,看到了 onCreate() 方法,由于文章篇幅过长,总结放到下一篇,路过的朋友如果对你有帮助,能否为辛苦的糖葫芦点个赞👍再走啊~

内存泄漏排查方法

Veröffentlicht am 2019-05-24 |

2. 内存泄漏

2.1 点击Android studio 中 Profiler image.png 这样的一个图标

打开后,运行程序,点击 record , 反复进去目标 Activity ,点击强制 GC 最后点击 stop :

image.png

然后点击 Arrange by package :

image.png

找到自己的应用包名:

image.png

点击 image.png

类似这样的图标,dump hprof 文件 。

我用idea 开发工具打开后:

image.png

点击右上角那个运行按钮,出现以下:

image.png

然后继续点开 Leaked Activities 选项 :

image.png

这样我们就知道是 LeakActivity 发生了 泄漏 ,那如何去找到泄漏的原因需要使用 MAT 工具;

以上是检测泄漏 Activity 的方式之一,通常我也会在做这个之前,做一个小的判断,通过 adb 命令,判断内存情况,同样也是反复进出 目标Activity , 然后打印内存信息。

1
adb shell dumpsys meminfo "packageName| pid"

image.png

观察 Activities 的数量,反复进出目标Activity , 如果 Activities 只增加而不减少,那么目标 Activity 基本上就发生内存泄漏了。

2.2 MAT

借助 sdk 里 platform-tools 中的 hprof-conv.exe 进行hprof 文件转化,转化命令为:

1
2
//示例
hprof-conv -z test.hprof test1.hprof

文件名我就叫 test , 转化后为 test1.hprof , 转化后的文件用 Mat 工具打开:

image.png

然后点击左上角的这个按钮:

image.png

然后在显示的 Historgram 中输入之前泄漏的 Activity :

image.png

将搜索到的结果,右键,去除 软引用,弱引用,虚引用的对象:

image.png

筛选得出结果:

image.png

发现 LeakActivity 被这个 callback –MyRunnable 引用,发现原因进一步就可以解决了。

泄漏的LeakActivity.java 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class LeakActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_leak1);

handler.postDelayed(new MyRunnable(),10000);
}

private Handler handler = new Handler();

class MyRunnable implements Runnable{

@Override
public void run() {
Log.i("qq >>>","test");
}
}
}

常见adb命令

Veröffentlicht am 2019-04-28 |

1. 查看当前设备

1
adb devices

2. 查看连接设备的详细信息

1
adb devices -l

3.查看当前交互的Activity信息

1
2
adb shell;//进入adb shell 里
dumpsys activity | grep "mFoc"

二叉树

Veröffentlicht am 2019-04-23 |

1. 简述

一个节点有不超过2个子节点,超过2个是多路树,不属于二叉树。

2. 二叉搜索树

左节点比根节点小,比根节点大的为右节点;

2.1 二叉搜索树的遍历

分为前序,中序和后序,使用最多的是中序;

前序 : 根节点——->左节点———->右节点;

中序 : 左节点——-> 根节点———>右节点;

后序 : 左节点——> 右节点——–>根节点

示例代码:

Node.java

1
2
3
4
5
6
7
8
9
public class Node {
public int data;
public Node leftNode;
public Node rightNode;

public Node(int data) {
this.data = data;
}
}

BinarySearchTree.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
* 二叉搜索树(Binary Search Tree)
* 要求是:若他的左子树不为null,则左子树上的所有节点的值均小于根节点;
* 若右子树不为null,则右子树上的所有节点的值均大于根节点;
*/
public class BinarySearchTree implements ITree {

//根节点
private Node root;

@Override
public Node find(int key) {
//查找节点
Node current = root;
if(root.data == key){//如果刚好是根节点
return root;
}else{
while(current != null){
if(current.data > key){
//当前节点比查找值大,要去左测节点查找
current = current.leftNode;
}else if(current.data == key){
return current;
}else{
current = current.rightNode;
}
}
}
return null;
}

public Node getRoot(){
return root;
}
@Override
public boolean insert(int key) {
//要插入新的节点
Node newNode = new Node(key);
//如果没有根节点
if(root == null){
root = newNode;
return true;
}else{
//如果有根节点,需要判断与根节点的比较大小
Node current = root;
Node parentNode = null;
while(current!=null){
parentNode = current;
if(current.data > key){
//当前值比插入值大,需要搜索左节点
current = current.leftNode;
if(current == null){
parentNode.leftNode = newNode;
return true;
}
}else{
//找右节点
current = current.rightNode;
if(current == null){
parentNode.rightNode = newNode;
return true;
}
}
}
}
return false;
}

//前序
@Override
public void preOrder(Node current) {
//前序是:根节点-左节点-右节点
//默认从顶部进行遍历
if(current!= null){
System.out.println(current.data);
preOrder(current.leftNode);
preOrder(current.rightNode);
}
}
//中序
@Override
public void middleOrder(Node current) {
//中序:左节点-根节点-右节点 , 中序是最常用的
if(current!=null){
middleOrder(current.leftNode);
System.out.println(current.data);
middleOrder(current.rightNode);
}
}

//后序
@Override
public void postOrder(Node current) {
//后序:左节点-右节点-根节点
postOrder(current.leftNode);
postOrder(current.rightNode);
System.out.println(current.data);
}

//查找最小值
@Override
public Node findMin() {
//从根节点开始查找
Node currentNode = root;
while(currentNode != null){
if(currentNode.leftNode == null){
return currentNode;
}
currentNode = currentNode.leftNode;
}
return currentNode;
}

@Override
public Node findMax() {
//一直寻找右节点
Node currentNode = root;
Node maxNode = currentNode;
while (currentNode!=null){
maxNode = currentNode;
currentNode = currentNode.rightNode;
}
return maxNode;
}

//删除节点(最简单的做法是给节点加一个属性isDelete)
/**
* 1.该节点没有子节点
* 2.该节点只有1个子节点
* 3.该节点有2个节点(最复杂)
*/
@Override
public boolean delete(int deleteKey) {
//要删除节点首先要找到删除节点的父节点
// Node currentNode = root;
// Node parentNode = currentNode;
// boolean isLeftNode = false;
// while(currentNode != null){
// parentNode = currentNode;
// if(currentNode.data > deleteKey){
// isLeftNode = true;
// currentNode = currentNode.leftNode;
// }else{
// isLeftNode = false;
// currentNode = currentNode.rightNode;
// }
// }

return false;
}

Test.java

1
2
3
4
5
6
BinarySearchTree binarySearchTree = new BinarySearchTree();
binarySearchTree.insert(30);
binarySearchTree.insert(25);
binarySearchTree.insert(20);
binarySearchTree.insert(31);
binarySearchTree.middleOrder(binarySearchTree.getRoot());

Handler中的Printer

Veröffentlicht am 2019-03-13 |

在 Looper#loop() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void loop() {
final Looper me = myLooper();//这里主线程的Looper
...xxx
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
//给 UI 事件设置一个日志
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...xx
try {
//线程分发消息给Handler
//msg.target 指的是 Handler
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
....xx
}
....xx
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
....xx
}
}

这个 Printer 日志给我打印了主线程分发消息之前打印一个日志,分发完成之后又打印一条日志,分别是:

1
2
3
Dispatching to + 目标Handler + msg.what;

Finished to + 目标Handler,分发完成

我们知道这个在应用程序启动的入口 ActivityThread#main 方法,初始化了主线程的 Looper . 同时也有一个 Handler H , Activity 的生命周期就是通过向 H 发送消息来完成的。

Android 是消息驱动的,这大概就是这个意思吧。如果我们可以得知主线程每个message的消耗时间,可以间接用来分析每个方法的耗时,从而进行一个优化工作,可以给主线程的Looper 设置一个Printer, 如下:

1
2
3
4
5
6
Looper.myLooper().setMessageLogging(new Printer() {
@Override
public void println(String x) {
xxx...
}
});

如果有另外一条线程,记录我们执行前的堆栈信息,同时记录每次 dispatchMessage 方法耗时,算出二者之差,当超过某个时间的时候,认为该方法耗时过长,需要进行优化。到这里你可能想到了 BlockCannary 是的它基本上就是利用了这个原理,来分析优化的。

屏幕旋转后Activity生命周期

Veröffentlicht am 2019-03-12 |

主要针对屏幕旋转对 Activity 生命周期有何影响。

第一种情况

在没有其它配置的情况下,通过日志打印屏幕旋转会调用的方法。

1
//onPause()----onStop()-----onDestroy()-----onCreate() ---- onStart()---onResume()

第二种情况

关于 android:configChanges 的配置,不同的值对生命周期也会有不同的影响。这个值最常用的值包括: orientation 和 keyboardHidden, 分别用于避免因屏幕方向和可用键盘改变而导致的重启。

例如:

1
2
<activity android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden" / >

当其中一个配置发生变化时, MainActivity 不会重启。 但是会收到 onConfigurationChanged() 的调用。向此方法传递 Configuration 对象指定新设备配置。可以通过读取 Configuration 中的字段,确定新配置。然后通过更新界面中使用的资源进行适当的更改。调用此方法时,Activity 的 Resources 对象会相应的进行更新,以根据新配置返回资源,这样,就能够在系统不重启 Activity 的情况下轻松重置 UI 的元素。

但是,我的编译版本高一些,实验,发现当配置以上configChanges 还是会重启 Activity, 并且没有回调 onConfigurationChanged() 的回调

而后,开始查阅文档说明,发现:

1
注意: 在Android3.2(API 13)开始,当设备在纵向和横向之间切换时, “屏幕尺寸”也会发生改变,因此,在开发针对 API 13或者更高版本的应用时(当然我们现在基本都是4.0 以上),若要避免由于设备方向改变而导致运行时重启,则除了 "orientation" 值以外,还必须添加 "screenSize" 值, 也就是说,您必须声明:android:configChanges="orientation|screenSize".

当添加新的配置 screenSize 之后,屏幕旋转回调如下:

1
// onConfigurationChanged invoked... 只会调用onConfigurationChanged() 方法,并没有重启

最后就是一些资源的变更,如果有些图像应该在横向和纵向之间切换,就必须在 onConfigurationChanged() 期间将每个资源重新分配给每个元素。

文档地址:https://developer.android.com/guide/topics/resources/runtime-changes?hl=zh-cn

Handler中的IdleHandler

Veröffentlicht am 2019-03-07 |

1.1 IdleHandler 基本情况

IdleHandler 可以用来提升性能,主要用在我们希望能够在当前线程 消息队列空闲时 做些事情(例如UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。

IdleHandler 位于 MessageQueue 类中的一个静态接口,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
MessageQueue#IdleHandler
class MessageQueue{
...
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*
// 可以理解为消息暂时处理完的适合回调的
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
//返回true就是单次回调后不删除,下次进入空闲时继续回调该方法,false 只回调单次执行完之后会移除
boolean queueIdle();
}

//判断当前队列是不是空闲的,辅助方法
public boolean isIdle() {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
return mMessages == null || now < mMessages.when;
}
}

//添加一个IdleHandler 到空闲队列中,ArrayList 存储
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
// 删除一个IdleHandler
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}

//message 的获取下一条消息
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {//循环获取下一条消息
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//表示没有设置idle handler 去运行,就阻塞
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//设置长度,最小长度是4
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//将集合转化为数组
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// Run the idle handlers.
// We only ever reach this code block during the first iteration.
//循环遍历这个空闲队列数组
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
//回调取出返回值
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//这里看到了吧,如果是返回false, 那就进去了,执行操作后就从集合中remove了
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
....
}
}
...
}

1.2 使用场景

1
2
3
4
5
6
7
8
9
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//这里做一些操作
Log.i("xx","addIdleHandler invoked..."+iv.getWidth() + "::"+iv.getHeight());

return false;
}
});

我记得之前我们想要获取xml中某个控件的width 和 height 时,在 onCreate 方法中直接获取,会获取到是 0 , 因为这个 view 还未绘制完成,所以获取不到,当时的解决方案我记得是使用 Handler 发送一个延时消息获取,现在有更好的方式实现了,那就是通过 IdleHandler, 如上面代码所示。 当然还可以做一些其它预处理的简单操作。

性能优化二

Veröffentlicht am 2019-02-26 |

1. SVG

在项目 res 右键出来,New ---> Vector Asset ,SVG 图不会像位图一样因为缩放而使图片质量下降,优点在于节省空间和内存,常常用于一些简单的图标。

image.png

image.png

  • 如果是从外部导入SVG,需要也是右键res---new---Vector Asset ,需要注意的是:Asset Type 勾选的是: Local File, 需要注意的是这个psd图片不支持渐变和透明度,如下图:

    image.png

导入进来后,自动生成 xml .

这里提供一个 android 所有 自带的 svg 图片的地址: https://megatronking.github.io/SVG-Android/

批量转化 svg2vector 的工具,这里可以 下载 , 是一个 svg2vector-cli-1.0.1.jar. 这个是最新版本

操作命令是:

1
2
3
4
5
6
7

java -jar svg2vector-cli-1.0.1.jar -d (svg图片所在的目录) -o (输入目录生成vector的目录)
//指定宽和高
java -jar svg2vector-cli-1.0.1.jar -d D:\svg -o D:\vector -w 24 -h 24

例如:
java -jar svg2vector-cli-1.0.1.jar -d C:\Users\xuqianqian\Desktop\svg\images -o C:\Users\xuqianqian\Desktop\svg\vector-image
  • 将svg 图片生成指定目录的图片。

    需要在我们的app 的 build.gradle 文件中配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    android{
    defaultConfig{
    ...
    //将svg生成指定目录的png, 下面可指定目录
    vectorDrawables.generatedDensities('hdpi','xhdpi')
    ....
    }
    }
    //在布局中用法
    android:src="@drawable/eye"

    配置完成后,我们可以点击 Build APK(s) ,观察生成的apk 里面的文件:

    image.png

image.png

我们在我们指定的目录下drawable-hdpi 和 drawable-xhdpi发现了 我们svg 图片生成了对应的 png 图片。这样的好处是我们可以避免配置多套图在每个分辨率下,用 svg 图可以自动帮我们生成指定目录的图片。

SVG 兼容方式

在 Android 5.0 之前,可以向上面那么配置,那么5.0之后,如果配置呢?

1
2
3
4
5
vectorDrawables.useSupportLibrary=true
implementation 'com.android.support:appcompat-v7:28.0.0'
//在布局中用法
xmlns:app="http://schemas.android.com/apk/res-auto"
app:srcCompat="@drawable/eye"

通过使用 svg 图片,解决了我们大量使用套图的问题。

2. Tint 着色器

tint 着色器就是可以改变我们一些图标的颜色,例如:

image.png

上述那个图片是黑色的,我们通过添加属性 android:tint="@color/colorAccent" , 就可以改变它的颜色:

image.png

我们经常会遇到例如点击按钮变颜色啥的,以前经常弄两张图,一个常态的icon, 一个点击态的 icon. 有了这个着色器,有些icon 我们不需要用两张图啦。例如:

res--color--tint_honor_color.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:color="@color/colorPrimary" android:state_pressed="true"/>
<item android:color="@android:color/transparent"/>
</selector>

再来一个 res-drawable-tint_honor_src.xml:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:drawable="@drawable/next" android:state_pressed="true"/>
<item android:drawable="@drawable/next"/>
</selector>

xml 中:

1
2
3
4
5
6
7
8
9
<ImageView
android:layout_marginTop="30dp"
android:clickable="true"
android:focusable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/tint_honor_src"
android:tint="@color/tint_honor_color"
/>

3. 资源打包

由于使用第三方库,例如 v7 包的引入,库中包含了大量国际化资源,我们可以根据实际情况,配置我们所需要的语言,去除其它语言,从而减少strings 文件的大小

image.png

从上图我们看到默认帮我们生成很多国家的语言,我们可以只保留中文,配置如下:

1
2
//只保留我们指定的语言
resConfigs('zh-rCN','ko')

再次 Build APK 查看:

image.png

经测试,apk 的大小还是有所减少的。

4. 动态库打包配置

在我们 libs 目录下配置指定 so 库,我们知道,真机CPU一般都是 armeabi 的, 模拟器一般都x86 的 ,还有一些 armeabi-v7a mips 等,一般要根据我们实际情况,只保留对我们真实有用的 cpu ,如果不进行配置,就会打包全 cpu 架构的动态库到apk中。

将so库打包到apk中:

1
2
3
4
5
souceSets{
main{
jniLibs.srcDirs = ['libs']
}
}

配置如下:

1
2
3
ndk{
abiFilters('armeabi','armeabi-v7a')
}

5. 移除无用资源

未避免我们移除了重要的文件,移除之前最好备份,因为都是物理删除,没了就没了。

  • 第一种看不见的删除。按照图示操作:

    image.png

执行之后,你会发现没有用的资源文件都被删除了,在实验这个之前,你可以添加复制几个无用的资源,观察执行之后是否还在,一般执行之后就没有了,特别注意:这种操作是物理删除,不会展示哪些文件需要删除,而是一次性将其删除,删除需谨慎。

  • 第二种操作是:会给我们列出需要删除无用的资源,如下:

    image.png

    image.png

搜索找到 unused resources , 之后会列出无用资源,例如:

image.png

上述文件都是我复制出来的,之后再决定是否删除。

6. 开启代码压缩

即对源代码进行混淆,还需要同时配置 proguard 文件,因为有些第三方库啥的一些类不能混淆,需要各自配置。

开启混淆:

1
2
3
4
5
6
buildTypes {
release {
minifyEnabled true // 改为true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

7. 启用资源缩减

1
2
3
4
5
6
//以debug为例,开启资源缩减,必须将 minifyEnabled 设置为 true, 否则会报错
debug {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

如果有想要保留或舍弃的特定资源,需要在项目中创建一个包含 标记的 xml 文件,并在tools:keep属性中指定每个要保留的资源,在tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。压缩配置: res/raw/keep.xml 该文件不会打包到apk中 .

称为严格的资源检查:

示例:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources
xmlns:tools="http://schemas.android.com/tools"
tools:discard="@color/tint_honor_color1"
tools:keep="@drawable/next1"
tools:shrinkMode="strict"
/>

使用之后,会出现什么效果呢:

对于 tint_honor_color1 我们选择要舍弃,Build APK 之后,查看里面的文件发现就为空了,而我们保留的

next1 文件没有为空,因为我们保留 keep 了。

8. 启用webp

webp 格式是谷歌推出的一种有损压缩格式,这种图片格式相对于png或jpg 格式的图片损失的质量几乎可以忽略不计,但是压缩后图片的体积却比png或jpg要小很多。

可以亲自找一张大图,目前已经集成到android studio 中了,导入 android studio 中后,右键 —conver to Webp ,就可以进行转化。亲测可以将一个4M 左右的图片 变为 400多kb. 图片质量也很高。

总结

相信经过层层处理,apk大小一定会有所减小, 不断补充。

Vollery源码阅读(二)

Veröffentlicht am 2019-02-25 |

上节写到关于 Vollery 的前半部分,需要查看的点击 这里 , 这次主要查看当我们真正 add request 的时候做进一步查看。

我们还是从使用的方法作为我们的切入点:

1
mQueue.add(postRequest);//当我们向队列中添加一个网络请求

跟进,查看 RequestQueue#add() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* Staging area for requests that already have a duplicate request in
* flight.
* 存储有重复请求的request暂存区,我个人认为就是如果 正在处理A发出的一个请求,此时又来一个A发出同样的一个请求,那么第二个请求就会暂时保存在这个集合中
*/
private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();

public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of
// current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}

// Process requests in the order they are added.
//按照添加请求的顺序处理请求
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");

// If the request is uncacheable, skip the cache queue and go straight
// to the network.
//判断是否可以缓存了,不缓存就添加到网络请求队列中去return,前面已经说过这个 mNetworkQueue 存储管理网络请求队列的
//默认是可以缓存的,在Request的构造方法可以看到
//public Request(int method, String url, ErrorListener listener) {
// this.mShouldCache = true;
// }
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}

//直接到下面
// Insert request into stage if there's already a request with the same
// cache key in flight.
synchronized (mWaitingRequests) {
//首先查看是否缓存过,取key
String cacheKey = request.getCacheKey();
//如果缓存过
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests
.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
//添加到暂时存储队列中
mWaitingRequests.put(cacheKey, stagedRequests);

} else {
//第一次请求,应该是进入到这里的
// Insert 'null' queue for this cacheKey, indicating there is
// now a request in
// flight.
//添加一个 null 队列,表示有一个请求正在进行
mWaitingRequests.put(cacheKey, null);
//添加到缓存队列中,那么我们接下来重点查看CacheDispatcher#run方法
mCacheQueue.add(request);
}
return request;
}
}

接下来就到 CacheDispatcher#run() 方法了,因为里面有值了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void run() {

Process.setThreadPriority(10);
//初始化缓存类型有两种目前:NoCache() 和 DiskBaseCache()
this.mCache.initialize();
//嵌套了好多 while 循环
while(true) {
while(true) {
while(true) {
while(true) {
try {
//从缓存队列中取出,在之前第一篇文章时,没展示下半部分,这次有请求就可以看下里面的逻辑了。
final Request<?> request = (Request)this.mCacheQueue.take();
request.addMarker("cache-queue-take");
//请求未取消
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
} else {
//拿到缓存结果,请求信息都保存一个叫Entry内部类中
Entry entry = this.mCache.get(request.getCacheKey());
//有可能取出的缓存为null
if (entry == null) {
request.addMarker("cache-miss");
//如果清空了缓存,那就重新添加到网络请求队列中去
this.mNetworkQueue.put(request);
} else if (!entry.isExpired()) {//判断是否过期
request.addMarker("cache-hit");
//拿到请求结果response
Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//表示是否需要重新刷新
if (!entry.refreshNeeded()) {
//不需要的话就直接分发给主线程了
this.mDelivery.postResponse(request, response);
} else {
//需要刷新,就重新进行网络请求
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
this.mDelivery.postResponse(request, response, new Runnable() {
public void run() {
try {
////需要刷新,就重新进行网络请求
CacheDispatcher.this.mNetworkQueue.put(request);
} catch (InterruptedException var2) {
;
}

}
});
}
} else {
//表示过期了,也重新进行请求
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
this.mNetworkQueue.put(request);
}
}
} catch (InterruptedException var4) {
if (this.mQuit) {
return;
}
}

关于 CacheDispatcher#run 类我画了一张图示:
CacheDispatcher.png

从这里我们知道,网络请求首先需要mCacheDispatcher 判断是否已缓存,若缓存了则直接 postResponse 如果没有,则重新进行网络请求,我们就直接添加到 mNetworkQueue 中,那第一次请求,肯定还未缓存, 那我们下面就又可以看这个 NetworkDispatcher#run 方法了,因为此时队列中有请求了,接下来我们再返回查看:

NetworkDispatcher#run() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public void run() {
//设置线程优先级
Process.setThreadPriority(10);

while(true) {
Request request;
while(true) {
try {
//从这个网络请求队列中中取出一条request
//第二次这里有了,因为我们add 了 一个 request
request = (Request)this.mQueue.take();
break;
} catch (InterruptedException var4) {
if (this.mQuit) {
return;
}
}
}
//以下都是网络请求队列有网络请求任务时执行
request.addMarker("network-queue-take");
if (request.isCanceled()) {//判断是否取消
request.finish("network-discard-cancelled");
} else {
//网络请求未取消
this.addTrafficStatsTag(request);
//处理网络请求,得到NetworkResponse
NetworkResponse networkResponse = this.mNetwork.performRequest(request);
//标识请求完成
request.addMarker("network-http-complete");
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
} else {
//开始解析返回的结果,解析parseNetworkResponse可根据不同类型的方式进行解析,请看下图:
Response<?> response = request.parseNetworkResponse(networkResponse);
//标识解析完成
request.addMarker("network-parse-complete");
//开始缓存请求结果,判断是否可以缓存,默认可以
if (request.shouldCache() && response.cacheEntry != null) {
this.mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
//分发请求结果通过 mDelivery 完成
request.markDelivered();
this.mDelivery.postResponse(request, response);
}
}
}

解析 有Json Image String 等多种类型。
image.png

到此基本分析完毕。奉上一个整体流程图,我在网上找到的,感觉还不错:
image.png

12…8

QQabby

79 Artikel
63 Tags
© 2020 QQabby
Erstellt mit Hexo
|
Theme — NexT.Pisces v5.1.3