1. 程式人生 > >Android Paging library詳解(二)

Android Paging library詳解(二)

重要API及原始碼分析

文章目錄

1.重要API介紹

Paging主要由三個部分組成:DataSource PageList PageListAdapter

1.1 DataSource

DataSource<Key, Value>從字面意思理解是一個數據源,其中key對應載入資料的條件資訊,Value對應載入資料的實體類。
DataSource是一個抽象類,但是我們不能直接繼承它實現它的子類。但是Paging庫裡提供了它的三個子類供我們繼承用於不同場景的實現:

PageKeyedDataSource<Key, Value>:適用於目標資料根據頁資訊請求資料的場景,即Key 欄位是頁相關的資訊。比如請求的資料的引數中包含類似next/previous頁數的資訊。
ItemKeyedDataSource<Key, Value>:適用於目標資料的載入依賴特定item的資訊, 即Key欄位包含的是Item中的資訊,比如需要根據第N項的資訊載入第N+1項的資料,傳參中需要傳入第N項的ID時,該場景多出現於論壇類應用評論資訊的請求。
PositionalDataSource<T>:適用於目標資料總數固定,通過特定的位置載入資料,這裡Key是Integer型別的位置資訊,T即Value。 比如從資料庫中的1200條開始加在20條資料。

1.2 PageList

PageList是一個List的子類,支援所有List的操作,除此之外它主要有五個成員:

mMainThreadExecutor: 一個主執行緒的Excutor, 用於將結果post到主執行緒。

mBackgroundThreadExecutor: 後臺執行緒的Excutor.

BoundaryCallback:載入Datasource中的資料載入到邊界時的回撥.

Config: 配置PagedList從Datasource載入資料的方式, 其中包含以下屬性:

pageSize:設定每頁載入的數量
prefetchDistance:預載入的數量,預設為pagesize
initialLoadSizeHint:初始化資料時載入的數量,預設為pageSize*3
enablePlaceholders:當item為null是否使用PlaceHolder展示
PagedStorage: 用於儲存載入到的資料,它是真正的蓄水池所在,它包含一個ArrayList 物件mPages,按頁儲存資料。

PagedList會從Datasource中載入資料,更準確的說是通過Datasource載入資料, 通過Config的配置,可以設定一次載入的數量以及預載入的數量。 除此之外,PagedList還可以向RecyclerView.Adapter傳送更新的訊號,驅動UI的重新整理。

1.3 PagedListAdapter

PagedListAdapte是RecyclerView.Adapter的實現,用於展示PagedList的資料。它本身實現的更多是Adapter的功能,但是它有一個小夥伴PagedListAdapterHelper, PagedListAdapterHelper會負責監聽PagedList的更新, Item數量的統計等功能。這樣當PagedList中新一頁的資料載入完成時, PagedAdapte就會發出載入完成的訊號,通知RecyclerView重新整理,這樣就省略了每次loading後手動調一次notifyDataChanged().

除此之外,當資料來源變動產生新的PagedList,PagedAdapter會在後臺執行緒中比較前後兩個PagedList的差異,然後呼叫notifyItem…()方法更新RecyclerView.這一過程依賴它的另一個小夥伴ListAdapterConfig, ListAdapterConfig負責主執行緒和後臺執行緒的排程以及DiffCallback的管理,DiffCallback的介面實現中定義比較的規則,比較的工作則是由PagedStorageDiffHelper來完成。

2.原始碼解析

下圖為Paging工作的原理

image

這是官方提供的非常棒的原理示意圖,簡單概括一下:

DataSource: 資料來源,資料的改變會驅動列表的更新,因此,資料來源是很重要的
PageList: 核心類,它從資料來源取出資料,同時,它負責控制 第一次預設載入多少資料,之後每一次載入多少資料,如何載入等等,並將資料的變更反映到UI上。
PagedListAdapter: 介面卡,RecyclerView的介面卡,通過分析資料是否發生了改變,負責處理UI展示的邏輯(增加/刪除/替換等)。

1.建立資料來源

我們思考一個問題,將資料作為列表展示在介面上,我們首先需要什麼。

資料來源,是的,在Paging中,它被抽象為 DataSource , 其獲取需要依靠 DataSource 的內部工廠類 DataSource.Factory ,通過create()方法就可以獲得DataSource 的例項:

public abstract static class Factory<Key, Value> {
     public abstract DataSource<Key, Value> create();
}

資料來源一般有兩種選擇,遠端伺服器請求 或者 讀取本地持久化資料——這些並不重要,本文我們以Room資料庫為例:

@Dao
interface StudentDao {

    @Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
    fun getAllStudent(): DataSource.Factory<Int, Student>
}

Paging可以獲得 Room的原生支援,因此作為示例非常合適,當然我們更多獲取 資料來源 是通過 API網路請求,其實現方式可以參考官方Sample,本文不贅述。

現在我們建立好了StudentDao,接下來就是展示UI了,在那之前,我們需要配置好PageList。

2.配置PageList

上文我說到了PageList的作用:

1.從資料來源取出資料
2.負責控制 第一次預設載入多少資料,之後每一次載入多少資料,如何載入 等等
3.將資料的變更反映到UI上。
我們仔細想想,這是有必要配置的,因此我們需要初始化PageList:

 val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
            .setPageSize(15)                         //配置分頁載入的數量
            .setEnablePlaceholders(false)     //配置是否啟動PlaceHolders
            .setInitialLoadSizeHint(30)              //初始化載入的數量
            .build()).build()

我們按照上面分的三個職責來講:

  • 從資料來源取出資料
    很顯然,這對應的是 dao.getAllStudent() ,通過資料庫取得最新資料,如果是網路請求,也應該對應API的請求方法,返回值應該是DataSource.Factory型別。

  • 進行相關配置
    PageList提供了 PagedList.Config 類供我們進行例項化配置,其提供了4個可選配置:

 public static final class Builder {
            //  省略其他Builder內部方法 
            private int mPageSize = -1;    //每次載入多少資料
            private int mPrefetchDistance = -1;   //距底部還有幾條資料時,載入下一頁資料
            private int mInitialLoadSizeHint = -1; //第一次載入多少資料
            private boolean mEnablePlaceholders = true; //是否啟用佔位符,若為true,則視為固定數量的item
}
  • 將變更反映到UI上
    這個指的是 LivePagedListBuilder,而不是 PagedList.Config.Builder,它可以設定 獲取資料來源的執行緒 和 邊界Callback,但是一般來講可以不用配置,大家瞭解一下即可。
    經過以上操作,我們的PageList設定好了,接下來就可以配置UI相關了。

3.配置Adapter

就像我們平時配置 RecyclerView 差不多,我們配置了ViewHolder和RecyclerView.Adapter,略微不同的是,我們需要繼承PagedListAdapter:

class StudentAdapter : PagedListAdapter<Student, StudentViewHolder>(diffCallback) {
    //省略 onBindViewHolder() && onCreateViewHolder()  
    companion object {
        private val diffCallback = object : DiffUtil.ItemCallback<Student>() {
            override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean =
                    oldItem.id == newItem.id

            override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean =
                    oldItem == newItem
        }
    }
}

當然我們還需要傳一個 DifffUtil.ItemCallback 的例項,這裡需要對資料來源返回的資料進行了比較處理, 它的意義是——我需要知道怎麼樣的比較,才意味著資料來源的變化,並根據變化再進行的UI重新整理操作。

ViewHolder的程式碼正常實現即可,不再贅述。

4.監聽資料來源的變更,並響應在UI上

這個就很簡單了,我們在Activity中宣告即可:

val adapter = StudentAdapter()
recyclerView.adapter = adapter

viewModel.allStudents.observe(this, Observer { adapter.submitList(it) })

這樣,每當資料來源發生改變,Adapter就會自動將 新的資料 動態反映在UI上。

為什麼不能把所有功能都封裝到一個 RecyclerView的Adapter裡面呢,它包含 下拉重新整理,上拉載入分頁 等等功能?

原因很簡單,因為這樣做會將業務層程式碼和UI層混在一起造成耦合 ,最直接就導致了 難 以通過程式碼進行單元測試。

UI層 和 業務層 程式碼的隔離是優秀的設計,這樣更便於 測試, 從Google官方文件也可以看出。指導文件包括UI元件和資料元件。

接下來,我會嘗試站在設計者的角度,嘗試去理解 Paging 如此設計的原因。

1.PagedListAdapter:基於RecyclerView的封裝
將分頁資料作為List展示在介面上,RecyclerView 是首選,那麼實現一個對應的 PagedListAdapter 當然是不錯的選擇。

Google對 PagedListAdapter 的職責定義的很簡單,僅僅是一個被代理的物件而已,所有相關的資料處理邏輯都委託給了 AsyncPagedListDiffer:

/* Advanced users that wish for more control over adapter behavior, or to provide a specific base
 * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging
 * events to adapter-friendly callbacks.
 *
 * @param <T> Type of the PagedLists this Adapter will receive.
 * @param <VH> A class that extends ViewHolder that will be used by the adapter.
 */
public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {
    private final AsyncPagedListDiffer<T> mDiffer;
    private final AsyncPagedListDiffer.PagedListListener<T> mListener =
            new AsyncPagedListDiffer.PagedListListener<T>() {
        @Override
        public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
            PagedListAdapter.this.onCurrentListChanged(currentList);
        }
    };

    /**
     * Creates a PagedListAdapter with default threading and
     * {@link android.support.v7.util.ListUpdateCallback}.
     *
     * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading
     * behavior.
     *
     * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
     *                     compare items in the list.
     */
    protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
        mDiffer.mListener = mListener;
    }

    @SuppressWarnings("unused, WeakerAccess")
    protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) {
        mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config);
        mDiffer.mListener = mListener;
    }

    /**
     * Set the new list to be displayed.
     * <p>
     * If a list is already being displayed, a diff will be computed on a background thread, which
     * will dispatch Adapter.notifyItem events on the main thread.
     *
     * @param pagedList The new list to be displayed.
     */
    public void submitList(PagedList<T> pagedList) {
        mDiffer.submitList(pagedList);
    }

    @Nullable
    protected T getItem(int position) {
        return mDiffer.getItem(position);
    }

    @Override
    public int getItemCount() {
        return mDiffer.getItemCount();
    }

    /**
     * Returns the PagedList currently being displayed by the Adapter.
     * <p>
     * This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
     * because a diff is computed asynchronously between the new list and the current list before
     * updating the currentList value. May be null if no PagedList is being presented.
     *
     * @return The list currently being displayed.
     */
    @Nullable
    public PagedList<T> getCurrentList() {
        return mDiffer.getCurrentList();
    }

    /**
     * Called when the current PagedList is updated.
     * <p>
     * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't
     * needed (such as when the first list is passed, or the list is cleared). In either case,
     * PagedListAdapter will simply call
     * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}.
     * <p>
     * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList
     * to a snapshot version of the PagedList during a diff. This means you cannot observe each
     * PagedList via this method.
     *
     * @param currentList new PagedList being displayed, may be null.
     */
    @SuppressWarnings("WeakerAccess")
    public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
    }
}

當資料來源發生改變時,實際上會通知 AsyncPagedListDiffer 的 submitList() 方法通知其內部儲存的 PagedList 更新並反映在UI上:

//實際上內部儲存了要展示在UI上的資料來源PagedList<T>
public class AsyncPagedListDiffer<T> {
    //省略大量程式碼
    private PagedList<T> mPagedList;
    private PagedList<T> mSnapshot;
}

分頁載入如何觸發的呢?

如果你認真思考了,肯定是在我們滑動list的時候載入到某一項的時候觸發的,當RecyclerView滑動的時候會觸發getItem()執行

public T getItem(int index) {
        if (mPagedList == null) {
            if (mSnapshot == null) {
                throw new IndexOutOfBoundsException(
                        "Item count is zero, getItem() call is invalid");
            } else {
                return mSnapshot.get(index);
            }
        }

        mPagedList.loadAround(index);
        return mPagedList.get(index);
    }

其中, 觸發下一頁載入的就是PagingList中的loadAround(int index) 方法。

/**
     * Load adjacent items to passed index.
     *
     * @param index Index at which to load.
     */
    public void loadAround(int index) {
        mLastLoad = index + getPositionOffset();
        loadAroundInternal(index);

        mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
        mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);

        /*
         * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
         * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
         * and accesses happen near the boundaries.
         *
         * Note: we post here, since RecyclerView may want to add items in response, and this
         * call occurs in PagedListAdapter bind.
         */
        tryDispatchBoundaryCallbacks(true);
    }

PagedList,也有很多種不同的 資料分頁策略:

image

這些不同的 PagedList 在處理分頁邏輯上,可能有不同的邏輯,那麼,作為設計者,應該做到的是**,把異同的邏輯抽象出來交給子類實現(即loadAroundInternal方法),而把公共的處理邏輯暴漏出來**,並向上轉型交給Adapter(實際上是 AsyncPagedListDiffer)去執行分頁載入的API,也就是loadAround方法。

好處顯而易見,對於Adapter來說,它只需要知道,在我需要請求分頁資料時,呼叫PagedList的loadAround方法即可,至於 是PagedList的哪個子類,內部執行什麼樣的分頁邏輯,Adapter並不關心

這些PagedList的不同策略的邏輯,是在PagedList.create()方法中進行的處理:

/**
     * Create a PagedList which loads data from the provided data source on a background thread,
     * posting updates to the main thread.
     *
     *
     * @param dataSource DataSource providing data to the PagedList
     * @param notifyExecutor Thread that will use and consume data from the PagedList.
     *                       Generally, this is the UI/main thread.
     * @param fetchExecutor Data loading will be done via this executor -
     *                      should be a background thread.
     * @param boundaryCallback Optional boundary callback to attach to the list.
     * @param config PagedList Config, which defines how the PagedList will load data.
     * @param <K> Key type that indicates to the DataSource what data to load.
     * @param <T> Type of items to be held and loaded by the PagedList.
     *
     * @return Newly created PagedList, which will page in data from the DataSource as needed.
     */
    @NonNull
    private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
            @NonNull Executor notifyExecutor,
            @NonNull Executor fetchExecutor,
            @Nullable BoundaryCallback<T> boundaryCallback,
            @NonNull Config config,
            @Nullable K key) {
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
            int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
            if (!dataSource.isContiguous()) {
                //noinspection unchecked
                dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
                        .wrapAsContiguousWithoutPlaceholders();
                if (key != null) {
                    lastLoad = (int) key;
                }
            }
            ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
            return new ContiguousPagedList<>(contigDataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    key,
                    lastLoad);
        } else {
            return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
                    notifyExecutor,
                    fetchExecutor,
                    boundaryCallback,
                    config,
                    (key != null) ? (Integer) key : 0);
        }
    }

PagedList是一個抽象類,實際上它的作用是 通過Builder例項化PagedList真正的物件:

image

通過Builder.build()呼叫create()方法,決定例項化哪個PagedList的子類:

public PagedList<Value> build() {
            // TODO: define defaults, once they can be used in module without android dependency
            if (mNotifyExecutor == null) {
                throw new IllegalArgumentException("MainThreadExecutor required");
            }
            if (mFetchExecutor == null) {
                throw new IllegalArgumentException("BackgroundThreadExecutor required");
            }

            //noinspection unchecked
            return PagedList.create(
                    mDataSource,
                    mNotifyExecutor,
                    mFetchExecutor,
                    mBoundaryCallback,
                    mConfig,
                    mInitialKey);
        }
    }

Builder模式是非常耳熟能詳的設計模式,它的好處是作為API的門面,便於開發者更簡單上手並進行對應的配置。

不同的PagedList對應不同的DataSource,比如:

ContiguousPagedList 對應ContiguousDataSource

ContiguousPagedList(
            @NonNull ContiguousDataSource<K, V> dataSource,
            @NonNull Executor mainThreadExecutor,
            @NonNull Executor backgroundThreadExecutor,
            @Nullable BoundaryCallback<V> boundaryCallback,
            @NonNull Config config,
            final @Nullable K key,
            int lastLoad)

TiledPagedList 對應 PositionalDataSource

@WorkerThread
    TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
            @NonNull Executor mainThreadExecutor,
            @NonNull Executor backgroundThreadExecutor,
            @Nullable BoundaryCallback<T> boundaryCallback,
            @NonNull Config config,
            int position)

回到create()方法中,我們看到dataSource此時也僅僅是抽象型別的宣告:

private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
            @NonNull Executor notifyExecutor,
            @NonNull Executor fetchExecutor,
            @Nullable BoundaryCallback<T> boundaryCallback,
            @NonNull Config config,
            @Nullable K key)

實際上,create方法的作用是,通過將不同的DataSource,作為依賴例項化對應的PagedList,除此之外,還有對DataSource的對應處理,或者Wrapper(再次包裝,詳情請參考原始碼的create方法,篇幅所限本文不再敘述)。

這個過程中,通過Builder,將 多種資料來源(DataSource)多種分頁策略(PagedList) 互相進行組合,並 向上轉型 交給 介面卡(Adapter) ,然後Adapter將對應的功能 委託 給了 代理類的AsyncPagedListDiffer 處理——這之間通過數種設計模式的組合,最終展現給開發者的是一個 簡單且清晰 的API介面去呼叫,其設計的精妙程度,遠非普通的開發者所能企及。

資料庫分頁實現原始碼

public abstract class LimitOffsetDataSource<T> extends PositionalDataSource<T> {
    private final RoomSQLiteQuery mSourceQuery;
    private final String mCountQuery;
    private final String mLimitOffsetQuery;
    private final RoomDatabase mDb;
    @SuppressWarnings("FieldCanBeLocal")
    private final InvalidationTracker.Observer mObserver;
    private final boolean mInTransaction;

    protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query,
            boolean inTransaction, String... tables) {
        this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables);
    }

    protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query,
            boolean inTransaction, String... tables) {
        mDb = db;
        mSourceQuery = query;
        mInTransaction = inTransaction;
        mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
        mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
        mObserver = new InvalidationTracker.Observer(tables) {
            @Override
            public void onInvalidated(@NonNull Set<String> tables) {
                invalidate();
            }
        };
        db.getInvalidationTracker().addWeakObserver(mObserver);
    }

    /**
     * Count number of rows query can return
     */
    @SuppressWarnings("WeakerAccess")
    public int countItems() {
        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mCountQuery,
                mSourceQuery.getArgCount());
        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
        Cursor cursor = mDb.query(sqLiteQuery);
        try {
            if (cursor.moveToFirst()) {
                return cursor.getInt(0);
            }
            return 0;
        } finally {
            cursor.close();
            sqLiteQuery.release();
        }
    }

    @Override
    public boolean isInvalid() {
        mDb.getInvalidationTracker().refreshVersionsSync();
        return super.isInvalid();
    }

    @SuppressWarnings("WeakerAccess")
    protected abstract List<T> convertRows(Cursor cursor);

    @Override
    public void loadInitial(@NonNull LoadInitialParams params,
            @NonNull LoadInitialCallback<T> callback) {
        int totalCount = countItems();
        
            
           

相關推薦

Android Paging library

重要API及原始碼分析 文章目錄 1.重要API介紹 1.1 DataSource 1.2 PageList 1.3 PagedListAdapter 2.原始碼解析 1.重要API介紹 Pagin

Android Paging library

官方文件翻譯 文章目錄 1.概覽 1.1 庫架構 1.2 支援不同的資料架構 1.2.1 網路獲取或者資料庫 1.2.2 網路和資料庫同時獲取 1.2.3 處理網路錯誤 1.2.4 更新

Android Animation動畫: 組合動畫特效

前言     上一篇部落格Android Animation動畫詳解(一): 補間動畫 我已經為大家介紹了Android補間動畫的四種形式,相信讀過該部落格的兄弟們一起都瞭解了。如果你還不瞭解,那點連結過去研讀一番,然後再過來跟著我一起學習如何把簡單的動畫效果組合在一起,做

Android進階筆記:AIDL內部實現

ucc == 筆記 null stack 直接 android 最好 public 接著上一篇分析的aidl的流程解析。知道了aidl主要就是利用Ibinder來實現跨進程通信的。既然是通過對Binder各種方法的封裝,那也可以不使用aidl自己通過Binder來實現跨進

android Audio

android Audio 詳解( 二 ) 2018年01月04日 15:57:45 韓半仙 閱讀數:302更多 個人分類: linux驅動 2  tinyalsa    tinyalsa是Google在Android 4.0之

Android應用程式啟動從原始碼瞭解App的啟動過程

本文承接《Android應用程式啟動詳解(一)》繼續來學習應用程式的啟動的那些事。上文提到startActivity()方法啟動一個app後經過一翻過程就到了app的入口方法ActivityThread.main()。其實我們在之前的文章中《Android的訊息機制(二)之L

Android動畫插值器

在上一篇Android動畫詳解(一)補間動畫中我們提到過一個叫插值器的東西,看名字一頭霧水完全不知道是什麼神奇玩意。其實用人話翻譯過來就是速度模型或者速度曲線的意思。為動畫設定插值器就是設定動畫的速度模型,就是設定它是怎麼動的,先加速再加速呀、一直減速呀、勻速的

Android客戶端實現註冊/登入

上文中介紹了安卓客戶端與伺服器互動,實現註冊功能 本文將繼續介紹App與伺服器的互動實現登入和自動登入的功能,上文說到請求伺服器進行註冊主要是通過POST請求攜帶引數實現,起作用的程式碼主要是 StringRequest request=new

Android事件分發機制流程

前言:上一篇我們已經從事件分發執行流程入手,一起來了解並分析了事件分發的經過,大家應該從分析中能對事件分發的有個總體的認識,並且我相信應該也能自己分析出事件會如何執行,其實就那麼點東西,弄明白了就不難了,但是今天我們還是要來看看activity,viewg

Android之內容提供器Content Provider

上一篇 Android之內容提供器Content Provider詳解(一)講解了Content Provider之訪問其他程式中的資料,本篇繼續講解創如何建自己的內容提供器 本博文是《第一行程式碼 Android》的讀書筆記/摘錄。 三、建立自己的內容提供

Android中的Intent和Intent-Filter

 Data、Type屬性與intent-filter配置 Data屬性接收一個Uri物件作為其值,Uri物件類似於“content://com.android.contacts/contacts/#”,關於Uri的相關知識大家可以自行搜尋。 Data屬性通常

Android選單——建立並響應選項選單

今天讓我們看一下如何通過程式碼建立和響應最常用的選項選單(options menu)。 建立options menu 之前提到,Android的activity已經為我們提前建立好了android.view.Menu物件,並提供了回撥方法(Menu menu)供我們初始化

java.util包——Connection接口

操作 相同 元素 叠代 cat roo soft true nbsp Connection接口介紹   Connection接口是java集合的root接口,沒有實現類,只有子接口和實現子接口的各種容器。主要用來表示java集合這一大的抽象概念。   Connection接

C++ 模板

創建 規則 error ++ 例如 public err iostream () 四、類模板的默認模板類型形參   1、可以為類模板的類型形參提供默認值,但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認值。   2、類模板的類型形

mybatis ------入門實例基於XML

ssi 開發模式 文件中 Coding import 拼接 upd baidu actor   通過上一小節,mybatis 和 jdbc 的區別:http://www.cnblogs.com/ysocean/p/7271600.html,我們對 mybatis有了一個大致

Spring------IOC控制反轉

tsp name 調試 的人 好的 turn 同時 eth 時機   我相信提到 Spring,很多人會脫口而出IOC(控制反轉)、DI(依賴註入)、AOP等等概念,這些概念也是面試官經常問到的知識點。那麽這篇博客我們就來詳細的講解 IOC控制反轉。   ps:本篇博客源

.Net AppDomain

onf urn attach msdn 允許 cut isolation cal pst AppDomain 類 表示應用程序域,它是一個應用程序在其中執行的獨立環境。 此類不能被繼承。 命名空間: System程序集: mscorlib(位於 mscorlib.

Ansible

latest load 遠程 即使 centos fine oct syn srv Ansible系列命令 Ansible系列命令有如下: ansible:這個命令是日常工作中使用率非常高的命令之一,主要用於臨時一次性操作; ansible-doc:是Ansible模塊文

Zookeeper:Zookeeper安裝

zookeeper安裝安裝環境:CentOS 7 內存1GBJDK版本:1.8.0_112為JDK配置如下環境變量:編輯/etc/profile.d/jdk.sh#!/bin/bash JAVA_HOME=/usr/local/jdk1.8.0_112 export PATH=$JAVA_HOME/bi

dns

dns 子域授權 主從架構 bind 轉發區域 bind view dns的另外一種實現方式:dnsmasq較為簡單請自行理解一.主從架構 二.子域授權 三.轉發區域 四.bind中的安全相關配置 五.bind view視圖一.主從架構從s擁有和主s一樣的解析庫 註意:從服務器是