何炅做的代言網(wǎng)站推廣優(yōu)化
引言
Jetpack是一個開發(fā)組件工具集,它的主要目的是幫助我們編寫出更加簡潔的代碼,并簡化我們的開發(fā)過程,在這么多的組件當(dāng)中,最需要我們關(guān)注的其實還是架構(gòu)組件,接下來就對Jetpack的主要架構(gòu)組件進行學(xué)習(xí)!
ViewModel
ViewModel的誕生
- 瞬時數(shù)據(jù)的丟失:在之前應(yīng)該已經(jīng)寫過一些程序了,當(dāng)我們開啟屏幕旋轉(zhuǎn)的時候會發(fā)現(xiàn)之間的數(shù)據(jù)丟失,這是因為在屏幕旋轉(zhuǎn)的時候相當(dāng)于新創(chuàng)建了一個當(dāng)前活動
- 異步調(diào)用時的內(nèi)存泄漏:UI 控制器(Activity 或 Fragment)有自己的生命周期,它們可能會在不需要時被銷毀(例如,用戶按下 Home 鍵,或者屏幕旋轉(zhuǎn))。然而,異步任務(wù)一旦開始,通常會在后臺線程中運行,直到任務(wù)完成,不管 UI 控制器的狀態(tài)如何,如果在異步任務(wù)中持有 UI 控制器的強引用(如直接引用 Activity),那么即使 UI 控制器已經(jīng)被銷毀,這個引用仍然存在。這意味著垃圾回收器(GC)不能回收 UI 控制器的實例,因為它仍然被異步任務(wù)引用,從而導(dǎo)致內(nèi)存泄漏
- 類膨脹提高維護難度和測試難度:類似于活動當(dāng)中的代碼量過多
ViewModel的作用
它是介于View(視圖)和Model(數(shù)據(jù)模型)之間的橋梁,使數(shù)據(jù)和視圖能夠分離,也能保持通信
實踐:
新建一個活動放置一個TextView,和一個按鈕,當(dāng)每次按下這個按鈕就會使TextView部分的數(shù)字加一,這個邏輯大家想必都能很快的寫出來,找到按鈕與文本控件,為按鈕注冊點擊事件,每次使用getText()
與setText()
方法修改文本部分的內(nèi)容,但當(dāng)我們的屏幕進行旋轉(zhuǎn)的時候,數(shù)據(jù)就會丟失變?yōu)?,因此使用ViewModel來解決這個問題,我們?yōu)榘粹o注冊點擊事件,創(chuàng)建一個MyViewModel繼承于ViewModel,將文本內(nèi)容的數(shù)字設(shè)置在這里
public class MyViewModel extends ViewModel {public int number;
}
主活動的代碼:
public class MainActivity extends AppCompatActivity {private TextView textView;private MyViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {......textView = findViewById(R.id.Textview);viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);textView.setText(String.valueOf(viewModel.number));}//為按鈕注冊的點擊事件public void plus(View view) {textView.setText(String.valueOf(++viewModel.number));}
}
大部分的代碼之間經(jīng)??吹?#xff0c;分析一下中間的代碼:
ViewModelProvider
是Android架構(gòu)組件庫中的一個類,用于獲取ViewModel
實例。this
參數(shù)傳遞的是當(dāng)前的Activity
實例。new ViewModelProvider.AndroidViewModelFactory(getApplication())
創(chuàng)建了一個AndroidViewModelFactory
實例,它是一個工廠類,用于創(chuàng)建AndroidViewModel
類型的ViewModel
實例。getApplication()
方法用于獲取當(dāng)前應(yīng)用程序的上下文。.get(MyViewModel.class)
調(diào)用ViewModelProvider
的get
方法,傳入MyViewModel.class
作為參數(shù),以獲取MyViewModel
的實例。
此時無論我們?nèi)绾涡D(zhuǎn)屏幕數(shù)據(jù)都不會丟失!
ViewModel的生命周期
ViewModel
的生命周期比 Activity
或 Fragment
的生命周期更長,它不會因為配置更改(如屏幕旋轉(zhuǎn))而重建。
ViewModel 的生命周期
- 創(chuàng)建 (
onCreate
): 當(dāng)ViewModel
首次被請求時,它會被創(chuàng)建。這通常發(fā)生在Activity
或Fragment
的onCreate
或onCreateView
方法中,通過ViewModelProvider
獲取ViewModel
實例時。 - 準備 (
onCleared
):ViewModel
提供了一個onCleared()
方法,這是一個生命周期回調(diào),當(dāng)ViewModel
被清除并且即將銷毀時會被調(diào)用。你可以在這個回調(diào)中執(zhí)行清理工作,比如取消網(wǎng)絡(luò)請求、注銷觀察者等。 - 活躍: 在
ViewModel
被創(chuàng)建后,它會保持活躍狀態(tài),直到與它關(guān)聯(lián)的Activity
或Fragment
被銷毀。即使Activity
因為配置更改(如屏幕旋轉(zhuǎn))而重建,ViewModel
也不會被銷毀,它會保持原有的狀態(tài)。 - 銷毀 (
onCleared
): 當(dāng)Activity
或Fragment
被銷毀,并且沒有任何其他組件與ViewModel
關(guān)聯(lián)時,ViewModel
會被銷毀。onCleared()
方法會在銷毀時被調(diào)用。
ViewModel 與 Activity/Fragment 生命周期的關(guān)系
- Activity/Fragment 創(chuàng)建: 當(dāng)
Activity
或Fragment
創(chuàng)建時,你可以通過ViewModelProvider
獲取ViewModel
實例。如果ViewModel
已經(jīng)存在(比如在屏幕旋轉(zhuǎn)后重建Activity
時),則直接使用現(xiàn)有的實例。 - Activity/Fragment 重建: 如果
Activity
因為配置更改而重建,ViewModel
會保持不變,這意味著ViewModel
中的數(shù)據(jù)不會丟失。 - Activity/Fragment 銷毀: 當(dāng)
Activity
或Fragment
被銷毀時,ViewModel
不會被銷毀,除非沒有任何Activity
或Fragment
與它關(guān)聯(lián)。 - ViewModel 銷毀: 當(dāng)最后一個與
ViewModel
相關(guān)聯(lián)的Activity
或Fragment
被銷毀,并且ViewModel
沒有被其他組件引用時,它會被銷毀。
LiveData
和ViewModel之間的關(guān)系
當(dāng)ViewModel中的數(shù)據(jù)發(fā)生變化的時候,LiveData告訴View
使用
簡單應(yīng)用
在活動當(dāng)中設(shè)置一個TextView來顯示數(shù)據(jù),同樣的我們創(chuàng)建一個MyViewModel來防止數(shù)據(jù)的丟失
public class MyViewModel extends ViewModel {private MutableLiveData<Integer> current;public MutableLiveData<Integer> getCurrent() {if (current == null) {current = new MutableLiveData<>();current.setValue(0);}return current;}
}
public class MainActivity extends AppCompatActivity {TextView textView;MyViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {......textView = findViewById(R.id.text);viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);textView.setText(String.valueOf(viewModel.getCurrent().getValue()));viewModel.getCurrent().observe(this, new Observer<Integer>() {@Overridepublic void onChanged(Integer i) {textView.setText(String.valueOf(i));}});startTimer();}private void startTimer() {new Timer().schedule(new TimerTask() {@Overridepublic void run() {//非UI線程postValue()//UI線程setValue()viewModel.getCurrent().postValue(viewModel.getCurrent().getValue() + 1);}}, 1000, 1000);}
}
此時運行程序,屏幕上的數(shù)字就在隔一秒加一。
ViewModel與LiveData實現(xiàn)Fragment之間的數(shù)據(jù)傳遞
我們創(chuàng)建一個活動,在這個活動當(dāng)中放置兩個碎片,碎片當(dāng)中放置各自放置一個滑動條,此時活動的布局如下:
當(dāng)我們拖動碎片當(dāng)中的一條滑動條,另一個是不動的,因為我們沒有將兩個碎片聯(lián)系起來,若我們想將兩個滑動條進行關(guān)聯(lián),使滑動一條滑動條另一個也跟著動就會想到通過Fragment之間的數(shù)據(jù)傳遞部分進行聯(lián)系,就會使主活動的代碼量非常大并且 Fragment 之間高度耦合,這使得代碼難以維護和擴展。此時我們就可以使用 ViewModel
+ LiveData
去實現(xiàn)。
- 新建一個
MyViewModel
繼承于ViewModel
public class MyViewModel extends ViewModel {private MutableLiveData<Integer> progress;public MutableLiveData<Integer> getProgress() {if (progress == null) {progress.setValue(0);}return progress;}
}
- 對Fragment部分代碼進行修改,另一個碎片的代碼也是一樣的
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View root = inflater.inflate(R.layout.fragment_blank, container, false);SeekBar seekBar = root.findViewById(R.id.progress_bar1);MyViewModel myViewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);//第一處myViewModel.getProgress().observe(getActivity(), new Observer<Integer>() {@Overridepublic void onChanged(Integer i) {seekBar.setProgress(i);}});//第二處seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {//第三處@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {myViewModel.getProgress().setValue(progress);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//當(dāng)用戶開始觸摸 SeekBar 時,這個方法會被調(diào)用}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {//當(dāng)用戶停止觸摸 SeekBar 時,這個方法會被調(diào)用}});return root;
}
- 第一處:這行代碼注冊了一個觀察者(Observer)來監(jiān)聽
myViewModel.getProgress()
LiveData 中的數(shù)據(jù)變化。當(dāng) LiveData 中的值發(fā)生變化時,onChanged(Integer i)
方法會被調(diào)用,并且新的進度值i
會被傳遞到這個方法中。 - 第二處:為 SeekBar 設(shè)置了一個監(jiān)聽器,用于監(jiān)聽用戶與 SeekBar 的交互(如拖動進度條),當(dāng)進度條發(fā)生變化的時候就會執(zhí)行里面的方法
- 第三處:當(dāng)用戶拖動 SeekBar 或 SeekBar 的進度發(fā)生變化時,這個方法會被調(diào)用,
progress
參數(shù)表示 SeekBar 當(dāng)前的進度值。fromUser
參數(shù)是一個布爾值,表示進度變化是否由用戶操作引起(如果是程序調(diào)用setProgress
則為 false)。
我們并沒有對主活動進行任何的代碼添加與修改,接下來運行程序,當(dāng)我們無論滑動哪一個碎片里的進度條,另一個也會跟著滑動。
LiveData的優(yōu)勢
- 生命周期感知:
LiveData
遵循觀察者的生命周期。它確保在觀察者(如Activity
或Fragment
)處于活躍狀態(tài)時才發(fā)送數(shù)據(jù)更新,從而避免在組件已經(jīng)銷毀后更新 UI 導(dǎo)致的內(nèi)存泄漏。 - 避免內(nèi)存泄漏: 由于
LiveData
與觀察者的生命周期綁定,它不會向已經(jīng)銷毀的觀察者發(fā)送更新,這減少了因 UI 控制器生命周期管理不當(dāng)而導(dǎo)致的內(nèi)存泄漏風(fēng)險。 - 數(shù)據(jù)變化的響應(yīng)式更新: 當(dāng)數(shù)據(jù)發(fā)生變化時,
LiveData
會自動通知活躍的觀察者。這種響應(yīng)式編程模式使得 UI 能夠自動響應(yīng)數(shù)據(jù)的變化,而無需手動輪詢數(shù)據(jù)。 - 可觀察的數(shù)據(jù)存儲:
LiveData
可以存儲數(shù)據(jù)的最新狀態(tài),當(dāng)觀察者開始觀察LiveData
時,它會立即接收到當(dāng)前的數(shù)據(jù)狀態(tài),確保 UI 顯示的數(shù)據(jù)是最新的。 - 支持轉(zhuǎn)換操作:
LiveData
可以與Transformations
和MediatorLiveData
等工具結(jié)合使用,支持復(fù)雜的數(shù)據(jù)轉(zhuǎn)換和組合操作,使得數(shù)據(jù)的處理更加靈活和強大。 - 線程安全:
LiveData
的更新操作是線程安全的,你可以在后臺線程中更新LiveData
的數(shù)據(jù),而無需擔(dān)心線程同步問題。 - 簡化數(shù)據(jù)共享:
LiveData
可以簡化不同組件間的數(shù)據(jù)共享,特別是當(dāng)多個組件需要觀察同一份數(shù)據(jù)時,LiveData
提供了一種簡潔的觀察和更新機制。 - 易于測試: 由于
LiveData
的數(shù)據(jù)更新是可觀察的,你可以更容易地編寫測試用例來驗證數(shù)據(jù)更新是否按預(yù)期觸發(fā)了 UI 的變化。 - 減少 boilerplate 代碼:
LiveData
減少了在 Activity 或 Fragment 中處理數(shù)據(jù)更新和 UI 響應(yīng)的樣板代碼,使得代碼更加簡潔和易于維護。 - 支持數(shù)據(jù)恢復(fù): 在配置更改(如屏幕旋轉(zhuǎn))后,
LiveData
可以幫助恢復(fù)數(shù)據(jù)狀態(tài),因為它保持了數(shù)據(jù)的最新值,直到觀察者重新觀察它。
DataBinding
意義:讓布局文件承擔(dān)了部分原本屬于頁面的工作,使頁面與布局耦合度進一步降低
應(yīng)用
基本應(yīng)用
根據(jù)一個例子來理解吧!
- 新建一個活動,活動當(dāng)中放置一個圖片和兩個
TextView
控件,我們之前為TextView
設(shè)置內(nèi)容都是在主活動當(dāng)中先獲取到控件,在使用setText()
方法設(shè)置,現(xiàn)在我們試試不在活動當(dāng)中,此時的布局文件大家都會寫,在這里就不展示了 - 打開build.gradle文件,添加以下內(nèi)容,注意不要忘記
sync now
- 鼠標位于布局文件的最前端點擊alt+回車,就會出現(xiàn)以下內(nèi)容,我們需要添加紅色邊框部分內(nèi)容
<data>
:這個元素定義了布局文件中可以使用的數(shù)據(jù)源。<variable
:這個元素定義了一個變量,它可以在布局文件中被引用。name="idol"
:這是變量的名稱,在布局文件中可以通過這個名稱來引用變量。type="com.example.databinding.Idol"
:這是變量的類型,它指定了變量的數(shù)據(jù)類型。在這個例子中,Idol
是一個Java類,它可能定義了一些屬性,名字、等級,這些屬性可以在布局文件中被綁定到UI組件上。
- 對主活動進行修改
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Idol idol = new Idol("熊大", "五星");
activityMainBinding.setIdol(idol);
activityMainBinding
:這是一個變量,它存儲了綁定類的實例,可以通過這個變量訪問布局文件中定義的視圖和變量。DataBindingUtil.setContentView
:這是一個靜態(tài)方法,用于將布局文件綁定到當(dāng)前的Activity。它返回一個綁定類(在這個例子中是ActivityMainBinding
)的實例,這個綁定類是由Data Binding庫根據(jù)布局文件自動生成的。setIdol
:這是在ActivityMainBinding
類中定義的一個方法,它用于將Idol
對象綁定到布局文件中定義的變量idol
上。
- 對布局文件進行修改
接下來運行程序:
Important標簽和事件綁定
- Important標簽
若在我們創(chuàng)建的時候有一個值為整型怎么辦,例如在Idol當(dāng)中將star設(shè)置為整型,此時我們就需要將整形轉(zhuǎn)化為字符串類型,創(chuàng)建一個類
public class StarUtils {public static String getStar(int star) {switch (star) {case 1:return "一星";case 2:return "二星";case 3:return "三星";case 4:return "四星";}return "";}
}
修改xml文件
<data><variablename="idol"type="com.example.databinding.Idol" /><import type="com.example.databinding.StarUtils"/>
</data>
<TextViewandroid:id="@+id/dengji"android:layout_gravity="center"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{StarUtils.getStar(idol.star)}"android:textSize="25sp" />
此時在創(chuàng)建對象的時候:
Idol idol = new Idol("熊大", 1);
運行程序,也會得到相應(yīng)的結(jié)果。
<import>
:這是Data Binding布局文件中的一個XML標簽,用于導(dǎo)入Java類。type
:這是<import>
標簽的一個屬性,用于指定要導(dǎo)入的類或包的全限定名。
- 與事件綁定
設(shè)置一個按鈕,為點擊按鈕注冊事件
public class EventHander {private Context context;public EventHander(Context context) {this.context = context;}public void buttonOnclick(View view) {Toast.makeText(context, "喜歡", Toast.LENGTH_SHORT).show();}
}
將這個類添加到綁定資源當(dāng)中
<data><variablename="idol"type="com.example.databinding.Idol" /><variablename="eventbutton"type="com.example.databinding.EventHander" /><import type="com.example.databinding.StarUtils"/>
</data>
按鈕控件進行添加綁定
android:onClick="@{eventbutton.buttonOnclick}"
此時運行程序,當(dāng)按下按鈕
二級頁面的綁定
標簽引用二級頁面
我們將上面布局的按鈕進行刪除,將兩個TextView控件放在一個xml文件之下,使主活動的xml文件引用這個xml文件,此時我們就不需要在一級頁面將star進行轉(zhuǎn)換,將Import標簽進行刪除,并對引用布局部分進行標簽傳遞修改
<include layout="@layout/sub"app:idol="@{idol}"/>
在二級頁面當(dāng)中TextView的代碼與之前的一級頁面一樣,注意不要忘記添加資源部分。此時運行程序的結(jié)果與之前一樣。
文章到這里就結(jié)束了!