一、背景
明天是周二,正好是我們團隊每周一次的技術分享,我會把前段時間花了幾天在干其他活的同時,整的一套詭異的冷啟動速度優化方案分享一下。
二、特意聲明
我這邊文章的內容不會涉及網上變地都是的常規的優化方案~ ,同時,平時工作的時候,工作內容雜且多,所以這個優化方案也不是特別成熟,僅供參考吧~
三、最常見的優化方案
數據懶加載,比如Fragment用戶不可見時不進行數據的獲取
優化布局層級,減少首次inflate layout的耗時
將絕大部分sdk的初始化放線程池中運行
能用ViewStub的就用ViewStub,按需加載layout
一定要盡量避免啟動過程中,出現的主線程去unpack一些全局配置的數據
不僅僅是三方庫可以放子線程進行,一些時效性要求沒那么高的邏輯都可以放子線程
四、項目結構
在我們的Android項目中,應用過了閃屏之后會進入到主屏 - MainActivity,這個地方我吐槽很多次了,廣告閃屏作為launcher真的不是特別靠譜,最好的方式應該是從MainActivity里面來啟動AdActivity,甚至是不用Activity,采用一個全屏的AdView都可以。
先簡單介紹一下我們項目中MainActivity涉及到的結構:
簡單的畫了個圖,簡直是。。畫圖界的恥辱。。。
大概看看意思就可以了,我在組內分享就是用的這個草圖,急著下班,就不重新畫了。。
當App冷啟動的時候,肉眼可見的要初始化的東西太多了,本身Fragment就是一個相對重的東西。比Activity要輕量很多,但是比View又要重
我們首頁大概是 4-5個tab,每個tab都是一個Fragment,且第一個tab內嵌了4個Fragment,我這一次的優化主要將目標瞄準了首頁的 tab1 以及tab1內嵌的四個tab
五、極致的懶加載
5.1 極致的懶加載
平時見到的懶加載:
就是初始化fragment的時候,會連同我們寫的網絡請求一起執行,這樣非常消耗性能,最理想的方式是,只有用戶點開或滑動到當前fragment時,才進行請求網絡的操作。因此,我們就產生了懶加載這樣一個說法。
但是。。。。
由于我們首屏4個子Tab都是繼承自一個基類BaseLoadListFragment,數據加載的邏輯非常的死,按照上述的改法,影響面太大。后續可能會徒增煩惱
5.2 懶加載方案
首屏加載時,只往ViewPager中塞入默認要展示的tab,剩余的tab用空的占位Fragment代替
當用戶滑動到其他tab時,比如滑動到好友動態tab,就用FriendFragment把當前的EmptyPlaceholderFragment替換掉,然后adapter.notifyDataSetChanged
當四個Tab全部替換為數據tab時,清除掉EmptyFragment的引用,釋放內存
說到這里,又不得不提一個老生常談的一個坑,因為我們的首頁是用的ViewPager + FragmentPagerAdapter來進行實現的。因此就出現了一個坑:
ViewPager + FragmentPagerAdapter組合使用,調用notifyDataSetChanged()方法無效,無法刷新Fragment列表
下面我會對這個問題進行一下詳細的介紹
5.3 FragmentPagerAdapter與FragmentStatePagerAdapter
當我們要使用ViewPager來加載Fragment時,官方為我們提供了這兩種Adapter,都是繼承自PagerAdapter。
區別,上官方描述:
FragmentPagerAdapter
This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider?FragmentStatePagerAdapter.
FragmentStatePagerAdapter
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared toFragmentPagerAdapter?at the cost of potentially more overhead when switching between pages
總結:
使用FragmentStatePagerAdapter時,如果tab對于用戶不可見了,Fragment就會被銷毀,FragmentPagerAdapter則不會,使用FragmentPagerAdapter時,所有的tab上的Fragment都會hold在內存里
當tab非常多時,推薦使用FragmentStatePagerAdapter
當tab不多,且固定時,推薦用FragmentPagerAdapter
我們項目中就是使用的ViewPager+FragmentPagerAdapter。
5.4 FragmentPagerAdapter的刷新問題
正常情況,我們使用adapter時,想要刷新數據只需要:
更新dataSet
調用notifyDataSetChanged()
但是,這個在這個Adapter中是不適用的。因為(這一步沒耐心的可以直接看后面的總結):
默認的PagerAdapter的destoryItem只會把Fragment detach掉,而不會remove
當再次調用instantiateItem的時候,之前detach掉的Fragment,又會從mFragmentManager中取出,又可以attach了
3,ViewPager的dataSetChanged代碼如下:
4,且adapter的默認實現
簡單總結一下:
1,ViewPager的dataSetChanged()中會去用adapter.getItemPosition來判斷是否要移除當前Item(position = POSITION_NONE時remove)
2,PagerAdapter的getItemPosition默認實現為POSITION_UNCHANGED
上述兩點導致ViewPager構建完成Adapter之后,不會有機會調用到Adapter的instantiateItem了。
再者,即使重寫了getItemPosition方法,每次返回POSITION_NONE,還是不會替換掉Fragment,這是因為instantiateItem方法中,會根據getItemId()去從FragmetnManager中找到已經創建好的Fragment返回回去,而getItemId()的默認實現是return position。
5.5?FragmentPagerAdapter刷新的正確姿勢
重寫getItemId()和getItemPosition()
class TabsAdapter extends FragmentPagerAdapter {
? ? ? ? private ArrayList<Fragment> mFragmentList;
? ? ? ? private ArrayList<String> mPageTitleList;
? ? ? ? private int mCount;
? ? ? ? TabsAdapter(FragmentManager fm, ArrayList<Fragment> fragmentList, ArrayList<String> pageTitleList) {
? ? ? ? ? ? super(fm);
? ? ? ? ? ? mFragmentList = fragmentList;
? ? ? ? ? ? mCount = fragmentList.size();
? ? ? ? ? ? mPageTitleList = pageTitleList;
? ? ? ? }
? ? ? ? @Override
? ? ? ? public Fragment getItem(int position) {
? ? ? ? ? ? return mFragmentList.get(position);
? ? ? ? }
? ? ? ? @Override
? ? ? ? public CharSequence getPageTitle(int position) {
? ? ? ? ? ? return mPageTitleList.get(position);
? ? ? ? }
? ? ? ? @Override
? ? ? ? public int getCount() {
? ? ? ? ? ? return mCount;
? ? ? ? }
? ? ? ? @Override
? ? ? ? public long getItemId(int position) {
? ? ? ? ? ? //這個地方的重寫非常關鍵,super中是返回position,
? ? ? ? ? ? //如果不重寫,還是會繼續找到FragmentManager中緩存的Fragment
? ? ? ? ? ? return mFragmentList.get(position).hashCode();
? ? ? ? }
? ? ? ? @Override
? ? ? ? public int getItemPosition(@NonNull Object object) {
? ? ? ? ? ? //不在數據集合里面的話,return POSITION_NONE,進行item的重建
? ? ? ? ? ? int index = mFragmentList.indexOf(object);
? ? ? ? ? ? if (index == -1) {
? ? ? ? ? ? ? ? return POSITION_NONE;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? return mFragmentList.indexOf(object);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? void refreshFragments(ArrayList<Fragment> fragmentList) {
? ? ? ? ? ? mFragmentList = fragmentList;
? ? ? ? ? ? notifyDataSetChanged();
? ? ? ? }
? ? }
其他的相關代碼:
(1)實現ViewPager.OnPageChangeListener,來監控ViewPager的滑動狀態,才可以在滑動到下一個tab的時候進行Fragment替換的操作,其中mDefaultTab是我們通過接口返回的當前啟動展示的tab序號
? ? @Override
? ? public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
? ? }
? ? @Override
? ? public void onPageSelected(int position) {
? ? ? ? mCurrentSelectedTab = position;
? ? }
? ? @Override
? ? public void onPageScrollStateChanged(int state) {
? ? ? ? if (!hasReplacedAllEmptyFragments && mCurrentSelectedTab != mDefaultTab && state == 0) {
? ? ? ? ? ? //當滿足: 1. 沒有全部替換完 2. 當前tab不是初始化的默認tab(默認tab不會用空的Fragment去替換) 3. 滑動結束了,即state = 0
? ? ? ? ? ? replaceEmptyFragmentsIfNeed(mCurrentSelectedTab);
? ? ? ? }
? ? }
備注:
onPageScrollStateChanged接滑動的狀態值。一共有三個取值:
0:什么都沒做
1:開始滑動
2:滑動結束
一次引起頁面切換的滑動,state的順序分別是: 1? ->? 2? ->? 0
(2)進行Fragment的替換,這里因為我們的tab數量是可能根據全局config信息而改變的,所以這個地方寫的稍微糾結了一些。
? ? /**
? ? * 如果全部替換完了,直接return
? ? * 替換過程:
? ? * 1. 找到當前空的tab在mEmptyFragmentList 中的實際下標
? ? *
? ? * @param tabId 要替換的tab的tabId - (當前空的Fragment在adapter數據列表mFragmentList的下標)
? ? */
? ? private void replaceEmptyFragmentsIfNeed(int tabId) {
? ? ? ? if (hasReplacedAllEmptyFragments) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? int tabRealIndex = mEmptyFragmentList.indexOf(mFragmentList.get(tabId)); //找到當前的空Fragment在 mEmptyFragmentList 是第幾個
? ? ? ? if (tabRealIndex > -1) {
? ? ? ? ? ? if (Collections.replaceAll(mFragmentList, mEmptyFragmentList.get(tabRealIndex), mDataFragmentList.get(tabRealIndex))) {
? ? ? ? ? ? ? ? mTabsAdapter.refreshFragments(mFragmentList); //將mFragmentList中的相應empty fragment替換完成之后刷新數據
? ? ? ? ? ? ? ? boolean hasAllReplaced = true;
? ? ? ? ? ? ? ? for (Fragment fragment : mFragmentList) {
? ? ? ? ? ? ? ? ? ? if (fragment instanceof EmptyPlaceHolderFragment) {
? ? ? ? ? ? ? ? ? ? ? ? hasAllReplaced = false;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (hasAllReplaced) {
? ? ? ? ? ? ? ? ? ? mEmptyFragmentList.clear(); //全部替換完成的話,釋放引用
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? hasReplacedAllEmptyFragments = hasAllReplaced;
? ? ? ? ? ? }
? ? ? ? }
? ? }
六、神奇的的預加載(預加載View,而不是data)
Android在啟動過程中可能涉及到的一些View的預加載方案:
WebView提前創建好,因為webview創建的耗時較長,如果首屏有h5的頁面,可以提前創建好。
Application的onCreate時,就可以開始在子線程中進行后面要用到的Layout的inflate工作了,最先想到的應該是官方提供的AsyncLayoutInflater
填充View的數據的預加載,今天的內容不涉及這一項
6.1 需要預加載什么
直接看圖,這個是首頁四個子Tab Fragment的基類的layout,因為某些東西設計的不合理,導致層級是非常的深,直接導致了首頁上的三個tab加上FeedMainFragment自身,光將這個View inflate出來的時間就非常長。因此我們考慮在子線程中提前inflate layout
6.2?修改AsyncLayoutInflater
官方提供了一個類,可以來進行異步的inflate,但是有兩個缺點:
每次都要現場new一個出來
異步加載的view只能通過callback回調才能獲得(死穴)
因此決定自己封裝一個AsyncInflateManager,內部使用線程池,且對于inflate完成的View有一套緩存機制。而其中最核心的LayoutInflater則直接copy出來就好。
先看AsyncInflateManager的實現,這里我直接將代碼copy進來,而不是截圖了,這樣你們如果想用其中部分東西,可以直接copy:
/**
* @author zoutao
* <p>
* 用來提供子線程inflate view的功能,避免某個view層級太深太復雜,主線程inflate會耗時很長,
* 實就是對 AsyncLayoutInflater進行了抽取和封裝
*/
public class AsyncInflateManager {
? ? private static AsyncInflateManager sInstance;
? ? private ConcurrentHashMap<String, AsyncInflateItem> mInflateMap; //保存inflateKey以及InflateItem,里面包含所有要進行inflate的任務
? ? private ConcurrentHashMap<String, CountDownLatch> mInflateLatchMap;
? ? private ExecutorService mThreadPool; //用來進行inflate工作的線程池
? ? private AsyncInflateManager() {
? ? ? ? mThreadPool = new ThreadPoolExecutor(4, 4, 0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());
? ? ? ? mInflateMap = new ConcurrentHashMap<>();
? ? ? ? mInflateLatchMap = new ConcurrentHashMap<>();
? ? }
? ? public static AsyncInflateManager getInstance() {
? ? ? ? 單例
? ? }
? ? /**
? ? * 用來獲得異步inflate出來的view
? ? *
? ? * @param context
? ? * @param layoutResId 需要拿的layoutId
? ? * @param parent? ? ? container
? ? * @param inflateKey? 每一個View會對應一個inflateKey,因為可能許多地方用的同一個 layout,但是需要inflate多個,用InflateKey進行區分
? ? * @param inflater? ? 外部傳進來的inflater,外面如果有inflater,傳進來,用來進行可能的SyncInflate,
? ? * @return 最后inflate出來的view
? ? */
? ? @UiThread
? ? @NonNull
? ? public View getInflatedView(Context context, int layoutResId, @Nullable ViewGroup parent, String inflateKey, @NonNull LayoutInflater inflater) {
? ? ? ? if (!TextUtils.isEmpty(inflateKey) && mInflateMap.containsKey(inflateKey)) {
? ? ? ? ? ? AsyncInflateItem item = mInflateMap.get(inflateKey);
? ? ? ? ? ? CountDownLatch latch = mInflateLatchMap.get(inflateKey);
? ? ? ? ? ? if (item != null) {
? ? ? ? ? ? ? ? View resultView = item.inflatedView;
? ? ? ? ? ? ? ? if (resultView != null) {
? ? ? ? ? ? ? ? ? ? //拿到了view直接返回
? ? ? ? ? ? ? ? ? ? removeInflateKey(inflateKey);
? ? ? ? ? ? ? ? ? ? replaceContextForView(resultView, context);
? ? ? ? ? ? ? ? ? ? return resultView;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (item.isInflating() && latch != null) {
? ? ? ? ? ? ? ? ? ? //沒拿到view,但是在inflate中,等待返回
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? latch.wait();
? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? Log.e(TAG, e.getMessage(), e);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? removeInflateKey(inflateKey);
? ? ? ? ? ? ? ? ? ? if (resultView != null) {
? ? ? ? ? ? ? ? ? ? ? ? replaceContextForView(resultView, context);
? ? ? ? ? ? ? ? ? ? ? ? return resultView;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //如果還沒開始inflate,則設置為false,UI線程進行inflate
? ? ? ? ? ? ? ? item.setCancelled(true);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //拿異步inflate的View失敗,UI線程inflate
? ? ? ? return inflater.inflate(layoutResId, parent, false);
? ? }
? ? /**
? ? * inflater初始化時是傳進來的application,inflate出來的view的context沒法用來startActivity,
? ? * 因此用MutableContextWrapper進行包裝,后續進行替換
? ? */
? ? private void replaceContextForView(View inflatedView, Context context) {
? ? ? ? if (inflatedView == null || context == null) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? Context cxt = inflatedView.getContext();
? ? ? ? if (cxt instanceof MutableContextWrapper) {
? ? ? ? ? ? ((MutableContextWrapper) cxt).setBaseContext(context);
? ? ? ? }
? ? }
? ? @UiThread
? ? private void asyncInflate(Context context, AsyncInflateItem item) {
? ? ? ? if (item == null || item.layoutResId == 0 || mInflateMap.containsKey(item.inflateKey) || item.isCancelled() || item.isInflating()) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? onAsyncInflateReady(item);
? ? ? ? inflateWithThreadPool(context, item);
? ? }
? ? private void onAsyncInflateReady(AsyncInflateItem item) {
? ? ? ...
? ? }
? ? private void onAsyncInflateStart(AsyncInflateItem item) {
? ? ? ? ...
? ? }
? ? private void onAsyncInflateEnd(AsyncInflateItem item, boolean success) {
? ? ? ? item.setInflating(false);
? ? ? ? CountDownLatch latch = mInflateLatchMap.get(item.inflateKey);
? ? ? ? if (latch != null) {
? ? ? ? ? ? //釋放鎖
? ? ? ? ? ? latch.countDown();
? ? ? ? }
? ? ? ? ...
? ? }
? ? private void removeInflateKey(String inflateKey) {
? ? ? ? ...
? ? }
? ? private void inflateWithThreadPool(Context context, AsyncInflateItem item) {
? ? ? ? mThreadPool.execute(new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? if (!item.isInflating() && !item.isCancelled()) {
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? onAsyncInflateStart(item);
? ? ? ? ? ? ? ? ? ? ? ? item.inflatedView = new BasicInflater(context).inflate(item.layoutResId, item.parent, false);
? ? ? ? ? ? ? ? ? ? ? ? onAsyncInflateEnd(item, true);
? ? ? ? ? ? ? ? ? ? } catch (RuntimeException e) {
? ? ? ? ? ? ? ? ? ? ? ? Log.e(TAG, "Failed to inflate resource in the background! Retrying on the UI thread", e);
? ? ? ? ? ? ? ? ? ? ? ? onAsyncInflateEnd(item, false);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? });
? ? }
? ? /**
? ? * copy from AsyncLayoutInflater - actual inflater
? ? */
? ? private static class BasicInflater extends LayoutInflater {
? ? ? ? private static final String[] sClassPrefixList = new String[]{"android.widget.", "android.webkit.", "android.app."};
? ? ? ? BasicInflater(Context context) {
? ? ? ? ? ? super(context);
? ? ? ? }
? ? ? ? public LayoutInflater cloneInContext(Context newContext) {
? ? ? ? ? ? return new BasicInflater(newContext);
? ? ? ? }
? ? ? ? protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
? ? ? ? ? ? for (String prefix : sClassPrefixList) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? View view = this.createView(name, prefix, attrs);
? ? ? ? ? ? ? ? ? ? if (view != null) {
? ? ? ? ? ? ? ? ? ? ? ? return view;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } catch (ClassNotFoundException ignored) {
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? return super.onCreateView(name, attrs);
? ? ? ? }
? ? }
}
這里我用一個AsyncInflateItem來管理一次要inflate的一個單位,
/**
* @author zoutao
*/
public class AsyncInflateItem {
? ? String inflateKey;
? ? int layoutResId;
? ? ViewGroup parent;
? ? OnInflateFinishedCallback callback;
? ? View inflatedView;
? ? private boolean cancelled;
? ? private boolean inflating;
? ? //還有一些set get方法
}
以及最后inflate的回調callback:
public interface OnInflateFinishedCallback {
? ? void onInflateFinished(AsyncInflateItem result);
}
經過這樣的封裝,外面可以直接在Application的onCreate中,開始異步的inflate view的任務。調用如下:
AsyncInflateUtil.startTask();
/**
* @author zoutao
*/
public class AsyncInflateUtil {
? ? public static void startTask() {
? ? ? ? Context context = new MutableContextWrapper(CommonContext.getApplication());
? ? ? ? AsyncInflateManager.getInstance().asyncInflateViews(context,
? ? ? ? ? ? ? ? new AsyncInflateItem(InflateKey.TAB_1_CONTAINER_FRAGMENT, R.layout.fragment_main),
? ? ? ? ? ? ? ? new AsyncInflateItem(InflateKey.SUB_TAB_1_FRAGMENT, R.layout.fragment_load_list),
? ? ? ? ? ? ? ? new AsyncInflateItem(InflateKey.SUB_TAB_2_FRAGMENT, R.layout.fragment_load_list),
? ? ? ? ? ? ? ? new AsyncInflateItem(InflateKey.SUB_TAB_3_FRAGMENT, R.layout.fragment_load_list),
? ? ? ? ? ? ? ? new AsyncInflateItem(InflateKey.SUB_TAB_4_FRAGMENT, R.layout.fragment_load_list));
? ? }
? ? public class InflateKey {
? ? ? ? public static final String TAB_1_CONTAINER_FRAGMENT = "tab1";
? ? ? ? public static final String SUB_TAB_1_FRAGMENT = "sub1";
? ? ? ? public static final String SUB_TAB_2_FRAGMENT = "sub2";
? ? ? ? public static final String SUB_TAB_3_FRAGMENT = "sub3";
? ? ? ? public static final String SUB_TAB_4_FRAGMENT = "sub4";
? ? }
}
注意:這里會有一個坑。就是在Application的onCreate中,能拿到的Context只有Application,這樣inflate的View,View持有的Context就是Application,這會導致一個問題。
如果用View.getContext()這個context去進行Activity的跳轉就會。。拋異常
Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
而如果想要傳入Activity來創建LayoutInflater,時機又太晚。眾所周知,Context是一個抽象類,實現它的包裝類就是ContextWrapper,而Activity、Appcation等都是ContextWrapper的子類,然而,ContextWrapper還有一個神奇的子類,
package android.content;
/**
* Special version of {@link ContextWrapper} that allows the base context to
* be modified after it is initially set.
*/
public class MutableContextWrapper extends ContextWrapper {
? ? public MutableContextWrapper(Context base) {
? ? ? ? super(base);
? ? }
? ? /**
? ? * Change the base context for this ContextWrapper. All calls will then be
? ? * delegated to the base context.? Unlike ContextWrapper, the base context
? ? * can be changed even after one is already set.
? ? *
? ? * @param base The new base context for this wrapper.
? ? */
? ? public void setBaseContext(Context base) {
? ? ? ? mBase = base;
? ? }
}
6.3 裝飾器模式
可以看到Android上Context的設計采用了裝飾器模式,裝飾器模式極大程度的提高了靈活性。這個例子對我最大的感受就是,當官方沒有提供MutableContextWrapper這個類時,其實我們自己也完全可以通過同樣的方式去進行實現。思維一定要靈活~
七、總結
常見的啟動速度優化的方案有:
數據懶加載,比如Fragment用戶不可見時不進行數據的獲取
優化布局層級,減少首次inflate layout的耗時
將絕大部分sdk的初始化放線程池中運行
能用ViewStub的就用ViewStub,按需加載layout
一定要盡量避免啟動過程中,出現的主線程去unpack一些全局配置的數據
不僅僅是三方庫可以放子線程進行,一些時效性要求沒那么高的邏輯都可以放子線程
這些都可以在網上找到大量的文章以及各個大佬的實現方案。
首先,優化的大方向肯定先定好:
懶加載
預加載
懶加載:
首屏加載時,只往ViewPager中塞入默認要展示的tab,剩余的tab用空的占位Fragment代替
當用戶滑動到其他tab時,比如滑動到好友動態tab,就用FriendFragment把當前的EmptyPlaceholderFragment替換掉,然后adapter.notifyDataSetChanged
當四個Tab全部替換為數據tab時,清除掉EmptyFragment的引用,釋放內存
預加載:
Application onCreate方法中,針對后續所有的Fragment,在子線程中將Layout先給inflate出來
針對inflate完成的View加入一套緩存的存取機制,以及等待機制
如果正在inflate,則進行阻塞等待
如果已經inflate完成了,取出view,并釋放緩存對于View的引用
如果還沒有開始Inflate,則在UI線程直接進行inflate
這些方案不一定非常好使,所以僅供參考~~~
從ContextWrapper、MutableContextWrapper類的設計中學到了? ↓
寫代碼的時候,首先要進行設計,選用最合適的設計模式,這樣后續賺到的遠遠大于寫一個文檔、想一個設計所耗費的時間和腦力成本
我的簡書?鄒啊濤濤濤的簡書
我的CSDN?鄒啊濤濤濤的CSDN
我的掘金?鄒啊濤濤濤的掘金