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

源碼解析Android Jetpack組件之ViewModel的使用

 更新時(shí)間:2023年04月21日 15:27:30   作者:孫先森Blog  
Jetpack 是一個(gè)豐富的組件庫,它的組件庫按類別分為 4 類,分別是架構(gòu)(Architecture)、界面(UI)、 行為(behavior)和基礎(chǔ)(foundation)。本文將從源碼和大家講講Jetpack組件中ViewModel的使用

前言

在之前 LiveData 源碼淺析的博客中提到了 ViewModel 組件,當(dāng)時(shí)對 ViewModel 的解釋是 “生命周期比Activity” 更長的對象。本文就來了解下其實(shí)現(xiàn)原理。

依賴版本

// 注意這里的 appcompat、activity-ktx、fragment-ktx
// 高版本的自動引入了 viewmodel-savedstate 實(shí)戰(zhàn)中很少用到的功能
// 篇幅原因 就不再本文中分析 viewmodel-savedstate 擴(kuò)展組件了
implementation 'androidx.appcompat:appcompat:1.0.0'

def fragment_version = "1.1.0"
def activity_version = "1.0.0"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

def lifecycle_version = "2.5.1"

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

基礎(chǔ)使用

定義

class MainViewModel: ViewModel(){ ... }
// or 
class MainViewModel(application: Application): AndroidViewModel(application){
    val data: String = ""
    
    fun requestData(){
        data = "xxx"
    }
}

在 MainVieModel 中可以定義 UI 界面中需要的數(shù)據(jù)(對象、LiveData、Flow 等等)和方法,在 Activity 真正銷毀前 ViewModel 中的數(shù)據(jù)不會丟失。

Activity 中獲取

val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or
// 引入 activity-ktx 庫可以這樣初始化 ViewModel
val vm by viewModels<MainViewModel>()

// 通過 vm 可以調(diào)用其中的方法、獲取其中的數(shù)據(jù)
vm.requestData()
Log.e(TAG, vm.data)

Fragment 中獲取

val vm = ViewModelProvider(this).get(MainViewModel::class.java)
// or 
// 獲取和 Activity 共享的 ViewModel 也就是同一個(gè) ViewModel 對象
val vm = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)

引入 fragment-ktx 可以這樣初始化

val vm = viewModels<MainViewModel>()
// or 效果同上
val vm = activityViewModels<MainViewModel>()

前置知識

ViewModel 的使用非常簡單,也很容易理解,就是一個(gè)生命周期長于 Activity 的對象,區(qū)別在于不會造成內(nèi)存泄漏。ViewModel 不是魔法,站在開發(fā)者的角度在 ViewModel 沒有問世之前橫豎屏切換需要保存狀態(tài)數(shù)據(jù)的需求通常都是通過 onSaveInstanceState、onRestoreInstanceState 來實(shí)現(xiàn)。

onSaveInstanceState、onRestoreInstanceState

關(guān)于這兩個(gè)方法這里就簡單概述一下:onSaveInstanceState 用于在 Activity 橫豎屏切換(意外銷毀)前保存數(shù)據(jù),而 onRestoreInstanceState 是用于 Activity 橫豎屏切換(重建)后獲取保存的數(shù)據(jù);

onSaveInstanceState 調(diào)用流程

由于是在 Activity 銷毀前觸發(fā),那么直接來 ActivityThread 中找到 performPauseActivity 方法:

ActivityThread.java

private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason, PendingTransactionActions pendingActions) {
    // ...
    if (shouldSaveState) {
        callActivityOnSaveInstanceState(r);
    }
    // ...
}

private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
    // ...
    // 這里通過 ActivityClientRecord 獲取到 activity
    // state 是 Bundle 對象,后面要保存的數(shù)據(jù)就放在 state 中
    mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
    // ...
}

這里有 ActivityThread 調(diào)用到了 Instrumentation 中,繼續(xù)看源碼:

Instrumentation.java

public void callActivityOnSaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
    activity.performSaveInstanceState(outState);
}

根據(jù)傳入的 activity 調(diào)用其 performSaveInstanceState 方法:

Activity.java

final void performSaveInstanceState(@NonNull Bundle outState) {
    onSaveInstanceState(outState);
}

總結(jié)一下,onSaveInstanceState 中我們將數(shù)據(jù)存儲在 Bundle 對象中,而這個(gè) Bundle 對象是存儲在 ActivityClientRecord 中。

onRestoreInstanceState 調(diào)用流程

看完了 onSaveInstanceState 的調(diào)用流程,那么 onRestoreInstanceState 的流程就來簡單說說,由于在 onStart 后發(fā)生回調(diào),所以直接去看 ActivityThread 中的源碼:

ActivityThread.java

public void handleStartActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
    // ...
    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
    // ...
}

可以看出這里從 ActivityClientRecord 中取出了 activity 和 state 進(jìn)行傳毒,后面就和 onSaveInstanceState 調(diào)用流程一樣了,源碼比較簡單就不貼了。

onRetainCustomNonConfigurationInstance、getLastCustomNonConfigurationInstance

除了 onSaveInstanceState 和 onRestoreInstanceState,在 Activity 中還有一組方法可以實(shí)現(xiàn)類似的功能,就是 onRetainCustomNonConfigurationInstance 和 getLastCustomNonConfigurationInstance,前者即保存數(shù)據(jù),后者即獲取保存的數(shù)據(jù);

簡單使用

override fun onRetainCustomNonConfigurationInstance(): Any? {
    val data = SaveStateData()
    return data
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 獲取保存的數(shù)據(jù)
    val data = getLastCustomNonConfigurationInstance() as SaveStateData
}

和 onSaveInstanceState 使用的區(qū)別在于 onSaveInstanceState 只能在其參數(shù)中的 Bundle 對象中寫入數(shù)據(jù),而 onRetainCustomNonConfigurationInstance 返回的類型是 Any(Java Object)不限制數(shù)據(jù)類型。老樣子看一下這組方法的源碼調(diào)用流程。

onRetainCustomNonConfigurationInstance

onRetainCustomNonConfigurationInstance 是在 ComponentActivity 中定義的,默認(rèn)實(shí)現(xiàn)返回 null,其在 onRetainNonConfigurationInstance 方法中被調(diào)用:

ComponentActivity.java

public Object onRetainCustomNonConfigurationInstance() {
    // ComponentActivity 中默認(rèn)返回 null
    return null;
}

public final Object onRetainNonConfigurationInstance() {
    // 保存在了 custom 變量中
    Object custom = onRetainCustomNonConfigurationInstance();
    // 這里已經(jīng)出現(xiàn) ViewModel 相關(guān)的源碼了,這里先按下不表
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
    // 新建 NonConfigurationInstances 對象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // custom 賦值給了 NonConfigurationInstances 對象
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

從 ComponentActivity 的這部分源碼中可以看出保存的數(shù)據(jù)最終放在了 NonConfigurationInstances 對象的 custom 屬性中;接著找 onRetainNonConfigurationInstance 的定義,在 Activity 中:

Activity.java

public Object onRetainNonConfigurationInstance() {
    // 默認(rèn)返回 null
    return null;
}

NonConfigurationInstances retainNonConfigurationInstances() {
    // ComponentActivity 中返回的 NonConfigurationInstances 對象
    Object activity = onRetainNonConfigurationInstance();
    // ...
    // 注意 這里有新建另一個(gè) NonConfigurationInstances 對象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    // ComponentActivity 中返回的 NonConfigurationInstances 對象
    // 存儲到了新的 NonConfigurationInstances 中的 activity 屬性中
    nci.activity = activity;
    // ...
    return nci;
}

在 Activity 類中相當(dāng)于做了一層套娃,又新建了一個(gè) NonConfigurationInstances 對象,將 ComponentActivity 中返回的 NonConfigurationInstances 對象存了進(jìn)去;

其實(shí)源碼看到這里就可以了,不過本著刨根問底的原則,我們接著再看一下 NonConfigurationInstances 到底存在了哪里?在 ActivityThread.java 中找到了調(diào)用 retainNonConfigurationInstances 的地方:

ActivityThread.java

void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    // ...
    // 這個(gè) r 是參數(shù)中的 ActivityClientRecord
    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
}

和 onSaveInstanceState 一樣存儲在了 ActivityClientRecord 中,只不過換了一個(gè)屬性罷了。

getLastCustomNonConfigurationInstance

看完了存儲的流程,簡單來看看取數(shù)據(jù)的流程。既然存的時(shí)候套娃了一下 NonConfigurationInstances,那取數(shù)據(jù)的時(shí)候肯定也需要套娃:

ComponentActivity.java

public Object getLastCustomNonConfigurationInstance() {
    // 通過 getLastNonConfigurationInstance 獲取 NonConfigurationInstances
    NonConfigurationInstances nc = (NonConfigurationInstances)
            getLastNonConfigurationInstance();
    // 返回 custom
    return nc != null ? nc.custom : null;
}

那么在 Activity 中肯定還需要取一次 ActivityClientRecord 中的 NonConfigurationInstances:

Activity.java

NonConfigurationInstances mLastNonConfigurationInstances;

public Object getLastNonConfigurationInstance() {
    // 返回其 activity 字段
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

// mLastNonConfigurationInstances 賦值在 attach 方法中
final void attach(Context context, /*參數(shù)太多 省略了*/ NonConfigurationInstances lastNonConfigurationInstances) {
    // ...
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    // ...
}

可以看出在 Activity attach 方法中就已經(jīng)拿到了套娃后的 NonConfigurationInstances 對象,我們都知道 Activity attach 方法是在 ActivityThread 的 performLaunchActivity 中調(diào)用,看一下源碼:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // ...
    // 參數(shù)太多 省略了
    // 可以看到是從 ActivityClientRecord 中取出傳入的
    activity.attach(appContext, r.lastNonConfigurationInstancesn);
    // ...
}

小節(jié)總結(jié)

兩種方式都是將數(shù)據(jù)保存到了 ActivityClientRecord 中,不同的是前者限制了 Bundle 類型,后者不限制類型(ViewModel 采用的就是后者這組方法實(shí)現(xiàn)),不過后者已經(jīng)在源碼中被標(biāo)記了刪除,并不影響使用,標(biāo)記刪除是為了讓開發(fā)者們利用 ViewModel 來接管這種需求。下面我們就正式進(jìn)入 ViewModel 源碼。

源碼分析

前置知識有點(diǎn)長,不過也幾乎把 ViewModel 的原理說透了,ViewModel 的保存、恢復(fù)是利用了系統(tǒng)提供的方法,不過還有些細(xì)節(jié)還需要在源碼中探索,比如:如何實(shí)現(xiàn) Activity/Fragment 共享 ViewModel?接下來就來深入 ViewModel 源碼。

創(chuàng)建

先來以 Activity 中創(chuàng)建 ViewModel 的這段代碼入手:

val vm by viewModels<MainViewModel>()

查看 viewModels 源碼:

// 這是一個(gè) ComponentActivity 的擴(kuò)展方法
@MainThread // 在主線程中使用
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    // 從命名也可以看出是一個(gè)工廠模式,默認(rèn)是 null
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    // 默認(rèn) factoryProducer 為 null
    // 返回的是 AndroidViewModelFactory
    val factoryPromise = factoryProducer ?: {
        val application = application ?: throw IllegalArgumentException(
            "ViewModel can be accessed only when Activity is attached"
        )
        AndroidViewModelFactory.getInstance(application)
    }
    
    // 返回了一個(gè) ViewModelLazy 對象,將 viewModelStore、factoryProducer 傳入
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

到這里先暫??匆幌?AndroidViewModelFactory 是如何初始化的,以及 viewModelStore 是什么東東:

ViewModelProvider.kt

private var sInstance: AndroidViewModelFactory? = null

@JvmStatic
public fun getInstance(application: Application): AndroidViewModelFactory {
    if (sInstance == null) {
        sInstance = AndroidViewModelFactory(application)
    }
    return sInstance!!
}

是一個(gè)單例模式,直接對 AndroidViewModelFactory 進(jìn)行實(shí)例化,再來看看 mViewModelStore

ComponentActivity.java

// 都是定義在 ComponentActivity 中的變量,默認(rèn) null
private ViewModelStore mViewModelStore;

public ViewModelStore getViewModelStore() {
    // ...
    if (mViewModelStore == null) { // 第一次啟動 activity 為 null
        // 獲取保存的數(shù)據(jù)
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
         // 優(yōu)先從保存的數(shù)據(jù)中獲取
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        // 默認(rèn)返回 ViewModelStore
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

ViewModelStore 內(nèi)部僅僅是管理一個(gè) Map<String, ViewModel>,用于緩存、清理創(chuàng)建的 ViewModel。

回過頭接著看擴(kuò)展方法 viewModels 返回的 ViewModelLazy:

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>, // ViewModel 的 class
    private val storeProducer: () -> ViewModelStore, // 默認(rèn)是 ViewModelStore
    private val factoryProducer: () -> ViewModelProvider.Factory, // 這里就是 mDefaultFactory
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty } // 
) : Lazy<VM> { // 注意這里返回的 Lazy,延遲初始化
    private var cached: VM? = null

    override val value: VM
        get() { // 由于返回的是 Lazy,也就是當(dāng)使用 ViewModel 時(shí)才會調(diào)用 get
            val viewModel = cached
            return if (viewModel == null) { // 第一次調(diào)用是 null,進(jìn)入 if
                val factory = factoryProducer() // mDefaultFactory
                val store = storeProducer() // ViewModelStore
                ViewModelProvider( // 生成 ViewModelProvider 對象
                    store,
                    factory,
                    extrasProducer()
                ).get(viewModelClass.java).also { // 調(diào)用其 get 方法獲取 ViewModel
                    cached = it  // 保存到 cached 變量
                } 
            } else {
                viewModel
            }
        }
    
    override fun isInitialized(): Boolean = cached != null
}

這里又出現(xiàn)了一個(gè)陌生的對象 CreationExtras,其內(nèi)部也是一個(gè) map,可以理解為一個(gè)鍵值對存儲對象,只不過他的 Key 是一個(gè)特殊類型。

接著查看 ViewModelProvider 的 get 方法是如何創(chuàng)建 ViewModel 的:

// 存儲ViewModel的key的前綴
internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
        ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
    // 調(diào)用重載方法,拼接 key 傳入
    // 當(dāng)前key即為:androidx.lifecycle.ViewModelProvider.DefaultKey$com.xxx.MainViewModel
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    val viewModel = store[key] // 優(yōu)先從 ViewModelStroe 中獲取緩存
    if (modelClass.isInstance(viewModel)) { // 如果類型相同 直接返回
        // 這里我們的 factory 是 AndroidViewModelFactory 所以不會走這行代碼
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        return viewModel as T
    }
    // ...
    // 這里的 defaultCreationExtras 是上一步驟中的 CreationExtras,默認(rèn)值為 CreationExtras.Empty
    // MutableCreationExtras 包裝一層就是將 defaultCreationExtras 中所有的鍵值對都copy一份
    val extras = MutableCreationExtras(defaultCreationExtras)
    // 將當(dāng)前 ViewModel 的 key 存儲進(jìn)去
    extras[VIEW_MODEL_KEY] = key
    
    return try {
        // 優(yōu)先調(diào)用雙參數(shù)方法
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        // 調(diào)用雙參數(shù)方法發(fā)生異常再調(diào)用單參數(shù)方法
        factory.create(modelClass)
    }.also { 
        // 獲取到 ViewModel 后存儲到 viewModelStore 中
        // 再提一嘴 viewModelStore 是在 ComponentActivity 中定義 
        store.put(key, it) 
    }
}

終于到了創(chuàng)建 ViewModel 的部分了,直接去看 AndroidViewModelFactory 的 create 方法:

ViewModelProvider.kt

override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
    // application 不為 null 調(diào)用單參數(shù)方法
    // 在新建 AndroidViewModelFactory 已經(jīng)傳入了 application,一般情況不為 null
    return if (application != null) { 
        create(modelClass)
    } else { 
        // application 如果為 null,則會從傳入的 extras 中嘗試獲取
        val application = extras[APPLICATION_KEY]
        if (application != null) {
            // 這個(gè) create 也是雙參數(shù),但不是遞歸,第二個(gè)參數(shù)是 application,源碼貼在下面
            create(modelClass, application)
        } else {
            // 如果 application 仍然為 null,且 ViewModel 類型為 AndroidViewModel 則拋異常
            if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                throw IllegalArgumentException(...)
            }
            // 類型不是 AndroidViewModel 則根據(jù) class 創(chuàng)建
            // 注意這里調(diào)用的 super.create 是父類方法
            // 父類方法直接根據(jù) modelClass.newInstance() 創(chuàng)建,就一行就不貼源碼了
            super.create(modelClass)
        }
    }
}

override fun <T : ViewModel> create(modelClass: Class<T>): T {
    return if (application == null) { // application 為 null 直接拋異常
        throw UnsupportedOperationException(...)
    } else {
        // 調(diào)用下面的雙參數(shù)方法
        create(modelClass, application)
    }
}

private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
    // 如果是 AndroidViewModel 類型則獲取帶 application 的構(gòu)造參數(shù)創(chuàng)建
    return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
        modelClass.getConstructor(Application::class.java).newInstance(app)
    } else {
        // 直接調(diào)用父類 create 方法通過 modelClass.newInstance() 創(chuàng)建
        super.create(modelClass)
    }
}

至此 Activity 中的 ViewModel 創(chuàng)建過程源碼就全部分析完了,總結(jié)一下:Activity 中的 ViewModel 創(chuàng)建都是通過單例工廠 AndroidViewModelFactory 的 create 方法中反射創(chuàng)建,在調(diào)用 create 創(chuàng)建前會生成字符串 key,創(chuàng)建完成后會將 key 和 vm 對象存儲到 ViewModelStore 中,后續(xù)獲取將優(yōu)先從 ViewModelStore 緩存中獲取。

ViewModelStore 是定義在 ComponentActivity 中的,ViewModel 生命周期 “長于” Activity 的原理跟這個(gè) ViewModelStore 脫不了干系。

恢復(fù)

前面小節(jié)提過,ViewModel 的恢復(fù)利用的是 onRetainNonConfigurationInstance 方法,ViewModelStore 又是定義在 ComponentActivity 中,那么直接去看 ComponentActivity 這部分的源碼:

ComponentActivity.java

public final Object onRetainNonConfigurationInstance() {
    // 留給開發(fā)者使用的字段
    Object custom = onRetainCustomNonConfigurationInstance();
    
    // 獲取當(dāng)前 Activity 的 mViewModelStore
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) { 
        // 如果為 null 則嘗試獲取上一次保存的數(shù)據(jù)
        NonConfigurationInstances nc =  (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // 獲取上一次存儲的 viewModelStore
            viewModelStore = nc.viewModelStore;
        }
    }
    // ...
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom; // 開發(fā)者用的字段
    nci.viewModelStore = viewModelStore; // 保存 viewModelStore 的字段
    return nci;
}

再來看一看 ViewModelStore 的獲取方法:

public ViewModelStore getViewModelStore() {
    // ...
    if (mViewModelStore == null) {
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
         // 優(yōu)先從保存的數(shù)據(jù)中獲取 viewModelStore
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        // 獲取不到才會新建
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

Activity 獲取 mViewModelStore 時(shí)優(yōu)先從 getLastNonConfigurationInstance 獲取到 NonConfigurationInstances 對象,再從其中獲取 viewModelStore,這樣在當(dāng)前 Activity 作用域中創(chuàng)建過的 ViewModel 都存儲在 ViewModelStore 中,當(dāng)需要再次使用時(shí)走 ViewModel 創(chuàng)建流程會直接從 ViewModelStore 中返回。

最后

再了解了 onRetainNonConfigurationInstance 這組方法之后再來探究 ViewModel 的恢復(fù)原理就很簡單了,onRetainNonConfigurationInstance 也被標(biāo)記為了刪除,google 也希望開發(fā)者盡可能的使用 ViewModel 來保存數(shù)據(jù)(臨時(shí)數(shù)據(jù))。

onRetainNonConfigurationInstance 雖然被標(biāo)記為刪除,但仍然可以正常使用,相比于 onSaveInstanceState 沒有了數(shù)據(jù)類型限制,但并不意味著我們可以隨意存儲,比較大的數(shù)據(jù)還是應(yīng)該考慮持久化存儲。

以上就是源碼解析Android Jetpack組件之ViewModel的使用的詳細(xì)內(nèi)容,更多關(guān)于Android Jetpack ViewModel的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android startActivityForResult的基本用法詳解

    Android startActivityForResult的基本用法詳解

    這篇文章主要介紹了Android startActivityForResult的基本用法詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 圖解Eclipse在線安裝ADT插件過程

    圖解Eclipse在線安裝ADT插件過程

    這篇文章主要以圖解的方式為大家分享了Eclipse在線安裝ADT插件過程,需要的朋友可以參考下
    2015-12-12
  • Android HelloChart開源庫圖表之折線圖的實(shí)例代碼

    Android HelloChart開源庫圖表之折線圖的實(shí)例代碼

    這篇文章主要介紹了Android HelloChart開源庫圖表之折線圖的實(shí)例代碼,具有很好的參考價(jià)值,希望對大家有所幫助,一起跟隨小編過來看看吧
    2018-05-05
  • Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)

    Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)

    這篇文章主要介紹了Android Bluetooth藍(lán)牙技術(shù)初體驗(yàn)的相關(guān)資料,需要的朋友可以參考下
    2016-02-02
  • Android使用surfaceView自定義抽獎大轉(zhuǎn)盤

    Android使用surfaceView自定義抽獎大轉(zhuǎn)盤

    這篇文章主要為大家詳細(xì)介紹了Android使用surfaceView自定義抽獎大轉(zhuǎn)盤,熟練掌握SurfaceVie實(shí)現(xiàn)抽獎大轉(zhuǎn)盤,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • Android系統(tǒng)檢測程序內(nèi)存占用各種方法

    Android系統(tǒng)檢測程序內(nèi)存占用各種方法

    這篇文章主要介紹了Android系統(tǒng)檢測程序內(nèi)存占用各種方法,本文講解了檢查系統(tǒng)總內(nèi)存、檢查某個(gè)程序的各類型內(nèi)存占用、檢查程序狀態(tài)、檢查程序各部分的內(nèi)存占用等內(nèi)容,需要的朋友可以參考下
    2015-03-03
  • Android畫中畫窗口開啟方法

    Android畫中畫窗口開啟方法

    Android8.0 Oreo(API Level26)允許活動啟動畫中畫Picture-in-picture(PIP)模式。PIP是一種特殊類型的多窗口模式,主要用于視頻播放。PIP模式已經(jīng)可用于Android TV,而Android8.0則讓該功能可進(jìn)一步用于其他Android設(shè)備
    2023-01-01
  • Android自定義水平進(jìn)度條的圓角進(jìn)度

    Android自定義水平進(jìn)度條的圓角進(jìn)度

    這篇文章主要為大家詳細(xì)介紹了Android自定義水平進(jìn)度條的圓角進(jìn)度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • 詳解RecyclerView設(shè)置背景圖片長寬一樣(以GridLayoutManager為例)

    詳解RecyclerView設(shè)置背景圖片長寬一樣(以GridLayoutManager為例)

    這篇文章主要介紹了詳解RecyclerView設(shè)置背景圖片長寬一樣(以GridLayoutManager為例),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • Android使用ViewPager實(shí)現(xiàn)啟動引導(dǎo)頁

    Android使用ViewPager實(shí)現(xiàn)啟動引導(dǎo)頁

    這篇文章主要為大家詳細(xì)介紹了Android使用ViewPager實(shí)現(xiàn)第一次啟動引導(dǎo)頁,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-07-07

最新評論