RecyclerView性能優(yōu)化及高級使用

1裁奇、RecyclerView.setHasFixedSize(true);

當知道Adapter內Item的改變不會影響RecyclerView寬高的時候泥兰,設置這個屬性為true可以提高性能,尤其是在插入胰柑、刪除時性能提升更明顯缭黔。RecyclerView在條目數量改變辨图,會重新測量、布局各個item猿妈,如果設置了setHasFixedSize(true)吹菱,由于item寬高固定的巍虫,adapter的內容改變時,RecyclerView不會整個布局都重繪鳍刷。具體可用以下偽代碼表示:

void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
triggerUpdateProcessor方法被調用時機
  1. onItemRangeChanged()
  2. onItemRangeInserted()
  3. onItemRangeRemoved()
  4. onItemRangeMoved()

當調用Adapter的增刪改插方法占遥,最后就會根據mHasFixedSize這個值來判斷需要不需要requestLayout();再來看一下notifyDataSetChanged()執(zhí)行的代碼输瓜,最后是調用了onChanged瓦胎,調用了requestLayout(),會去重新測量寬高尤揣。

    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;
        setDataSetChangedAfterLayout();
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }

RecyclerView 數據預取

android sdk>=21時搔啊,支持渲染(Render)線程,RecyclerView數據顯示分兩個階段:

1)在UI線程北戏,處理輸入事件负芋、動畫、布局嗜愈、記錄繪圖操作旧蛾,每一個條目在進入屏幕顯示前都會被創(chuàng)建和綁定view;

2)渲染(Render)線程把指令送往GPU蠕嫁。

數據預取的思想就是:將閑置的UI線程利用起來蚜点,提前加載計算下一幀的Frame Buffer

在新的條目進入視野前,會花大量時間來創(chuàng)建和綁定view拌阴,而在前一幀卻可能很快完成了這些操作绍绘,導致前一幀的UI線程有一大片空閑時間。


A.png

將創(chuàng)建和綁定移到前一幀迟赃,使UI線程與渲染線程同時工作陪拘,在一個條目即將進入視野時預取數據。具體如下圖纤壁,在前一幀的紅色虛線圈中左刽,UI線程有一定的空閑時間,可以把第二幀Create B的工作移到前一幀的空閑時間來完成酌媒。


B.png

具體實現(xiàn)方式是:在 RecyclerView 開始一個滾動時new Runnable對象欠痴,根據 layout manager 和滾動的方向預取即將進入視野的條目,可以同時取出一個或多個條目

如果使用 RecyclerView 提供的LayoutManager秒咨,自動使用了這種優(yōu)化操作喇辽。如果使用嵌套 RecyclerView 或者自己實現(xiàn)Layout Manager,則需要在代碼中設置雨席。

1)對于嵌套 RecyclerView菩咨,要獲取最佳的性能,在內部的 LayoutManager 中調用 LinearLayoutManager.setInitialItemPrefetchCount()方法。

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();

    ...

    @Override
    public void onCreateViewHolder(ViewGroup parent, int viewType) {
        // inflate inner item, find innerRecyclerView by ID…
        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(),
                LinearLayoutManager.HORIZONTAL);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setInitialPrefetchItemCount(4);
        return new OuterAdapter.ViewHolder(innerRv);

    }
    ...

例如每一行有3.5個item需要顯示抽米,調用innerLLM.setInitialItemPrefetchCount(4);這將告訴RecyclerView特占,當一個水平行即將出現(xiàn)在屏幕上時,如果UI線程上有空閑時間云茸,它應該嘗試預取內部的項目

2)如果自己實現(xiàn)了LayoutManager是目,需要重寫 LayoutManager.collectAdjacentPrefetchPositions()方法。

復用RecycledViewPool

如果RecycledView的adapter是一樣的話可以考慮共享一個對象池,可以避免創(chuàng)建ViewHolder的開銷标捺,避免GC懊纳。RecycledViewPool對象可通過RecyclerView對象獲取,也可以自己實現(xiàn)宜岛。

RecycledViewPool mPool = mRecyclerView1.getRecycledViewPool();

mRecyclerView2.setRecycledViewPool(mPool);

mRecyclerView3.setRecycledViewPool(mPool);

注意:

(1)RecycledViewPool是依據ItemViewType來索引ViewHolder的长踊,必須確保共享的RecyclerView的Adapter是同一個,或view type 是不會沖突的萍倡。

(2)RecycledViewPool可以自主控制需要緩存的ViewHolder數量身弊,每種type的默認容量是5,可通過setMaxRecycledViews來設置大小列敲。mPool.setMaxRecycledViews(itemViewType, number); 但這會增大應用內存開銷阱佛,所以也需要根據應用具體情況來使用。

(3)利用此特性一般建議設置layout.setRecycleChildrenOnDetach(true);此屬性是用來告訴LayoutManager從RecyclerView分離時戴而,是否要回收所有的item凑术,如果項目中復用RecycledViewPool時,開啟該功能會更好的實現(xiàn)復用所意。其他RecyclerView可以復用這些回收的item淮逊。

RecycleView與NestedScrollView的嵌套

  • RecycleView滑動會感覺到卡頓,可以通過mRecyclerView.setNestedScrollingEnabled(false)
    解決這個問題
  • 一次性加載所有item扶踊,而不是加載當前可見的Item泄鹏,如果你的item布局夠復雜或者條目很多,卡頓會很嚴重
  • 相關屬性會失效秧耗,例如 linearManager.findLastVisibleItemPosition()

避免創(chuàng)建過多對象

onCreateViewHolder 和 onBindViewHolder 對時間都比較敏感备籽,盡量避免繁瑣的操作和循環(huán)創(chuàng)建對象。例如創(chuàng)建 OnClickListener分井,可以全局創(chuàng)建一個车猬。

優(yōu)化前:

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    holder.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
         //do something
       }
    });
}

優(yōu)化后:

private class XXXHolder extends RecyclerView.ViewHolder {
        private EditText mEt;
        EditHolder(View itemView) {
            super(itemView);
            mEt = (EditText) itemView;
            mEt.setOnClickListener(mOnClickListener);
        }
    }
    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //do something
        }
    }

用SortedList實現(xiàn)添加刪除ItemView自動更新

顧名思義就是排序列表,它適用于列表有序且不重復的場景尺锚。并且SortedList會幫助你比較數據的差異珠闰,定向刷新數據。而不是簡單粗暴的notifyDataSetChanged()缩麸。

創(chuàng)建SortedListAdapterCallback 的實現(xiàn)類 SortedListCallback铸磅,SortedListCallback 定義了如何排序和如何判斷重復項赡矢。
public class SortedListCallback extends SortedListAdapterCallback<City> {
    
    public SortedListCallback(RecyclerView.Adapter adapter) {
        super(adapter);
    }

    /**
     * 排序條件
     */
    @Override
    public int compare(City o1, City o2) {
        return o1.getFirstLetter().compareTo(o2.getFirstLetter());
    }

    /**
     * 用來判斷兩個對象是否是相同的Item杭朱。
     */
    @Override
    public boolean areItemsTheSame(City item1, City item2) {
        return item1.getId() == item2.getId();
    }
    
    /**
     * 用來判斷兩個對象是否是內容的Item阅仔。
     */
    @Override
    public boolean areContentsTheSame(City oldItem, City newItem) {
        if (oldItem.getId() != newItem.getId()) {
            return false;
        }
        return oldItem.getCityName().equals(newItem.getCityName());
    }
}
Adapter部分
public class SortedAdapter extends RecyclerView.Adapter<SortedAdapter.ViewHolder> {
   
    // 數據源使用SortedList
    private SortedList<City> mSortedList;
    private LayoutInflater mInflater;
    
    public SortedAdapter(Context mContext) {
        mInflater = LayoutInflater.from(mContext);
    }

    public void setSortedList(SortedList<City> mSortedList) {
        this.mSortedList = mSortedList;
    }
    
    /**
     * 批量更新操作,例如:
     * <pre>
     *     mSortedList.beginBatchedUpdates();
     *     try {
     *         mSortedList.add(item1)
     *         mSortedList.add(item2)
     *         mSortedList.remove(item3)
     *         ...
     *     } finally {
     *         mSortedList.endBatchedUpdates();
     *     }
     * </pre>
    * */
    public void setData(List<City> mData){
        mSortedList.beginBatchedUpdates();
        mSortedList.addAll(mData);
        mSortedList.endBatchedUpdates();
    }

    public void removeData(int index){
        mSortedList.removeItemAt(index);
    }

    public void clear(){
        mSortedList.clear();
    }
    
    @Override
    @NonNull
    public SortedAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull SortedAdapter.ViewHolder holder, final int position) {
       ...
    }
    
    @Override
    public int getItemCount() {
        return mSortedList.size();
    }

    ...
}
使用部分:
public class SortedListActivity extends AppCompatActivity {

    private SortedAdapter mSortedAdapter;
    private int count = 10;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sorted_list);
        RecyclerView mRecyclerView = findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mSortedAdapter = new SortedAdapter(this);
        // SortedList初始化
        SortedListCallback mSortedListCallback = new SortedListCallback(mSortedAdapter);
        SortedList mSortedList = new SortedList<>(City.class, mSortedListCallback);
        
        mSortedAdapter.setSortedList(mSortedList);
        mRecyclerView.setAdapter(mSortedAdapter);
        updateData();
    }
}

AsyncListUtil

AsyncListUtil 是一個用于異步內容加載的類弧械,通常和 RecyclerView 搭配使用的八酒。其能夠在后臺線程中加載 Cursor 數據,同時保持 UI 和緩存的同步刃唐。

為了創(chuàng)建 AsyncListUtil羞迷,我們需要傳入 DataCallback 和 ViewCallback。
(1)實現(xiàn) DataCallback:

private class DataCallback(val itemSource: ItemSource) : AsyncListUtil.DataCallback<Item>() {
    override fun fillData(data: Array<Item>?, startPosition: Int, itemCount: Int) {
        if (data != null) {
            for (i in 0 until itemCount) {
                data[i] = itemSource.getItem(startPosition + i)
            }
        }
    }

    override fun refreshData(): Int = itemSource.getCount()

    fun close() {
        itemSource.close()
    }
}

DataCallback 是用來為 AsyncListUtil 提供數據訪問画饥,其中所有方法都會在后臺線程中調用衔瓮。
其中有兩個方法必需要實現(xiàn):

  1. fillData(data, startPosition, itemCount) - 當 AsyncListUtil 需要更多數據時,將會在后臺線程調用該方法抖甘。

2.refreshData() - 返回刷新后的數據個數热鞍。

(2)實現(xiàn) ViewCallback:

private class ViewCallback(val recyclerView: RecyclerView) : AsyncListUtil.ViewCallback() {
    override fun onDataRefresh() {
        recyclerView.adapter.notifyDataSetChanged()
    }

    override fun getItemRangeInto(outRange: IntArray?) {
        if (outRange == null) {
            return
        }
        (recyclerView.layoutManager as LinearLayoutManager).let { llm ->
            outRange[0] = llm.findFirstVisibleItemPosition()
            outRange[1] = llm.findLastVisibleItemPosition()
        }

        if (outRange[0] == -1 && outRange[1] == -1) {
            outRange[0] = 0
            outRange[1] = 0
        }
    }

    override fun onItemLoaded(position: Int) {
        recyclerView.adapter.notifyItemChanged(position)
    }
}

AsyncListUtil 通過 ViewCallback 主要是做兩件事:

  1. 通知視圖數據已經更新(onDataRefresh);
  2. 了解當前視圖所展示數據的位置衔彻,從而確定什么時候獲取更多數據或釋放掉目前不在窗口內的舊數據(getItemRangeInto)薇宠。

DiffUtil/AsyncListDiffer

雖然SortedList、AsyncListUtil很方便了艰额,但是大多數的列表都無需我們排序和加載本地數據澄港,大多是獲取網絡數據展示。這個時候就可以使用這兩個工具類柄沮,它用來比較新舊兩個數據集回梧,尋找最小變化量,定向刷新列表祖搓。

差異

DiffUtil.calculateDiff(mDiffCallback)時是一個耗時操作狱意,需要我們放到子線程去處理,最后在主線程刷新棕硫。為了方便這一操作髓涯,AsyncListDiffer對DiffUtil作了封裝

具體使用

首先實現(xiàn)DiffUtil.ItemCallback,制定規(guī)則哈扮,如何區(qū)分數據纬纪。

public class MyDiffUtilItemCallback extends DiffUtil.ItemCallback<TestBean> {

    /**
     * 是否是同一個對象
     */  
    @Override
    public boolean areItemsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getId() == newItem.getId();
    }
    /**
     * 是否是相同內容
     */ 
    @Override
    public boolean areContentsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getName().equals(newItem.getName());
    }

    /**
     * areItemsTheSame()返回true而areContentsTheSame()返回false時調用,也就是說兩個對象代表的數據是一條,但是內容更新了滑肉。此方法為定向刷新使用包各,可選。
     */
    @Nullable
    @Override
    public Object getChangePayload(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        Bundle payload = new Bundle();

        if (!oldItem.getName().equals(newItem.getName())) {
            payload.putString("KEY_NAME", newItem.getName());
        }

        if (payload.size() == 0){
            //如果沒有變化 就傳空
            return null;
        }
        return payload;
    }
}

Adapter部分有兩種實現(xiàn)方法靶庙,一種是實現(xiàn)RecyclerView.Adapter

public class AsyncListDifferAdapter extends RecyclerView.Adapter<AsyncListDifferAdapter.ViewHolder> {
   
    private LayoutInflater mInflater;
    // 數據的操作由AsyncListDiffer實現(xiàn)
    private AsyncListDiffer<TestBean> mDiffer;
    
    public AsyncListDifferAdapter(Context mContext) {
        // 初始化AsyncListDiffe
        mDiffer = new AsyncListDiffer<>(this, new MyDiffUtilItemCallback());
        mInflater = LayoutInflater.from(mContext);
    }

    public void setData(TestBean mData){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.add(mData);
        mDiffer.submitList(mList);
    }

    public void setData(List<TestBean> mData){
        // 由于DiffUtil是對比新舊數據问畅,所以需要創(chuàng)建新的集合來存放新數據。
        // 實際情況下,每次都是重新獲取的新數據护姆,所以無需這步矾端。
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        mDiffer.submitList(mList);
    }

    public void removeData(int index){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.remove(index);
        mDiffer.submitList(mList);
    }
    
    public void clear(){
        mDiffer.submitList(null);
    }
    
    @Override
    @NonNull
    public AsyncListDifferAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull AsyncListDifferAdapter.ViewHolder holder, final int position) {
        TestBean bean = mDiffer.getCurrentList().get(position);
        holder.mTvName.setText(bean.getName());
    }
    
    @Override
    public int getItemCount() {
        return mDiffer.getCurrentList().size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

       ......
    }
}

另一種Adapter寫法可以實現(xiàn)ListAdapter,它的內部幫我們實現(xiàn)了getItemCount()卵皂、getItem()和AsyncListDiffer的初始化秩铆。

public class MyListAdapter extends ListAdapter<TestBean, MyListAdapter.ViewHolder> {
   
    private LayoutInflater mInflater;
    // 自己維護的集合
    private List<TestBean> mData = new ArrayList<>();
    
    public MyListAdapter(Context mContext) {
        super(new MyDiffUtilItemCallback());
        mInflater = LayoutInflater.from(mContext);
    }

    public void setData(TestBean testBean){
        mData.add(testBean);
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        // 提交新的數據集
        submitList(mList);
    }

    public void setData(List<TestBean> list){
        mData.clear();
        mData.addAll(list);
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        submitList(mList);
    }

    public void removeData(int index){
        mData.remove(index);
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        submitList(mList);
    }

    public void clear(){
        mData.clear();
        submitList(null);
    }
    
    @Override
    @NonNull
    public MyListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull MyListAdapter.ViewHolder holder, final int position) {
        TestBean bean = getItem(position);
        holder.mTvName.setText(bean.getName());
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ......
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市灯变,隨后出現(xiàn)的幾起案子殴玛,更是在濱河造成了極大的恐慌,老刑警劉巖添祸,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滚粟,死亡現(xiàn)場離奇詭異,居然都是意外死亡刃泌,警方通過查閱死者的電腦和手機凡壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔬咬,“玉大人鲤遥,你說我怎么就攤上這事×炙遥” “怎么了盖奈?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狐援。 經常有香客問我钢坦,道長,這世上最難降的妖魔是什么啥酱? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任爹凹,我火速辦了婚禮,結果婚禮上镶殷,老公的妹妹穿的比我還像新娘禾酱。我一直安慰自己,他們只是感情好绘趋,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布颤陶。 她就那樣靜靜地躺著,像睡著了一般陷遮。 火紅的嫁衣襯著肌膚如雪滓走。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天帽馋,我揣著相機與錄音搅方,去河邊找鬼比吭。 笑死,一個胖子當著我的面吹牛姨涡,可吹牛的內容都是我干的衩藤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼绣溜,長吁一口氣:“原來是場噩夢啊……” “哼慷彤!你這毒婦竟也來了娄蔼?” 一聲冷哼從身側響起怖喻,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岁诉,沒想到半個月后锚沸,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡涕癣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年哗蜈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坠韩。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡距潘,死狀恐怖,靈堂內的尸體忽然破棺而出只搁,到底是詐尸還是另有隱情音比,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布氢惋,位于F島的核電站洞翩,受9級特大地震影響,放射性物質發(fā)生泄漏焰望。R本人自食惡果不足惜骚亿,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望熊赖。 院中可真熱鬧来屠,春花似錦、人聲如沸震鹉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽足陨。三九已至嫂粟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墨缘,已是汗流浹背星虹。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工零抬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宽涌。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓平夜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卸亮。 傳聞我的和親對象是個殘疾皇子忽妒,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內容

  • 主要是在使用 RecyclerView 過程中遇到的細碎問題和解決方案。 簡單使用 LinearLayoutMan...
    三流之路閱讀 3,849評論 0 5
  • 這篇文章分三個部分兼贸,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法段直;工作原理與ListView比...
    LucasAdam閱讀 4,377評論 0 27
  • 概述 隨著2014年Google IO的召開,Android L Preview版隨之發(fā)布溶诞,對于開發(fā)著來說鸯檬,帶來了...
    小鄧子閱讀 35,727評論 33 225
  • Android四大組件: activity: activity的生命周期:activity是context的子類,...
    梧桐樹biu閱讀 633評論 0 2
  • 姓名:徐偉 常州新日催化劑有限公司 組別:第455期樂觀二組紀律委員 【日精進打卡第195天】 【知~學習】 書名...
    奔波兒灞_87f6閱讀 64評論 0 0