我的主頁: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重繪。