为什么不建议使用Intent传递大的数据
Intent
传递大数据,会出现 TransactionTooLargeException
的场景。
简单来说,Intent
传输数据的机制中,用到了 Binder
。Intent
中的数据,会作为 Parcel
被存储在 Binder 的事务缓冲区
(Binder transaction buffer)中的对象进行传输。
而这个 Binder 事务缓冲区
具有一个有限的固定大小,当前为 1MB
。你可别以为传递 1MB 以下的数据就安全了,这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。也就是说 Intent 在 Activity 间传输数据,本身也不适合传递太大的数据。
activity启动模式,与taskAffinity
每一个 Activity
都有一个 Affinity
属性,如果不在清单文件中指定,默认为当前应用的包名。
taskAffinity
是与任务栈有关属性。
单纯使用 taskAffinity
不能导致 Activity
被创建在新的任务栈中,需要配合 singleTask
或者 singleInstance
.
可以使用如下命令,查看activity
栈
adb shell dumpsys activity activities
allowTaskReparenting
赋予 Activity
在各个 Task
中间转移的特性。一个在后台任务栈中的 Activity A,当有其他任务进入前台,并且 taskAffinity
与 A 相同,且栈的affinity
属性相同,则会自动将 A 添加到当前启动的任务栈中。
参考资料:Android 启动模式和 taskAffinity 属性详解
progress造成多个Application
Activity
可以在不同的进程中启动,而每一个不同的进程都会创建出一个 Application,因此有可能造成 Application 的 onCreate 方法被执行多次。
可以在applycation
onCreate
方法中判断进程的名称,只有在符合要求的进程里,才执行初始化操作。通过如下方法,获取进程名称。
private String getProcessName(Context cxt) {
int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName;
}
}
return null;
}
RecyclerView的缓存机制
RecyclerView
的 onMeasure
,onLayout
工作委托给LayoutManager
来执行,不同的LayoutManager
会有不同风格的布局显示(策略模式)。
缓存复用原理 Recycler
在onLayout
阶段时,有介绍在 layoutChunk
方法中通过调用 layoutState.next
方法拿到某个子 ItemView
,然后添加到 RV
最终调用是在Recycler
的getViewForPosition
tryGetViewHolderForPositionByDeadline
方法来查找相应位置上的ViewHolder
,在这个方法中会从下面介绍的 4 级缓存中依次查找。
如果在各级缓存中都没有找到相应的 ViewHolder
,则会使用 Adapter
中的 createViewHolder
方法创建一个新的 ViewHolder
。
第一级缓存 mAttachedScrap
&mChangedScrap
这两者主要用来缓存屏幕内的 ViewHolder
第二级缓存 mCachedViews
用来缓存移除屏幕之外的 ViewHolder
,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize
方法来改变缓存的容量大小。如果 mCachedViews
的容量已满,则会根据 FIFO 的规则将旧 ViewHolder
抛弃,然后添加新的 ViewHolder
第三级缓存 ViewCacheExtension
这是 RV 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法geViewForPositionAndType
第四级缓存 RecycledViewPool
RecycledViewPool
同样是用来缓存屏幕外的 ViewHolder
,当 mCachedViews
中的个数已满(默认为 2),则从 mCachedViews
中淘汰出来的 ViewHolder
会先缓存到 RecycledViewPool
中。ViewHolder
在被缓存到 RecycledViewPool
时,会将内部的数据清理,因此从 RecycledViewPool
中取出来的 ViewHolder
需要重新调用 onBindViewHolder
绑定数据。
多个 RV 之间可以共享一个 RecycledViewPool
,这对于多 tab 界面的优化效果会很显著。需要注意的是,RecycledViewPool
是根据 type 来获取 ViewHolder
,每个 type 默认最大缓存 5 个。因此多个 RecyclerView
共享 RecycledViewPool
时,必须确保共享的 RecyclerView
使用的 Adapter
是同一个,或 view type
是不会冲突的。
对OkHttp的理解
优势
更好的WebSocket
支持
更多的Interceptor
责任链,可以更好的扩展
核心 HttpCodec
调度器
Dispatcher
是 OkHttpClient
的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher
内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等。
任务执行,会根据依次执行拦截器。常用拦截器说明。
BridgeInterceptor
:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等。
CacheInterceptor
:负责 HTTP 请求的缓存处理。
ConnectInterceptor
:负责建立与服务器地址之间的连接,也就是 TCP 链接。
CallServerInterceptor
:负责向服务器发送请求,并从服务器拿到远端数据结果。
首先 OkHttp
内部是一个门户模式,所有的下发工作都是通过一个门户 Dispatcher
来进行分发。
然后在网络请求阶段通过责任链模式,链式的调用各个拦截器的 intercept
方法。 2 个比较重要的拦截器:CacheInterceptor 和 CallServerInterceptor。它们分别用来做请求缓存和执行网络请求操作。
网络优化
OkHttp
3.11.0版本提供了EventListener
接口,可以让调用者接收一系列网络请求过程中的事件,例如DNS解析
、TSL/SSL连接、Response接收等。通过继承此接口,调用者可以监视整个应用中网络请求次数、流量大小、耗时情况。
我们要实现 EventListener 接口,及其工厂类。
在创建OkHttpClient时将EventListener工厂作为构建参数添加进去。
OkHttpClient.Builder builder = new OkHttpClient.Builder()
//HttpEventListener为自己实现EventListener类,FACTORY对应工厂
.eventListenerFactory(HttpEventListener.FACTORY)
.build();
使用OkHttp
一次完整http请求,包括以下几个步骤
0,000 callStart
0,027 dnsStart
5,189 dnsEnd
5,359 secureConnectStart
5,907 secureConnectEnd
5,910 connectEnd
5,921 connectionAcquired
5,925 requestHeadersStart
5,930 requestHeadersEnd
5,938 responseHeadersStart
6,181 responseHeadersEnd
6,189 responseBodyEnd
6,233 connectionReleased
DNS
解析优化,
首先是防劫持,我们可以考虑使用 HttpDns
.
目前业内主要由第三方厂商提供实现了 HttpDns
的 SDK,比较普及的是阿里云和腾讯云的 HttpDns Service
.可以考虑使用七牛云提供的免费的 happy-dns
.
在接口 Dns 中只有一个方法需要实现——lookup
,这个方法返回查找到的服务器地址集合。并且 OkHttp 已经实现了一个默认的 DNS 解析器,定义中的SYSTEM
,它使用 java net 包中的 InetAddress
获取某域名的 IP 地址集合。
参考资料
对于网络编程,你做过哪些优化?
OkHttp 之 网络请求耗时统计
bitmap的优化
内存中,一个像素点占几个byte,见Bitmap.Config
的设置,默认是ARGB_8888
.
BitmapFactory
加载 resource
中图片,会根据设备屏幕密度进行缩放。
BitmapFactory
加载Assert
中图片,不会进行缩放。
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rodman);
//getAllocationByteCount 获取 bitmap占用的字节大小
Log.i(TAG, "bitmap size is "+bitmap.getAllocationByteCount());
imageView.setImageBitmap(bitmap)
加载优化
BitmapFactory.Options options = new BitmapFactory.Options();
//优化1,修改像素的表示
options.inPreferredConfig = Bitmap.Config.RGB_565;
//优化2,宽和高没隔2个像素进行一次采样
options.inSampleSize = 2;
//优化3,使用bitmap的复用,(1.android4.4要求相等 2. >4.4要求要大于等于 2)
option.inBimap = reuseBitmap;
option.inMutable = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rodman);
//注意getByteCount() 和 bitmap.getAllocationByteCount() (后者可能会更大) 的区别
Log.i(TAG, "bitmap size is " + bitmap.getByteCount());
imageView.setImageBitmap(bitmap);
大图加载
在不压缩图片的前提下,不建议一次性将整张图加载到内存,而是采用分片加载的方式来显示图片部分内容,然后根据手势操作,放大缩小或者移动图片显示区域。
/**
* 显示图片的左上角 200*200 区域
**/
private void showRegionImage() {
try {
InputStream inputStream = getAssets().open("rodman3.png");
//设置显示图片的中心区域
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( inputStream, false );
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = decoder.decoder.decodeRegion( new Rect(0, 0, 200, 200), options );
regionImage.setImageBitmap( bitmap );
} catch (IOException e) {}
}
Bitmap 缓存,结合LruCache
进行优化
activity的启动流程
这一过程主要涉及 3 个进程间的通信过程:
- 首先进程 A 通过 Binder 调用
AMS
的startActivity
方法。(startActivity->AMS) - 然后
AMS
通过一系列的计算构造目标Intent
,然后在ActivityStack
与ActivityStackSupervisor
中处理Task
和Activity
的入栈操作。(AMS->ApplicationThread) - 最后
AMS
通过Binder
机制,调用目标进程中ApplicationThread
的方法来创建并执行Activity
生命周期方法,实际上ApplicationThread
是ActivityThread
的一个内部类,它的执行最终都调用到了ActivityThread
中的相应方法。(ApplicationThread->Activity)
Window、Activity、View之间关系
PhoneWindow
一个 Activity
中有一个 Window
(也就是 PhoneWindow
对象,PhoneWindow 是Window的唯一实现类)。
在PhoneWindow
中有一个DecorView
(继承自FrameLayout,mContentParent
也是取自DecorView中)。
activity
的 setContentView
最终是通过PhoneWindow
中方法,setContentView
mLayoutInflater.inflate(layoutResID, mContentParent);
完成添加的。
WindowManager
PhoneWindow
只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View
作为窗口添加到 WMS 的过程是由 WindowManager
(其实现类 WindowManagerImpl
)来完成的。
ActivityThread
- handleResumeActivity
为入口,
WindowMangerGlobal
(单例类) -> addView
,
ViewRootImpl
-> setView
在此处完成绘制。并通过 mWindowSession
的 addToDisplay
方法将 View 添加到 WMS 中。
事件的分发,也是通过WMS,发送到上层。最终调用了 PhoneWindow
中的dispatchTouchEvent
方法。
内存泄漏问题
常见的内存泄漏
-
静态变量。 特别是
View
,Context
置为static
时。 -
没有反注册各种
Listener
例如广播,没有对应的反注册方法 -
非静态
Handler
导致Activity
泄漏 可以将其置为static,然后内部持有一个Activity的弱引用。 -
第三方库使用的
Context
传参,尽量使用ApplicationContext.
内存泄漏检测
可以使用LeakCanary
.
原理
Java 中的 WeakReference
是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收。
WeakReference
的构造函数可以传入 ReferenceQueue
,当 WeakReference
指向的对象被垃圾回收器回收时,会把 WeakReference
放入 ReferenceQueue
中。
public class WeakRefDemo {
public static void main(String[] args) {
ReferenceQueue<BigObject> queue = new ReferenceQueue<>();
WeakReference<BigObject> reference = new WeakReference<>(new BigObject(), queque);
System.out.println("before gc, reference.get is "+ reference.get());
System.out.println("before gc, queue is"+ queue.pull());
//打印值为 BigObject@7852e922, null
System.gc();
Thread.sleep(1000);
System.out.println("after gc, reference.get is "+ reference.get());
System.out.println("after gc, queue is"+ queue.pull());
//null, WeakReference@4e25154f
}
static class BigObject {
}
}
LeakCanary
中对内存泄漏检测的核心原理就是基于 WeakReference
和 ReferenceQueue
实现的。
-
当一个
Activity
需要被回收时,就将其包装到一个WeakReference
中,并且在WeakReference
的构造器中传入自定义的 ReferenceQueue。 -
然后给包装后的
WeakReference
做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录 -
最后主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set
中的记录也删除
经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。
参考资料
面对内存泄漏,如何进行优化?
Android Studio和MAT结合使用来分析内存问题
APK包体积优化
包体积分析
- Android Studio的
APK Analyzer
,直接使用AS打开即可。 - 使用腾讯开源框架
Matrix
中的ApkChecker
. 支持命令行。
优化的方法
- 删除无用文件。使用Lint查看未引用资源。在 Android Studio 中点击
Analyze
->Inspect Code
. - 资源优化。设置
shrinkResources
.需要minifyEnabled 也为true. - 代码混淆及优化。
- 文件优化,例如多使用
VectorDrawable 图片
, 使用webp
格式图片(as可以直接将png转成webp)。 - so优化,例如只需要支持arm平台,就没有必要打包进其他平台的so
- google play store 使用
App Bundle
android {
defaultConfig {
applicationId "xxx"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.compileSdkVersion
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi', "armeabi-v7a", "arm64-v8a"
}
}
buildTypes {
release {
minifyEnabled true //启用代码压缩
shrinkResources true //启用资源压缩
proguardFiles getDefaultProguardFile(‘proguard-android.txt'),
'proguard-rules.pro'//混淆规则文件配置
}
}
}
参考资料
APK如何做到包体积优化