欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳細(xì)分析Fresco源碼之圖片加載流程

 更新時(shí)間:2021年01月28日 15:02:09   作者:huansky  
Fresco是一個(gè)強(qiáng)大的圖片加載組件,使用它之后,你不需要再去關(guān)心圖片的加載和顯示這些繁瑣的事情!它支持Android2.3及以后的版本

一、概述

Fresco 是一個(gè)強(qiáng)大的圖片加載組件。使用它之后,你不需要再去關(guān)心圖片的加載和顯示這些繁瑣的事情! 支持 Android 2.3 及以后的版本。如果需要了解 Fresco 的使用可以訪問 Fresco 使用文檔

Fresco是一個(gè)功能完善的圖片加載框架,在Android開發(fā)中有著廣泛的應(yīng)用,那么它作為一個(gè)圖片加載框架,有哪些特色讓它備受推崇呢?

  • 完善的內(nèi)存管理功能,減少圖片對(duì)內(nèi)存的占用,即便在低端機(jī)器上也有著不錯(cuò)的表現(xiàn)。
  • 自定義圖片加載的過程,可以先顯示低清晰度圖片或者縮略圖,加載完成后再顯示高清圖,可以在加載的時(shí)候縮放和旋轉(zhuǎn)圖片。
  • 自定義圖片繪制的過程,可以自定義谷中焦點(diǎn)、圓角圖、占位圖、overlay、進(jìn)圖條。
  • 漸進(jìn)式顯示圖片。
  • 支持Gif。
  • 支持Webp。

Fresco的組成結(jié)構(gòu)還是比較清晰的,大致如下圖所示:

其實(shí)這兩張圖來自不同的文章,但是我覺得兩者的分層實(shí)際上基本是一樣的。只是一個(gè)比較概括,一個(gè)比價(jià)具體,將兩者擺在一起,更有助于大家去理解其實(shí)現(xiàn)細(xì)節(jié)。當(dāng)然除了 UI 和加載顯示部分外,還有 Gif,動(dòng)態(tài)圖片等內(nèi)容,以及對(duì)應(yīng)圖片解碼編碼邏輯等。這部分不打算去講解,因?yàn)檫@部分雖然也是源碼很重要的一部分,但是這部分需要相關(guān)專業(yè)知識(shí)才好說明白,此外且涉及到 C++ 代碼。

下面結(jié)合代碼分別解釋一下上面各模塊的作用以及大概的工作原理。

二、DraweeView

它繼承自ImageView,Fresco加載圖片各個(gè)階段過程中圖片顯示的載體,比如在加載圖片過程中它顯示的是占位圖、在加載成功時(shí)切換為目標(biāo)圖片。不過后續(xù)官方可能不再讓這個(gè)類繼承ImageView,所以該類并不支持ImageView 的 setImageXxx, setScaleType 以及其他類似的方法。目前DraweeViewImageView唯一的交集是:它利用ImageView來顯示 Drawable:

//DraweeView.setController()
public void setController(@Nullable DraweeController draweeController) {
    mDraweeHolder.setController(draweeController);
    super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());  //super 就是 ImageView
}

//DraweeHolder.getTopLevelDrawable()
public @Nullable Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable(); // mHierarchy 是 DraweeHierachy,
}

DraweeView.setController()會(huì)在Fresco加載圖片時(shí)會(huì)調(diào)用。其實(shí)在這里可以看出Fresco的圖片顯示原理是 :利用ImageView顯示DraweeHierachyTopLevelDrawable。上面這段代碼引出了UI 層中另外兩個(gè)關(guān)鍵類:DraweeHolderDraweeHierachy

三、DraweeHierachy

可以說它是Fresco圖片顯示的實(shí)現(xiàn)者。它的輸出是Drawable,這個(gè)Drawable會(huì)被DraweeView拿來顯示(上面已經(jīng)說了)。它內(nèi)部有多個(gè)Drawable,當(dāng)前顯示在DraweeViewDrawable叫做TopLevelDrawable。在不同的圖片加載階段,TopLevelDrawable是不同的(比如加載過程中是 placeholder,加載完成是目標(biāo)圖片)。具體的Drawable切換邏輯是由它來具體實(shí)現(xiàn)的。

它是由DraweeController直接持有的,因此對(duì)于不同圖片顯示的切換操作具體是由DraweeController來直接操作的。

四、DraweeHolder

可以把它理解為DraweeView、DraweeHierachyDraweeController這 3 個(gè)類之間的粘合劑, DraweeView 并不直接和DraweeController 和DraweeHierachy 直接接觸,所有的操作都是通過它傳過去。這樣,后續(xù)將 DraweeView 的父類改為 View,也不會(huì)影響到其他類。DraweeView 作為 View 可以感知點(diǎn)擊和生命周期,通過DraweeHolder 來控制其他兩個(gè)類的操作。

想想如果是你,你會(huì)抽出 DraweeHolder 這樣一個(gè)類嗎?實(shí)際上,這里對(duì)我們平時(shí)開發(fā)也是有所借鑒,嚴(yán)格控制每一個(gè)類之間的關(guān)系,可以引入一些一些中間類,讓類與類之間的關(guān)系耦合度降低,方便日后迭代。

具體引用關(guān)系如下圖:

它的主要功能是: 接收DraweeView的圖片加載請(qǐng)求,控制ProducerSequence發(fā)起圖片加載和處理流程,監(jiān)聽ProducerSequence加載過程中的事件(失敗、完成等),并更新最新的DrawableDraweeHierachy。

五、DraweeController 的構(gòu)造邏輯

FrescoDraweeController是通過PipelineDraweeControllerBuilderSupplier 獲取的。Fresco在初始化時(shí)會(huì)調(diào)用下面的代碼:

// Fresco.java
private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) {
    sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
    SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}

sDraweeControllerBuilderSupplier 是靜態(tài)變量,也就是說其在只會(huì)初始一次。所有的DraweeController都是通過調(diào)用sDraweecontrollerbuildersupplier.get() 得到的。

private void init(Context context, @Nullable AttributeSet attrs) {
    try {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.beginSection("SimpleDraweeView#init");
      }
      if (isInEditMode()) {
        getTopLevelDrawable().setVisible(true, false);
        getTopLevelDrawable().invalidateSelf();
      } else {
        Preconditions.checkNotNull(
            sDraweecontrollerbuildersupplier, "SimpleDraweeView was not initialized!");
        mControllerBuilder = sDraweecontrollerbuildersupplier.get(); // 調(diào)用一次就會(huì)創(chuàng)建一個(gè)新的實(shí)例
    }
    // ...... 省略其他代碼  
}

Fresco每次圖片加載都會(huì)對(duì)應(yīng)到一個(gè)DraweeController,一個(gè)DraweeView的多次圖片加載可以復(fù)用同一個(gè)DraweeController:

SimpleDraweeView.java

public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller =
        mControllerBuilder
            .setCallerContext(callerContext)
            .setUri(uri) //設(shè)置新的圖片加載路徑
            .setOldController(getController())  //復(fù)用 controller
            .build(); 
    setController(controller);
}

所以一般情況下 : 一個(gè)DraweeView對(duì)應(yīng)一個(gè)DraweeController

六、通過 DataSource 發(fā)起圖片加載

在前面已經(jīng)說了DraweeController是直接持有DraweeHierachy,所以它觀察到ProducerSequence的數(shù)據(jù)變化是可以很容易更新到DraweeHierachy(具體代碼先不展示了)。那它是如何控制ProducerSequence來加載圖片的呢?其實(shí)DraweeController并不會(huì)直接和ProducerSequence發(fā)生關(guān)聯(lián)。對(duì)于圖片的加載,它直接接觸的是DataSource,由DataSource進(jìn)而來控制ProducerSequence發(fā)起圖片加載和處理流程。下面就跟隨源碼來看一下DraweeController是如果通過DataSource來控制ProducerSequence發(fā)起圖片加載和處理流程的。

// AbstractDraweeController.java
protected void submitRequest() {
    mDataSource = getDataSource(); 
    final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以簡(jiǎn)單的把它理解為一個(gè)監(jiān)聽者
        @Override
        public void onNewResultImpl(DataSource<T> dataSource) { //圖片加載成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調(diào)方法運(yùn)行的線程,這里是主線程
}

DataSource是什么呢? getDataSource()最終會(huì)調(diào)用到:

// PipelineDraweeControllerBuilder 
 protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
      DraweeController controller,
      String controllerId,
      ImageRequest imageRequest,
      Object callerContext,
      AbstractDraweeControllerBuilder.CacheLevel cacheLevel) {
    return mImagePipeline.fetchDecodedImage(
        imageRequest,
        callerContext,
        convertCacheLevelToRequestLevel(cacheLevel),
        getRequestListener(controller),
        controllerId);
  }
// CloseableProducerToDataSourceAdapter<T>
public static <T> DataSource<CloseableReference<T>> create(
      Producer<CloseableReference<T>> producer,
      SettableProducerContext settableProducerContext,
      RequestListener2 listener) {
   
    CloseableProducerToDataSourceAdapter<T> result =
        new CloseableProducerToDataSourceAdapter<T>(producer, settableProducerContext, listener);return result;
  }

所以DraweeController最終拿到的DataSourceCloseableProducerToDataSourceAdapter。這個(gè)類在構(gòu)造的時(shí)候就會(huì)啟動(dòng)圖片加載流程(它的構(gòu)造方法會(huì)調(diào)用producer.produceResults(...),這個(gè)方法就是圖片加載的起點(diǎn),我們后面再看)。

這里我們總結(jié)一下FrescoDataSource的概念以及作用:在FrescoDraweeController每發(fā)起一次圖片加載就會(huì)創(chuàng)建一個(gè)DataSource,這個(gè)DataSource用來提供這次請(qǐng)求的數(shù)據(jù)(圖片)。DataSource只是一個(gè)接口,至于具體的加載流程Fresco是通過ProducerSequence來實(shí)現(xiàn)的。

七、Fresco圖片加載前的邏輯

了解了上面的知識(shí)后,我們過一遍圖片加載的源碼(從 UI 到 DraweeController),來理一下目前所了解的各個(gè)模塊之間的聯(lián)系。我們?cè)谑褂?code>Fresco加載圖片時(shí)一般是使用這個(gè)API:SimpleDraweeView.setImageURI(imageLink),這個(gè)方法最終會(huì)調(diào)用到:

// SimpleDraweeView.java
public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller = mControllerBuilder
            .setCallerContext(callerContext)
            .setUri(uri)
            .setOldController(getController())
            .build();    //這里會(huì)復(fù)用 controller
    setController(controller);
}

public void setController(@Nullable DraweeController draweeController) {
    mDraweeHolder.setController(draweeController);
    super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());  
}

即每次加載都會(huì)使用DraweeControllerBuilderbuild一個(gè)DraweeController。其實(shí)這個(gè)DraweeController默認(rèn)是復(fù)用的,這里的復(fù)用針對(duì)的是同一個(gè)SimpleDraweeView

。然后會(huì)把DraweeController設(shè)置給DraweeHolder,并在加載開始默認(rèn)是從DraweeHolder獲取TopLevelDrawable并展示到DraweeView。繼續(xù)看一下DraweeHolder的邏輯:

// DraweeHolder.java
public @Nullable Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
}


/** Sets a new controller. */
public void setController(@Nullable DraweeController draweeController) {
boolean wasAttached = mIsControllerAttached;  
    if (wasAttached) {
        detachController();  
    }  
    // Clear the old controller 
    if (isControllerValid()) {    
        mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);   
        mController.setHierarchy(null);  
    }  
    mController = draweeController; 
    // 注意這里是只有確定已經(jīng) attached 才會(huì)調(diào)用,也就是才回去加載圖片  
    if (wasAttached) {
        attachController(); 
    }
}

DraweeHolder.setController()中把DraweeHierachy設(shè)置給DraweeController,并重新attachController(),attachController()主要調(diào)用了DraweeController.onAttach():

// AbstractDraweeController.java
public void onAttach() {
    ...
    mIsAttached = true;
    if (!mIsRequestSubmitted) {
      submitRequest();
    }
}

protected void submitRequest() {
    mDataSource = getDataSource(); 
    final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以簡(jiǎn)單的把它理解為一個(gè)監(jiān)聽者
        @Override
        public void onNewResultImpl(DataSource<T> dataSource) { //圖片加載成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回調(diào)方法運(yùn)行的線程,這里是主線程
}

即通過submitRequest()提交了一個(gè)請(qǐng)求,這個(gè)方法我們前面已經(jīng)看過了,它所做的主要事情就是,構(gòu)造了一個(gè)DataSource。這個(gè)DataSource我們經(jīng)過追蹤,它的實(shí)例實(shí)際上是CloseableProducerToDataSourceAdapter。CloseableProducerToDataSourceAdapter在構(gòu)造時(shí)就會(huì)調(diào)用producer.produceResults(...),進(jìn)而發(fā)起整個(gè)圖片加載流程。

用下面這張圖總結(jié)從SimpleDraweeView->DraweeController的圖片加載邏輯:

到這里我們梳理完了Fresco在真正發(fā)起圖片加載前所走的邏輯,那么Fresco的圖片加載流程是如何控制的呢?到底經(jīng)歷了哪些步驟呢?

八、Producer

Fresco中有關(guān)圖片的內(nèi)存緩存、解碼、編碼、磁盤緩存、網(wǎng)絡(luò)請(qǐng)求都是在這一層實(shí)現(xiàn)的,而所有的實(shí)現(xiàn)的基本單元是Producer,所以我們先來理解一下Producer:

看一下它的定義:

/**
 * <p> Execution of image request consists of multiple different tasks such as network fetch,
 * disk caching, memory caching, decoding, applying transformations etc. Producer<T> represents
 * single task whose result is an instance of T. Breaking entire request into sequence of
 * Producers allows us to construct different requests while reusing the same blocks.
 */
public interface Producer<T> {

  /**
   * Start producing results for given context. Provided consumer is notified whenever progress is made (new value is ready or error occurs).
   */
  void produceResults(Consumer<T> consumer, ProducerContext context);
}

結(jié)合注釋我們可以這樣定義Producer的作用:一個(gè)Producer用來處理整個(gè)Fresco圖片處理流程中的一步,比如從網(wǎng)絡(luò)獲取圖片、內(nèi)存獲取圖片、解碼圖片等等。而對(duì)于Consumer可以把它理解為監(jiān)聽者,看一下它的定義:

public interface Consumer<T> {
/**
   * Called by a producer whenever new data is produced. This method should not throw an exception.
   *
   * <p>In case when result is closeable resource producer will close it after onNewResult returns.
   * Consumer needs to make copy of it if the resource must be accessed after that. Fortunately,
   * with CloseableReferences, that should not impose too much overhead.
   *
   * @param newResult
   * @param status bitwise values describing the returned result
   * @see Status for status flags
   */
  void onNewResult(T newResult, @Status int status);

  /**
   * Called by a producer whenever it terminates further work due to Throwable being thrown. This
   * method should not throw an exception.
   *
   * @param t
   */
  void onFailure(Throwable t);

  /** Called by a producer whenever it is cancelled and won't produce any more results */
  void onCancellation();

  /**
   * Called when the progress updates.
   *
   * @param progress in range [0, 1]
   */
  void onProgressUpdate(float progress);
}

Producer的處理結(jié)果可以通過Consumer來告訴外界,比如是失敗還是成功。

九、Producer 的組合

一個(gè)ProducerA可以接收另一個(gè)ProducerB作為參數(shù),如果ProducerA處理完畢后可以調(diào)用ProducerB來繼續(xù)處理。并傳入Consumer來觀察ProducerB的處理結(jié)果。比如Fresco在加載圖片時(shí)會(huì)先去內(nèi)存緩存獲取,如果內(nèi)存緩存中沒有那么就網(wǎng)絡(luò)加載。這里涉及到兩個(gè)Producer分別是BitmapMemoryCacheProducerNetworkFetchProducer,假設(shè)BitmapMemoryCacheProducerProducerA,NetworkFetchProducerProducerB。我們用偽代碼看一下他們的邏輯:

// BitmapMemoryCacheProducer.java

public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {

    private final Producer<CloseableReference<CloseableImage>> mInputProducer;

    // 我們假設(shè) inputProducer 在這里為NetworkFetchProducer
    public BitmapMemoryCacheProducer(...,Producer<CloseableReference<CloseableImage>> inputProducer) { 
        ...
        mInputProducer = inputProducer;
    }

    @Override
    public void produceResults(Consumer<CloseableReference<CloseableImage>> consumer,...) {
        CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);

        if (cachedReference != null) { 
            //從緩存中獲取成功,直接通知外界
            consumer.onNewResult(cachedReference, BaseConsumer.simpleStatusForIsLast(isFinal));
            return; 
        }
        //包了一層Consumer,即mInputProducer產(chǎn)生結(jié)果時(shí),它自己可以觀察到
        Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer..); 
        //網(wǎng)絡(luò)加載
        mInputProducer.produceResults(wrappedConsumer, producerContext); 
    }
}
// NetworkFetchProducer.java

public class NetworkFetchProducer implements Producer<EncodedImage> {

   // 它并沒有 inputProducer, 對(duì)于 Fresco 的圖片加載來說如果網(wǎng)絡(luò)都獲取失敗,那么就是圖片加載失敗了

    @Override
    public void produceResults(final Consumer<CloseableReference<CloseableImage>> consumer,..) {

       // 網(wǎng)路獲取
       //   ...
        if(獲取到網(wǎng)絡(luò)圖片){
            notifyConsumer(...); //把結(jié)果通知給consumer,即觀察者
        }
        ...
    }
}

代碼可能不是很好理解,可以結(jié)合下面這張圖來理解這個(gè)關(guān)系:

Fresco可以通過組裝多個(gè)不同的Producer來靈活的定義不同的圖片處理流程的,多個(gè)Producer組裝在一塊稱為ProducerSequence (Fresco 中并沒有這個(gè)類哦)。一個(gè)ProducerSequence一般定義一種圖片處理流程,比如網(wǎng)絡(luò)加載圖片的ProducerSequence叫做NetworkFetchSequence,它包含多個(gè)不同類型的Producer。

十、網(wǎng)絡(luò)圖片加載的處理流程

Fresco中不同的圖片請(qǐng)求會(huì)有不同的ProducerSequence來處理,比如網(wǎng)絡(luò)圖片請(qǐng)求:

// ProducerSequenceFactory.java
private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(ImageRequest imageRequest) {
    switch (imageRequest.getSourceUriType()) {
        case SOURCE_TYPE_NETWORK: return getNetworkFetchSequence();
        ...
}

所以對(duì)于網(wǎng)絡(luò)圖片請(qǐng)求會(huì)調(diào)用getNetworkFetchSequence:

/**
* swallow result if prefetch -> bitmap cache get -> background thread hand-off -> multiplex ->
* bitmap cache -> decode -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
* network fetch.
*/
private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
    ...
    mNetworkFetchSequence = new BitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());
    ...
    return mNetworkFetchSequence;
}

getNetworkFetchSequence會(huì)經(jīng)過重重調(diào)用來組合多個(gè)Producer。這里我就不追代碼邏輯了,直接用下面這張圖來描述Fresco網(wǎng)絡(luò)加載圖片的處理流程:

可以看到Fresco的整個(gè)圖片加載過程還是十分復(fù)雜的。并且上圖我只是羅列一些關(guān)鍵的Producer,其實(shí)還有一些我沒有畫出來。

十一、總結(jié)

為了輔助理解,再提供一張總結(jié)的流程圖,將上面整個(gè)過程都放在里面了。后續(xù)的系列文章會(huì)詳細(xì)介紹 UI 和圖片加載過程,希望通過閱讀其源碼來詳細(xì)了解內(nèi)部的代碼邏輯以及設(shè)計(jì)思路。

其實(shí)我們?cè)陂喿x別人源碼的時(shí)候,除了要知道具體的細(xì)節(jié)之外,也要注意別人的模塊設(shè)計(jì),借鑒其設(shè)計(jì)思想。然后想想如果是你在設(shè)計(jì)的時(shí)候,你會(huì)怎么劃分模塊,如何將不同的模塊聯(lián)系起來。

當(dāng)模塊劃分后,里面的子模塊又是如何劃分的,它們之間協(xié)作關(guān)系如何保持。

以上就是詳細(xì)分析Fresco源碼之圖片加載流程的詳細(xì)內(nèi)容,更多關(guān)于Fresco 圖片加載流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android實(shí)現(xiàn)越界回彈效果

    Android實(shí)現(xiàn)越界回彈效果

    這篇文章主要介紹了Android實(shí)現(xiàn)越界回彈效果,支持水平和垂直方向,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Android編程實(shí)現(xiàn)手繪及保存為圖片的方法(附demo源碼下載)

    Android編程實(shí)現(xiàn)手繪及保存為圖片的方法(附demo源碼下載)

    這篇文章主要介紹了Android編程實(shí)現(xiàn)手繪及保存為圖片的方法,涉及Android畫布的使用及圖片的操作技巧,并附帶了demo源碼供讀者下載,需要的朋友可以參考下
    2015-12-12
  • Android開發(fā)之Parcel機(jī)制實(shí)例分析

    Android開發(fā)之Parcel機(jī)制實(shí)例分析

    這篇文章主要介紹了Android開發(fā)之Parcel機(jī)制,實(shí)例分析了Parcel機(jī)制的原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2015-05-05
  • Android開發(fā)中通過手機(jī)號(hào)+短信驗(yàn)證碼登錄的實(shí)例代碼

    Android開發(fā)中通過手機(jī)號(hào)+短信驗(yàn)證碼登錄的實(shí)例代碼

    最近在開發(fā)一個(gè)android的項(xiàng)目,需要通過獲取手機(jī)驗(yàn)證碼來完成登錄功能,接下來通過實(shí)例代碼給大家分享手機(jī)號(hào)+短信驗(yàn)證碼登錄的實(shí)現(xiàn)方法,需要的的朋友參考下吧
    2017-05-05
  • flutter中的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)獲取詳解

    flutter中的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)獲取詳解

    這篇文章主要為大家介紹了flutter中的網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)獲取示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Android自定義ViewGroup(側(cè)滑菜單)詳解及簡(jiǎn)單實(shí)例

    Android自定義ViewGroup(側(cè)滑菜單)詳解及簡(jiǎn)單實(shí)例

    這篇文章主要介紹了Android自定義ViewGroup(側(cè)滑菜單)詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • Android微信分享大圖遇到的問題的解決方法

    Android微信分享大圖遇到的問題的解決方法

    這篇文章主要介紹了Android微信分享大圖遇到的問題的解決方法,記錄了微信圖片分享出錯(cuò)的坑,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • Android MediaPlayer 播放音頻的方式

    Android MediaPlayer 播放音頻的方式

    這篇文章主要介紹了Android MediaPlayer 播放音頻的方式,本文給大家詳細(xì)介紹了MediaPlayer的使用方式,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-09-09
  • android嵌套滾動(dòng)入門實(shí)踐

    android嵌套滾動(dòng)入門實(shí)踐

    嵌套滾動(dòng)是 Android OS 5.0之后,google 為我們提供的新特性,本篇文章主要介紹了android嵌套滾動(dòng)入門實(shí)踐,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • Kotlin中的反射機(jī)制深入講解

    Kotlin中的反射機(jī)制深入講解

    反射,簡(jiǎn)單來說,是一種在運(yùn)行時(shí)動(dòng)態(tài)地訪問對(duì)象屬性和方法的方式,而不需要事先確定這些屬性是什么。下面這篇文章主要給大家介紹了關(guān)于Kotlin中反射機(jī)制的相關(guān)資料,需要的朋友可以參考下
    2018-11-11

最新評(píng)論