在上一篇文章中,我们通过跟踪源码了解了 Activity 大致的启动流程
,相信你也肯定看的晕头转向的,没关系,今天这篇文章是放松篇,帮你即使不看源码也能掌握。
其实就是一个跨进程通信的过程,从客户端进程到服务端进程,再最后转到客户端进程中去,来一张流程图更清晰。
在上一篇文章中,我们通过跟踪源码了解了 Activity 大致的启动流程
,相信你也肯定看的晕头转向的,没关系,今天这篇文章是放松篇,帮你即使不看源码也能掌握。
其实就是一个跨进程通信的过程,从客户端进程到服务端进程,再最后转到客户端进程中去,来一张流程图更清晰。
最近在研究启动优化方面的知识,那么对于 Activity
的启动过程这方面的知识自然是逃不掉的,那么废话不多说,我们开始梳理。
(文中基于sdk28, 不少有代码片段的地方,可能比较枯燥,我们只要把握主要流程即可~)
启动包括冷启动和热启动,本篇我们主要围绕冷启动做简述。(还不清楚冷启动和热启动的区别,就自行Google哦)
当我们在手机桌面开始点击一个应用图标的时候,此时应用就启动起来了,那么背后究竟发生了什么事,我们一起来看一下:
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
11public 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
11class ActivityThread{
...
public ApplicationThread getApplicationThread()
{
return mAppThread;
}
private class ApplicationThread extends IApplicationThread.Stub {
.....
}
...
}
OK ,直到了 ApplicationThread
我们继续,调用了 Instrumentation.execStartActivity
我们需要点进去看一下:
1 | public ActivityResult execStartActivity( |
注释1: 其中我们上面说的 ApplicationThread
被转化成 IApplicationThread
是一个 IBinder
类型 , 因为ApplicationThread
继承了 IApplicationThread.Stub
是不是很眼熟,类似于我们那个aidl
自动生成的那个代码,你可以认为是代理类。
接着往下,注释2: ActivityManager.getService
返回 IActivityManager
类型 ,看下面代码:1
2
3
4
5
6
7
8
9
10
11
12
13public 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
3public class ActivityManagerService extends IActivityManager.Stub..{
....
}
最终调用实际是 ActivityManagerService 的 startActiviy
方法,通过获取远程服务获取到代理类,调用了远程服务,此时由客户端进程转换到远程服务端进程。
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
19public 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
26int 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
,因为前面我们在启动的时候设置了:
所以我们就可以直接看 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
继续调用调用:
方法里最终 return
又还是继续调用 startActivity
方法 :
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25private 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
23private 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:复用或者创建新的任务栈 ;
1 | boolean resumeFocusedStackTopActivityLocked() { |
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
33void 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
方法 ;
1 | void scheduleTransaction(ClientTransaction transaction) throws RemoteException { |
1 | public void schedule() throws RemoteException { |
mClient
是 IApplicationThread
就是 ApplicationThread
, 而 ApplicationThread
是 ActivityThread
的一个内部类,执行了 scheduleTransaction
方法。
此时你会发现,由原来的 Client
进程 ———> AMS
进程 ,现在又回到了 客户端进程了。
Client
进程 ———> AMS
进程 ———> Client
进程
1 | public final class ActivityThread extends ClientTransactionHandler { |
因为 ActivityThread
继承了 ClientTransactionHandler
,在父类中实现了这个方法:
1 | void scheduleTransaction(ClientTransaction transaction) { |
终于看到点能看懂的代码了,开始切换主线程了,因为看到了 sendMessage
, 哈哈。
发送了一个 ActivityThread.H.EXECUTE_TRANSACTION
消息 ,obj
为 transaction
, 继续到 ActivityThread
中看下:
1 | public final class ActivityThread extends ClientTransactionHandler{ |
找到对应的消息类型,开始处理消息,取出之前的 ClientTransaction
, 并调用 其 execute
方法,继续跟进:
然后你会发现 在 ClientTransaction
类中并没有 execute
方法,因为前面我们说过把生命周期都封装成一个一个的 Item
了, 启动 Activity
是 LauncheActivityItem
所以我们应该去看 LaunchActivityItem
该类继承了 ClientTransactionItem
1
2
3
4
5
6
7
8
9
10
11
12
13
14public 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
中的,继续走:
1 | @Override |
1 | private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { |
1 | public void callActivityOnCreate(Activity activity, Bundle icicle, |
1 | final void performCreate(Bundle icicle) { |
终于走到头了,看到了 onCreate()
方法,由于文章篇幅过长,总结放到下一篇,路过的朋友如果对你有帮助,能否为辛苦的糖葫芦点个赞👍再走啊~
打开后,运行程序,点击 record
, 反复进去目标 Activity ,点击强制 GC 最后点击 stop
:
然后点击 Arrange by package
:
找到自己的应用包名:
点击
类似这样的图标,dump hprof 文件 。
我用idea 开发工具打开后:
点击右上角那个运行按钮,出现以下:
然后继续点开 Leaked Activities
选项 :
这样我们就知道是 LeakActivity
发生了 泄漏 ,那如何去找到泄漏的原因需要使用 MAT 工具;
以上是检测泄漏 Activity 的方式之一,通常我也会在做这个之前,做一个小的判断,通过 adb 命令,判断内存情况,同样也是反复进出 目标Activity , 然后打印内存信息。
1 | adb shell dumpsys meminfo "packageName| pid" |
观察 Activities 的数量,反复进出目标Activity , 如果 Activities 只增加而不减少,那么目标 Activity 基本上就发生内存泄漏了。
借助 sdk 里 platform-tools 中的 hprof-conv.exe 进行hprof 文件转化,转化命令为:
1 | //示例 |
文件名我就叫 test , 转化后为 test1.hprof , 转化后的文件用 Mat 工具打开:
然后点击左上角的这个按钮:
然后在显示的 Historgram
中输入之前泄漏的 Activity
:
将搜索到的结果,右键,去除 软引用,弱引用,虚引用的对象:
筛选得出结果:
发现 LeakActivity
被这个 callback –MyRunnable 引用,发现原因进一步就可以解决了。
泄漏的LeakActivity.java
的代码:
1 | public class LeakActivity extends AppCompatActivity { |
1 | adb devices |
1 | adb devices -l |
1 | adb shell;//进入adb shell 里 |
一个节点有不超过2个子节点,超过2个是多路树,不属于二叉树。
左节点比根节点小,比根节点大的为右节点;
分为前序,中序和后序,使用最多的是中序;
前序 : 根节点——->左节点———->右节点;
中序 : 左节点——-> 根节点———>右节点;
后序 : 左节点——> 右节点——–>根节点
示例代码:
Node.java
1 | public class Node { |
BinarySearchTree.java
1 | /** |
Test.java
1 | BinarySearchTree binarySearchTree = new BinarySearchTree(); |
在 Looper#loop()
方法。
1 | public static void loop() { |
这个 Printer
日志给我打印了主线程分发消息之前打印一个日志,分发完成之后又打印一条日志,分别是:
1 | Dispatching to + 目标Handler + msg.what; |
我们知道这个在应用程序启动的入口 ActivityThread#main
方法,初始化了主线程的 Looper
. 同时也有一个 Handler H
, Activity
的生命周期就是通过向 H
发送消息来完成的。
Android
是消息驱动的,这大概就是这个意思吧。如果我们可以得知主线程每个message
的消耗时间,可以间接用来分析每个方法的耗时,从而进行一个优化工作,可以给主线程的Looper
设置一个Printer
, 如下:
1 | Looper.myLooper().setMessageLogging(new Printer() { |
如果有另外一条线程,记录我们执行前的堆栈信息,同时记录每次 dispatchMessage
方法耗时,算出二者之差,当超过某个时间的时候,认为该方法耗时过长,需要进行优化。到这里你可能想到了 BlockCannary
是的它基本上就是利用了这个原理,来分析优化的。
主要针对屏幕旋转对 Activity 生命周期有何影响。
在没有其它配置的情况下,通过日志打印屏幕旋转会调用的方法。
1 | //onPause()----onStop()-----onDestroy()-----onCreate() ---- onStart()---onResume() |
关于 android:configChanges
的配置,不同的值对生命周期也会有不同的影响。这个值最常用的值包括: orientation
和 keyboardHidden
, 分别用于避免因屏幕方向和可用键盘改变而导致的重启。
例如:
1 | <activity android:name=".MainActivity" |
当其中一个配置发生变化时, 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
IdleHandler 可以用来提升性能,主要用在我们希望能够在当前线程 消息队列空闲时 做些事情(例如UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。
IdleHandler
位于 MessageQueue
类中的一个静态接口,如下:
1 | MessageQueue#IdleHandler |
1 | Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { |
我记得之前我们想要获取xml中某个控件的
width
和height
时,在onCreate
方法中直接获取,会获取到是 0 , 因为这个view
还未绘制完成,所以获取不到,当时的解决方案我记得是使用Handler
发送一个延时消息获取,现在有更好的方式实现了,那就是通过IdleHandler
, 如上面代码所示。 当然还可以做一些其它预处理的简单操作。
在项目 res
右键出来,New ---> Vector Asset
,SVG 图不会像位图一样因为缩放而使图片质量下降,优点在于节省空间和内存,常常用于一些简单的图标。
如果是从外部导入SVG,需要也是右键res---new---Vector Asset
,需要注意的是:Asset Type
勾选的是: Local File
, 需要注意的是这个psd
图片不支持渐变和透明度,如下图:
导入进来后,自动生成 xml
.
这里提供一个
android
所有 自带的svg
图片的地址: https://megatronking.github.io/SVG-Android/
批量转化 svg2vector
的工具,这里可以 下载 , 是一个 svg2vector-cli-1.0.1.jar
. 这个是最新版本
操作命令是:
1 |
|
将svg
图片生成指定目录的图片。
需要在我们的app 的
build.gradle
文件中配置
1 | android{ |
配置完成后,我们可以点击 Build APK(s)
,观察生成的apk
里面的文件:
我们在我们指定的目录下drawable-hdpi 和 drawable-xhdpi
发现了 我们svg 图片生成了对应的 png
图片。这样的好处是我们可以避免配置多套图在每个分辨率下,用 svg 图可以自动帮我们生成指定目录的图片。
在 Android 5.0 之前,可以向上面那么配置,那么5.0之后,如果配置呢?
1 | vectorDrawables.useSupportLibrary=true |
通过使用 svg
图片,解决了我们大量使用套图的问题。
tint 着色器就是可以改变我们一些图标的颜色,例如:
上述那个图片是黑色的,我们通过添加属性 android:tint="@color/colorAccent"
, 就可以改变它的颜色:
我们经常会遇到例如点击按钮变颜色啥的,以前经常弄两张图,一个常态的icon, 一个点击态的 icon. 有了这个着色器,有些icon 我们不需要用两张图啦。例如:
res--color--tint_honor_color.xml
1 | <?xml version="1.0" encoding="utf-8"?> |
再来一个 res-drawable-tint_honor_src.xml
:
1 | <?xml version="1.0" encoding="utf-8"?> |
xml
中:
1 | <ImageView |
由于使用第三方库,例如
v7
包的引入,库中包含了大量国际化资源,我们可以根据实际情况,配置我们所需要的语言,去除其它语言,从而减少strings
文件的大小
从上图我们看到默认帮我们生成很多国家的语言,我们可以只保留中文,配置如下:
1 | //只保留我们指定的语言 |
再次 Build APK
查看:
经测试,apk 的大小还是有所减少的。
在我们 libs 目录下配置指定
so
库,我们知道,真机CPU一般都是armeabi
的, 模拟器一般都x86
的 ,还有一些armeabi-v7a mips
等,一般要根据我们实际情况,只保留对我们真实有用的 cpu ,如果不进行配置,就会打包全 cpu 架构的动态库到apk中。
将so库打包到apk中:
1 | souceSets{ |
配置如下:
1 | ndk{ |
未避免我们移除了重要的文件,移除之前最好备份,因为都是物理删除,没了就没了。
第一种看不见的删除。按照图示操作:
执行之后,你会发现没有用的资源文件都被删除了,在实验这个之前,你可以添加复制几个无用的资源,观察执行之后是否还在,一般执行之后就没有了,特别注意:这种操作是物理删除,不会展示哪些文件需要删除,而是一次性将其删除,删除需谨慎。
第二种操作是:会给我们列出需要删除无用的资源,如下:
搜索找到 unused resources
, 之后会列出无用资源,例如:
上述文件都是我复制出来的,之后再决定是否删除。
即对源代码进行混淆,还需要同时配置
proguard
文件,因为有些第三方库啥的一些类不能混淆,需要各自配置。
开启混淆:
1 | buildTypes { |
1 | //以debug为例,开启资源缩减,必须将 minifyEnabled 设置为 true, 否则会报错 |
如果有想要保留或舍弃的特定资源,需要在项目中创建一个包含tools:keep
属性中指定每个要保留的资源,在tools:discard
属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。压缩配置: res/raw/keep.xml
该文件不会打包到apk
中 .
称为严格的资源检查:
示例:
1 | <?xml version="1.0" encoding="utf-8"?> |
使用之后,会出现什么效果呢:
对于 tint_honor_color1
我们选择要舍弃,Build APK
之后,查看里面的文件发现就为空了,而我们保留的
next1
文件没有为空,因为我们保留 keep
了。
webp 格式是谷歌推出的一种有损压缩格式,这种图片格式相对于png或jpg 格式的图片损失的质量几乎可以忽略不计,但是压缩后图片的体积却比png或jpg要小很多。
可以亲自找一张大图,目前已经集成到android studio
中了,导入 android studio
中后,右键 —conver to Webp ,就可以进行转化。亲测可以将一个4M 左右的图片 变为 400多kb. 图片质量也很高。
相信经过层层处理,apk
大小一定会有所减小, 不断补充。
上节写到关于
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
63public 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
类我画了一张图示:
从这里我们知道,网络请求首先需要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
47public 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
等多种类型。
到此基本分析完毕。奉上一个整体流程图,我在网上找到的,感觉还不错: