我的主页:chengang.plus/java
文章将会同步到我的微信公众号:Android部落格android
RecyclerView.Recyclerc++
void recycleViewHolderInternal(ViewHolder holder) {
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
}
复制代码
RecyclerView.RecycledViewPoolshell
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
for (int i = scrapHeap.size() - 1; i >= 0; i--) {
if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
复制代码
缓存分两个区域:json
到这里能够明白,当首页整个做为一个Viewtype类型的时候,会缓存一个很大的ViewHolder对象到mCachedViews或RecycledViewPool中。canvas
下边解释缘由。缓存
本地加载图片时的各类decode方法最终到了BitmapFactory.cpp的doDecode()方法中,以下:bash
BitmapFactory.cpp微信
static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, jobject padding, jobject options) {
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
const int density = env->GetIntField(options, gOptions_densityFieldID);
const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
if (density != 0 && targetDensity != 0 && density != screenDensity) {
scale = (float) targetDensity / density;
}
}
// Determine the output size.
SkISize size = codec->getSampledDimensions(sampleSize);
int scaledWidth = size.width();
int scaledHeight = size.height();
bool willScale = false;
// Apply a fine scaling step if necessary.
if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
willScale = true;
scaledWidth = codec->getInfo().width() / sampleSize;
scaledHeight = codec->getInfo().height() / sampleSize;
}
// Scale is necessary due to density differences.
if (scale != 1.0f) {
willScale = true;
scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
}
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
复制代码
从源码能够看出scaledWidth通过两次计算,一次是若是sampleSize不等于1的时候计算缩放宽高,等于原宽高分别除以采样倍数;另一次是若是目标屏幕密度和当前图片所处文件夹的密度不一致的话,计算出:框架
scale = targetDensity / density
(好比机器当前是xxhdpi,对应480,而图片放置在xhdpi中,对应320,就会算出一个大于1的拉伸系数)
若是scale不等于1,用第一次计算的
scaledWidth * scale + 0.5,scaledHeight * scale + 0.5
能够看到分两步,一步是用最初的图片大小除以采样系数;一步是根据屏幕密度计算出来的拉伸系数而后乘以这个系数
不过具体在作缩放操做的时候缩放因子等于两次计算以后的宽高分别处以原始宽高。可见对于设置采样率能够节省部份内存。
最后实际的占用大小:
width = (originWidth / sampleSize) * (targetDensity / density) + 0.5
height = (originHeight / sampleSize) * (targetDensity / density) + 0.5
totalSize = width * height * 像素位
(targetDensity是手机实际密度,等于宽平方 + 高平方开根号,处于屏幕对角线长度,density是图片在App所处文件的密度。)
getRowBytes()返回的是每行的像素值,乘以高度就是总的像素数,也就是占用内存的大小。
getAllocationByteCount()与getByteCount()的返回值通常状况下都是相等的。只是在图片 复用的时候,getAllocationByteCount()返回的是复用图像所占内存的大小,getByteCount()返回的是新解码图片占用内存的大小。
从这个版本开始,bitmap的ARGB数据(像素数据)和bitmap对象一块儿存在Dalvik的堆里了。这样bitmap对象和它的ARGB数据就能够同步回收了。
后续Android又引入了BitmapFactory.Options.inBitmap字段。
若是设置了这个字段,bitmap在加载数据时能够复用这个字段所指向的bitmap的内存空间。新增的这种内存复用的特性,能够优化掉因旧bitmap内存释放和新bitmap内存申请所带来的性能损耗。
可是,内存可以复用也是有条件的。好比,在Android 4.4(API level 19)以前,只有新旧两个bitmap的尺寸同样才能复用内存空间。Android 4.4开始只要旧bitmap的尺寸大于等于新的bitmap就能够复用了。
这样GC没法知道当前的内存状况是否乐观,大量建立bitmap可能不会触发到GC,而Native中bitmap的像素数据可能已经占用了过多内存,这时候就会OOM,因此推荐在bitmap使用完以后,调用recycle释放掉Native的内存。
Bitmap的内存分配在dalvik heap,Bitmap中有个byte[] mBuffer,其实就是用来存储像素数据的,它位于java heap中,经过在native层构建Java Bitmap对象的方式,将生成的byte[]传递给Bitmap.java对象。
像素数据就和bitmap对象一块儿都分配在堆中了,一块儿接受GC管理,只要bitmap置为null没有被强引用持有,GC就会把它回收掉,和普通对象同样。
Bitmap像素内存的分配是在native层直接调用calloc,因此其像素分配的是在native heap上,而且还引入了NativeAllocationRegistry机制。
Bitmap引入了NativeAllocationRegistry这样一种辅助自动回收native内存的机制,依然不须要用户主动回收了,当bitmap的Java对象被回收后,NativeAllocationRegistry辅助回收这个对象所申请的native内存。
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
try {
if (!((Activity) context).isDestroyed() && !((Activity) context).isFinishing()) {
ImageView img = holder.itemView.findViewById(R.id.goods_img);
img.setImageDrawable(null);
Glide.with(context).clear(img);
}
} catch (Exception e) {
MyLog.d(TAG, "recycle fail:" + e.getLocalizedMessage());
}
}
复制代码
总结上图的流程就是:
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
热启动。应用的热启动比冷启动简单得多,开销也更低。在热启动中,系统的全部工做就是将您的 Activity 带到前台。若是应用的全部 Activity 都还驻留在内存中,则应用能够无须重复对象初始化、布局扩充和呈现。
温启动。温启动涵盖在冷启动期间发生的操做的一些子集;同时,它的开销比热启动多。有许多潜在状态可视为温启动。例如:
用户退出您的应用,但以后又从新启动。进程可能已继续运行,但应用必须经过调用 onCreate() 从头开始从新建立 Activity。 系统将您的应用从内存中逐出,而后用户又从新启动它。进程和 Activity 须要重启,但传递到 onCreate() 的已保存实例状态包对于完成此任务有必定助益。
adb shell am start -W [packageName]/[packageName.MainActivity]
输出以下:
E:\data_parse>adb shell am start -S -W com.xx.xx.xx/.activity.MainActivity
Stopping: com.xx.xx.xx
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xx.xx.xx/.activity.MainActivity }
Status: ok
Activity: com.xx.xx.xx/.activity.MainActivity
ThisTime: 1136
TotalTime: 75246
WaitTime: 1179
Complete
复制代码
从两方面入手,想办法缩短Application消耗的时间;缩短Activity消耗的时间。
咱们的项目中有各类SDK的初始化,包括友盟,百川,开普勒,Glide,分享等。
json解析的过程存在json字符遍历,而商城类项目从服务端返回的数据上百k,有些json结构很是复杂,比较耗时
SharedPreferencesImpl
private final Object mLock = new Object();
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
@Override
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
复制代码
在这里能够看到,加锁的对象是mLock,当loadFromDisk方法执行完毕以后,才会执行mLock.notifyAll();
,至此,其余的代码才会得到执行时机。尤为是后续edit,以及put/get操做的时候。
当在StatefulWidget中调用setState的时候,会致使当前Widget下全部Widget树刷新,这种状况若是赶上复杂的布局,确定是不可想象的,先看看调用setState的时候发生了什么,伪代码以下:
@protected
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
scheduleFrame();
void handleDrawFrame() {};
void drawFrame() {};
rebuild();
preformRebuild();
build();
updateChild();
update();
}
复制代码
能够看到最终会致使从新请求渲染帧,更新视图。
解决方案是:
//第一步
@override
void _updateInheritance() {
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
//第二步
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@override
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
//第三步
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
@override
void notifyClients(InheritedWidget oldWidget) {
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
@mustCallSuper
void didChangeDependencies() {
markNeedsBuild();
}
复制代码
在这个阶段每个Element在mount的过程当中会调用_updateInheritance方法,生成一个HashMap _inheritedWidgets。这里比较取巧的是,当父类已经存在的时候,直接在父类的_inheritedWidgets里面追加,而runType就是他的key,因此能够轻松找到InheritedWidget。
InheritedWidget的子Widget调用它对外暴露的of方法时,经过调用dependOnInheritedWidgetOfExactType方法返回InheritedWidget自身。这里从第一步的_inheritedWidgets中经过runType找到这个对象,而后调用它的setDependencies方法,将子Widget的Element做为依赖项加入到一个HashSet _dependents中。
当InheritedWidget的数据发生变化时,会触发渲染树更新,当调用它的update方法更新Element的时候,会遍历上一步_dependents中保存的依赖Element,并重建这些Element。
透过以上步骤,咱们能够发现不论InheritedWidget与须要依赖它数据的Widget中间隔了多少层级,只要InheritedWidget数据发生变化,都能通知依赖它的Widget重绘。