Android中ViewPager懶加載的優(yōu)化詳解
01.ViewPager簡單介紹
ViewPager使用一個鍵對象來關(guān)聯(lián)每一頁,而不是管理View。這個鍵用于追蹤和唯一標(biāo)識在adapter中獨(dú)立位置中的一頁。調(diào)用方法startUpdate(ViewGroup)表明ViewPager中的內(nèi)容需要更改。
通過調(diào)用一次或多次調(diào)用instantiateItem(ViewGroup, int)來構(gòu)造頁面視圖。
調(diào)用destroyItem(ViewGroup, int, Object)來取消ViewPager關(guān)聯(lián)的頁面視圖。
最后,當(dāng)一次更新(添加和/或移除)完成之后將會調(diào)用finishUpdate(ViewGroup)來通知adapter, 提交關(guān)聯(lián)和/或取消關(guān)聯(lián)的操作。這三個方法就是用于ViewPager使用回調(diào)的方式來通知PagerAdapter來管理其中的頁面。
一個非常簡單的方式就是使用每頁視圖作為key來關(guān)聯(lián)它們自己,在方法instantiateItem(ViewGroup, int)中創(chuàng)建和添加它們到ViewGroup之后,返回該頁視圖。與之相匹配的方法destroyItem(ViewGroup, int, Object)實(shí)現(xiàn)從ViewGroup中移除視圖。當(dāng)然必須在isViewFromObject(View, Object)中這樣實(shí)現(xiàn):return view == object;.
PagerAdapter支持?jǐn)?shù)據(jù)改變時刷新界面,數(shù)據(jù)改變必須在主線程中調(diào)用,并在數(shù)據(jù)改變完成后調(diào)用方法notifyDataSetChanged(), 和AdapterView中派生自BaseAdapter相似。一次數(shù)據(jù)的改變可能關(guān)聯(lián)著頁面的添加、移除、或改變位置。ViewPager將根據(jù)adapter中實(shí)現(xiàn)getItemPosition(Object)方法返回的結(jié)果,來判斷是否保留當(dāng)前已經(jīng)構(gòu)造的活動頁面(即重用,而不完全自行構(gòu)造)。
02.ViewPager弊端分析
普通的viewpager如果你不使用setoffscreenpagelimit(int limit)這個方法去設(shè)置默認(rèn)加載數(shù)的話是會默認(rèn)加載頁面的左右兩頁的,也就是說當(dāng)你進(jìn)入viewpager第一頁的時候第二頁和第一頁是會被一起加載的,這樣同時加載就會造成一些問題,試想我們?nèi)绻O(shè)置了setoffscreenpagelimit為3的話,那么進(jìn)入viewpager以后就會同時加載4個fragment,像我們平時的項(xiàng)目中在這些fragment中一般都是會發(fā)送網(wǎng)絡(luò)請求的,也就是說我們有4個fragment同時發(fā)送網(wǎng)絡(luò)請求去獲取數(shù)據(jù),這樣的結(jié)果顯而易見給用戶的體驗(yàn)是不好的(如:浪費(fèi)用戶流量,造成卡頓等等)。
懶加載的實(shí)現(xiàn)弊端
- 概念:當(dāng)需要時才加載,加載之后一直保持該對象。
- 而關(guān)于Fragment實(shí)現(xiàn)的PagerAdapter都沒有完全保存其引用和狀態(tài)。FragmentPageAdapter需要重建視圖,F(xiàn)ragmentStatePageAdapter使用狀態(tài)恢復(fù),View都被銷毀,但是恢復(fù)的方式不同,而通常我們想得到的結(jié)果是,F(xiàn)ragment一旦被加載,其視圖也不會被銷毀,即不會再重新走一遍生命周期。而且ViewPager為了實(shí)現(xiàn)滑動效果,都是預(yù)加載左右兩側(cè)的頁面。
- 我們通常想要實(shí)現(xiàn)的兩種效果:不提供滑動,需要時才構(gòu)造,并且只走一遍生命周期,避免在Fragment中做過多的狀態(tài)保存和恢復(fù)。
03.ViewPager預(yù)加載
ViewPager的預(yù)加載機(jī)制。那么,我們可不可以設(shè)置ViewPager的預(yù)加載為0,不就解決問題了嗎?也就是代碼這樣操作:
vp.setOffscreenPageLimit(0);
然后看一下源碼
public void setOffscreenPageLimit(int limit) { if (limit < 1) { Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1); limit = 1; } if (limit != this.mOffscreenPageLimit) { this.mOffscreenPageLimit = limit; this.populate(); } }
即使你設(shè)置為0,那么還是會在里面判斷后設(shè)為默認(rèn)值1。所以這個方法是行不通的。
ViewPager默認(rèn)情況下的加載,當(dāng)切換到當(dāng)前頁面時,會默認(rèn)預(yù)加載左右兩側(cè)的布局到ViewPager中,盡管兩側(cè)的View并不可見的,我們稱這種情況叫預(yù)加載;由于ViewPager對offscreenPageLimit設(shè)置了限制,頁面的預(yù)加載是不可避免……
初始化緩存(mOffscreenPageLimit == 1)
當(dāng)初始化時,當(dāng)前顯示頁面是第0頁;mOffscreenPageLimit為1,所以預(yù)加載頁面為第1頁,再往后的頁面就不需要加載了(這里的2, 3, 4頁)
中間頁面緩存(mOffscreenPageLimit == 1)
當(dāng)向右滑動到第2頁時,左右分別需要緩存一頁,第0頁就需要銷毀掉,第3頁需要預(yù)加載,第4頁不需要加載
04.ViewPager部分源碼
ViewPager.setAdapter方法
- 銷毀舊的Adapter數(shù)據(jù),用新的Adaper更新UI
- 清除舊的Adapter,對已加載的item調(diào)用destroyItem,
- 將自身滾動到初始位置this.scrollTo(0, 0)
- 設(shè)置PagerObserver: mAdapter.setViewPagerObserver(mObserver);
- 調(diào)用populate()方法計(jì)算并初始化View(這個方法后面會詳細(xì)介紹)
- 如果設(shè)置了OnAdapterChangeListener,進(jìn)行回調(diào)
ViewPager.populate(int newCurrentItem)
- 該方法是ViewPager非常重要的方法,主要根據(jù)參數(shù)newCurrentItem和mOffscreenPageLimit計(jì)算出需要初始化的頁面和需要銷毀頁面,然后通過調(diào)用Adapter的instantiateItem和destroyItem兩個方法初始化新頁面和銷毀不需要的頁面!
- 根據(jù)newCurrentItem和mOffscreenPageLimit計(jì)算要加載的page頁面,計(jì)算出startPos和endPos
- 根據(jù)startPos和endPos初始化頁面ItemInfo,先從緩存里面獲取,如果沒有就調(diào)用addNewItem方法,實(shí)際調(diào)用mAdapter.instantiateItem
- 將不需要的ItemInfo移除: mItems.remove(itemIndex),并調(diào)用mAdapter.destroyItem方法
- 設(shè)置LayoutParams參數(shù)(包括position和widthFactor),根據(jù)position排序待繪制的View列表:mDrawingOrderedChildren,重寫了getChildDrawingOrder方法
- 最后一步獲取當(dāng)前顯示View的焦點(diǎn):currView.requestFocus(View.FOCUS_FORWARD)
ViewPager.dataSetChanged()
- 當(dāng)調(diào)用Adapter的notifyDataSetChanged時,會觸發(fā)這個方法,該方法會重新計(jì)算當(dāng)前頁面的position,
- 移除需要銷毀的頁面的ItemInfo對象,然后再調(diào)用populate方法刷新頁面
- 循環(huán)mItems(每個page對應(yīng)的ItemInfo對象),調(diào)用int newPos = mAdapter.getItemPosition方法
- 當(dāng)newPos等于PagerAdapter.POSITION_UNCHANGED表示當(dāng)前頁面不需要更新,不用銷毀,當(dāng)newPos等于PagerAdapter.POSITION_NONE時,需要更新,移除item,調(diào)用mAdapter.destroyItem
- 循環(huán)完成后,最后計(jì)算出顯示頁面的newCurrItem,調(diào)用setCurrentItemInternal(newCurrItem, false, true)方法更新UI(實(shí)際調(diào)用populate方法重新計(jì)算頁面信息)
ViewPager.scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected)
滑動到指定頁面,內(nèi)部會觸發(fā)OnPageChangeListener
ViewPager.calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo)
- 這個方法主要用于計(jì)算每個頁面對應(yīng)ItemInfo的offset變量,這個變量用于記錄當(dāng)前view在所有緩存View中(包含當(dāng)前顯示頁)的索引,用于布局的時候計(jì)算該View應(yīng)該放在哪個位置
- 在populate方法中更新完頁面數(shù)據(jù)后,會調(diào)用該方法計(jì)算所有頁面的offset
05.懶加載出現(xiàn)問題
發(fā)現(xiàn)Fragment中有一個setUserVisibleHint(boolean isVisibleToUser)方法,這個方法就是告訴用戶,UI對用戶是否可見,可以做懶加載初始化操作。
因?yàn)閂iewPager會加載好多Fragment,為了節(jié)省內(nèi)容等會在Fragment不可見的某個時候調(diào)用onDestroyView()將用戶界面銷毀掉但是Fragment的實(shí)例還在,所以可能第一次加載沒有問題,但是再次回到第一個Fragment再去加載的時候就會出現(xiàn)UI對用戶可見但是視圖還沒有初始化。
懶加載需要處理的幾個問題
預(yù)加載,雖然沒有顯示在界面上,但是當(dāng)前頁面的上一頁和下一頁的Fragment已經(jīng)執(zhí)行了一個Fragment能夠顯示在界面上的所有生命周期方法,但是我們想在跳轉(zhuǎn)到該頁時才真正構(gòu)造數(shù)據(jù)視圖和請求數(shù)據(jù)。那么我們可以使用一個占位視圖,那么可以想到使用ViewStub,當(dāng)真正跳轉(zhuǎn)到該頁時,執(zhí)行ViewStub.inflate()方法,加載真正的數(shù)據(jù)視圖和請求數(shù)據(jù)。
視圖保存
當(dāng)某一頁超出可視范圍和預(yù)加載范圍,那么它將會被銷毀,F(xiàn)ragmentStatePagerAdapter銷毀整個Fragment, 我們可以自己保存該Fragment,或使用FragmentPagerAdapter讓FragmentTransition來保留Fragment的引用。雖然這樣,但是它的周期方法已經(jīng)走完,那么我們只能手動的保存Fragment根View的引用,當(dāng)再次重新進(jìn)入新的聲明周期方法時,返回原來的View
是否已經(jīng)被用戶所看到
其實(shí)本身而言,F(xiàn)ragmentManager并沒有提供為Fragment被用戶所看到的回調(diào)方法,而是在FragmentPagerAdapter和FragmentStatePagerAdapter中,調(diào)用了Fragment.setUserVisibleHint(boolean)來表明Fragment是否已經(jīng)被作為primaryFragment. 所以這個方法可以被認(rèn)為是一個回調(diào)方法。
06.如何實(shí)現(xiàn)預(yù)加載機(jī)制
主要的方法是Fragment中的setUserVisibleHint(),此方法會在onCreateView()之前執(zhí)行,當(dāng)viewPager中fragment改變可見狀態(tài)時也會調(diào)用,當(dāng)fragment 從可見到不見,或者從不可見切換到可見,都會調(diào)用此方法,使用getUserVisibleHint() 可以返回fragment是否可見狀態(tài)。
在BaseLazyFragment中需要在onActivityCreated()及setUserVisibleHint()方法中都調(diào)了一次lazyLoad() 方法。如果僅僅在setUserVisibleHint()調(diào)用lazyLoad(),當(dāng)默認(rèn)首頁首先加載時會導(dǎo)致viewPager的首頁第一次展示時沒有數(shù)據(jù)顯示,切換一下才會有數(shù)據(jù)。因?yàn)槭醉揻ragment的setUserVisible()在onActivityCreated() 之前調(diào)用,此時isPrepared為false 導(dǎo)致首頁fragment 沒能調(diào)用onLazyLoad()方法加載數(shù)據(jù)。
/** * <pre> * @author yangchong * blog : https://github.com/yangchong211 * time : 2017/7/22 * desc : 懶加載 * revise: 懶加載時機(jī):onCreateView()方法執(zhí)行完畢 + setUserVisibleHint()方法返回true * </pre> */ public abstract class BaseLazyFragment extends BaseFragment { /* * 預(yù)加載頁面回調(diào)的生命周期流程: * setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()--> * onActivityCreate() --> onStart() --> onResume() */ /** * 懶加載過 */ protected boolean isLazyLoaded = false; /** * Fragment的View加載完畢的標(biāo)記 */ private boolean isPrepared = false; /** * 第一步,改變isPrepared標(biāo)記 * 當(dāng)onViewCreated()方法執(zhí)行時,表明View已經(jīng)加載完畢,此時改變isPrepared標(biāo)記為true,并調(diào)用lazyLoad()方法 */ @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); isPrepared = true; //只有Fragment onCreateView好了 //另外這里調(diào)用一次lazyLoad() lazyLoad(); } /** * 第二步 * 此方法會在onCreateView()之前執(zhí)行 * 當(dāng)viewPager中fragment改變可見狀態(tài)時也會調(diào)用 * 當(dāng)fragment 從可見到不見,或者從不可見切換到可見,都會調(diào)用此方法 * true表示當(dāng)前頁面可見,false表示不可見 */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); LogUtil.d("setUserVisibleHint---"+isVisibleToUser); //只有當(dāng)fragment可見時,才進(jìn)行加載數(shù)據(jù) if (isVisibleToUser){ lazyLoad(); } } /** * 調(diào)用懶加載 * 第三步:在lazyLoad()方法中進(jìn)行雙重標(biāo)記判斷,通過后即可進(jìn)行數(shù)據(jù)加載 */ private void lazyLoad() { if (getUserVisibleHint() && isPrepared && !isLazyLoaded) { showFirstLoading(); onLazyLoad(); isLazyLoaded = true; } else { //當(dāng)視圖已經(jīng)對用戶不可見并且加載過數(shù)據(jù),如果需要在切換到其他頁面時停止加載數(shù)據(jù),可以覆寫此方法 if (isLazyLoaded) { stopLoad(); } } } /** * 視圖銷毀的時候講Fragment是否初始化的狀態(tài)變?yōu)閒alse */ @Override public void onDestroyView() { super.onDestroyView(); isLazyLoaded = false; isPrepared = false; } /** * 第一次可見時,操作該方法,可以用于showLoading操作,注意這個是全局加載loading */ protected void showFirstLoading() { LogUtil.i("第一次可見時show全局loading"); } /** * 停止加載 * 當(dāng)視圖已經(jīng)對用戶不可見并且加載過數(shù)據(jù),但是沒有加載完,而只是加載loading。 * 如果需要在切換到其他頁面時停止加載數(shù)據(jù),可以覆寫此方法。 * 存在問題,如何停止加載網(wǎng)絡(luò) */ protected void stopLoad(){ } /** * 第四步:定義抽象方法onLazyLoad(),具體加載數(shù)據(jù)的工作,交給子類去完成 */ @UiThread protected abstract void onLazyLoad(); }
onLazyLoad()加載數(shù)據(jù)條件
- getUserVisibleHint()會返回是否可見狀態(tài),這是fragment實(shí)現(xiàn)懶加載的關(guān)鍵,只有fragment 可見才會調(diào)用onLazyLoad() 加載數(shù)據(jù)。
- isPrepared參數(shù)在系統(tǒng)調(diào)用onActivityCreated時設(shè)置為true,這時onCreateView方法已調(diào)用完畢(一般我們在這方法里執(zhí)行findviewbyid等方法),確保 onLazyLoad()方法不會報(bào)空指針異常。
- isLazyLoaded確保ViewPager來回切換時BaseFragment的initData方法不會被重復(fù)調(diào)用,onLazyLoad在該Fragment的整個生命周期只調(diào)用一次,第一次調(diào)用onLazyLoad()方法后馬上執(zhí)行 isLazyLoaded = true。
- 然后再繼承這個BaseLazyFragment實(shí)現(xiàn)onLazyLoad() 方法就行。他會自動控制當(dāng)fragment 展現(xiàn)出來時,才會加載數(shù)據(jù)
還有幾個細(xì)節(jié)需要優(yōu)化一下
- 當(dāng)視圖已經(jīng)對用戶不可見并且加載過數(shù)據(jù),如果需要在切換到其他頁面時停止加載數(shù)據(jù),可以覆寫此方法,也就是stopLoad
- 視圖銷毀的時候講Fragment是否初始化的狀態(tài)變?yōu)閒alse,這個也需要處理一下
- 第一次可見時,定義一個showFirstLoading方法,操作該方法,可以用于Loading加載操作,注意這個是全局加載loading,和下拉刷新數(shù)據(jù)或者局部刷新的loading不一樣的??赡苡行╅_發(fā)app,沒有將loading分的這么細(xì)。
07.懶加載配合狀態(tài)管理器
什么是狀態(tài)管理器?
- 一般在需要用戶等待的場景,顯示一個Loading動畫可以讓用戶知道App正在加載數(shù)據(jù),而不是程序卡死,從而給用戶較好的使用體驗(yàn)。
- 當(dāng)加載的數(shù)據(jù)為空時顯示一個數(shù)據(jù)為空的視圖、在數(shù)據(jù)加載失敗時顯示加載失敗對應(yīng)的UI并支持點(diǎn)擊重試會比白屏的用戶體驗(yàn)更好一些。
- 加載中、加載失敗、空數(shù)據(jù)的UI風(fēng)格,一般來說在App內(nèi)的所有頁面中需要保持一致,也就是需要做到全局統(tǒng)一。
如何降低偶性和入侵性
讓View狀態(tài)的切換和Activity徹底分離開,必須把這些狀態(tài)View都封裝到一個管理類中,然后暴露出幾個方法來實(shí)現(xiàn)View之間的切換。 在不同的項(xiàng)目中可以需要的View也不一樣,所以考慮把管理類設(shè)計(jì)成builder模式來自由的添加需要的狀態(tài)View。
那么如何降低耦合性,讓代碼入侵性低。方便維護(hù)和修改,且移植性強(qiáng)呢?大概具備這樣的條件……
- 可以運(yùn)用在activity或者fragment中
- 不需要在布局中添加LoadingView,而是統(tǒng)一管理不同狀態(tài)視圖,同時暴露對外設(shè)置自定義狀態(tài)視圖方法,方便UI特定頁面定制
- 支持設(shè)置自定義不同狀態(tài)視圖,即使在BaseActivity統(tǒng)一處理狀態(tài)視圖管理,也支持單個頁面定制
- 在加載視圖的時候像異常和空頁面能否用ViewStub代替,這樣減少繪制,只有等到出現(xiàn)異常和空頁面時,才將視圖給inflate出來
- 當(dāng)頁面出現(xiàn)網(wǎng)絡(luò)異常頁面,空頁面等,頁面會有交互事件,這時候可以設(shè)置點(diǎn)擊設(shè)置網(wǎng)絡(luò)或者點(diǎn)擊重新加載等等
那么具體怎么操作呢?
可以自由切換內(nèi)容,空數(shù)據(jù),異常錯誤,加載,網(wǎng)絡(luò)錯誤等5種狀態(tài)。父類BaseFragment直接暴露5中狀態(tài),方便子類統(tǒng)一管理狀態(tài)切換,這里fragment的封裝和activity差不多。
/** * <pre> * @author yangchong * blog : https://github.com/yangchong211 * time : 2017/7/20 * desc : fragment的父類 * revise: 注意,該類具有懶加載 * </pre> */ public abstract class BaseStateFragment extends BaseLazyFragment { protected StateLayoutManager statusLayoutManager; private View view; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { if(view==null){ view = inflater.inflate(R.layout.base_state_view, container , false); initStatusLayout(); initBaseView(view); } return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); initView(view); initListener(); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } /** * 獲取到子布局 * @param view view */ private void initBaseView(View view) { LinearLayout llStateView = view.findViewById(R.id.ll_state_view); llStateView.addView(statusLayoutManager.getRootLayout()); } /** * 初始化狀態(tài)管理器相關(guān)操作 */ protected abstract void initStatusLayout(); /** * 初始化View的代碼寫在這個方法中 * @param view view */ public abstract void initView(View view); /** * 初始化監(jiān)聽器的代碼寫在這個方法中 */ public abstract void initListener(); /** * 第一次可見狀態(tài)時,showLoading操作,注意下拉刷新操作時不要用該全局loading */ @Override protected void showFirstLoading() { super.showFirstLoading(); showLoading(); } /*protected void initStatusLayout() { statusLayoutManager = StateLayoutManager.newBuilder(activity) .contentView(R.layout.common_fragment_list) .emptyDataView(R.layout.view_custom_empty_data) .errorView(R.layout.view_custom_data_error) .loadingView(R.layout.view_custom_loading_data) .netWorkErrorView(R.layout.view_custom_network_error) .build(); }*/ /*---------------------------------下面是狀態(tài)切換方法-----------------------------------------*/ /** * 加載成功 */ protected void showContent() { if (statusLayoutManager!=null){ statusLayoutManager.showContent(); } } /** * 加載無數(shù)據(jù) */ protected void showEmptyData() { if (statusLayoutManager!=null){ statusLayoutManager.showEmptyData(); } } /** * 加載異常 */ protected void showError() { if (statusLayoutManager!=null){ statusLayoutManager.showError(); } } /** * 加載網(wǎng)絡(luò)異常 */ protected void showNetWorkError() { if (statusLayoutManager!=null){ statusLayoutManager.showNetWorkError(); } } /** * 加載loading */ protected void showLoading() { if (statusLayoutManager!=null){ statusLayoutManager.showLoading(); } } } //如何切換狀態(tài)呢? showContent(); showEmptyData(); showError(); showLoading(); showNetWorkError(); //或者這樣操作也可以 statusLayoutManager.showLoading(); statusLayoutManager.showContent();
狀態(tài)管理器的設(shè)計(jì)思路
StateFrameLayout是繼承FrameLayout自定義布局,主要是存放不同的視圖,以及隱藏和展示視圖操作
StateLayoutManager是狀態(tài)管理器,主要是讓開發(fā)者設(shè)置不同狀態(tài)視圖的view,以及切換視圖狀態(tài)操作
幾種異常狀態(tài)要用ViewStub,因?yàn)樵诮缑鏍顟B(tài)切換中l(wèi)oading和內(nèi)容View都是一直需要加載顯示的,但是其他的3個只有在沒數(shù)據(jù)或者網(wǎng)絡(luò)異常的情況下才會加載顯示,所以用ViewStub來加載他們可以提高性能。
OnRetryListener,為接口,主要是重試作用。比如加載失敗了,點(diǎn)擊視圖需要重新刷新接口,則可以用到這個。開發(fā)者也可以自己設(shè)置點(diǎn)擊事件
關(guān)于狀態(tài)視圖切換方案,目前市場有多種做法,具體可以看我的這篇博客:Android中不同狀態(tài)頁面管理優(yōu)化
到此這篇關(guān)于Android中ViewPager懶加載的優(yōu)化詳解的文章就介紹到這了,更多相關(guān)Android ViewPager懶加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Kotlin協(xié)程Context應(yīng)用使用示例詳解
這篇文章主要為大家介紹了Kotlin協(xié)程Context應(yīng)用使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12activity 獲取rootView 設(shè)置backGroundColor的方法
下面小編就為大家?guī)硪黄猘ctivity 獲取rootView 設(shè)置backGroundColor的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Android連接MySQL數(shù)據(jù)庫并進(jìn)行增刪改查操作示例講解
這篇文章主要介紹了Android 連接MySQL數(shù)據(jù)庫并進(jìn)行增刪改查操作示例講解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08深入Android中BroadcastReceiver的兩種注冊方式(靜態(tài)和動態(tài))詳解
這篇文章主要介紹了深入Android中BroadcastReceiver的兩種注冊方式(靜態(tài)和動態(tài))詳解,具有一定的參考價值,有需要的可以了解一下。2016-12-12Android項(xiàng)目中實(shí)體類entity的作用詳解
這篇文章主要介紹了Android項(xiàng)目中實(shí)體類entity的作用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04Android將Glide動態(tài)加載不同大小的圖片切圓角與圓形的方法
這篇文章主要給大家介紹了關(guān)于Android如何將Glide動態(tài)加載不同大小的圖片切圓角與圓形的方法,文中通過示例代碼介紹的非常吸納關(guān)系,對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11