天天看點

Jetpack常用庫的簡單使用(一)

寫在前面

我們經常被面試官問到,你的項目用的什麼架構模式呀,MVC、MVP、MVVM ? 其實這些都是我們開發者自己設計的架構模式,非谷歌官方解決方案,我們有時候也很難把控最佳架構模式。 出于這個原因,Google官方給我們提供了Jetpack。

初識Jetpack

Jetpack是一個由多個庫組成的套件,可幫助開發者遵循最佳做法,減少樣闆代碼,并編寫可在各種 Android 版本和裝置中一緻運作的代碼,讓開發者集中精力編寫重要的業務代碼。說白了就是谷歌官方給我們開發者提供了一套解決方案,讓我們的代碼性能更高更穩定,開發效率更快。
Jetpack常用庫的簡單使用(一)

為何使用Jetpack

  • 遵循最佳做法:Android Jetpack 元件采用最新的設計方法建構,具有向後相容性,可以減少崩潰和記憶體洩漏。
  • 消除樣闆代碼:Android Jetpack 可以管理各種繁瑣的Activity(如背景任務、導航和生命周期管理),以便我們更專注業務邏輯,打造出色的應用。
  • 減少不一緻:這些庫可在各種 Android 版本和裝置中以一緻的方式運作,降低适配帶來的開發難度。

Jetpack 與 AndroidX

  • AndroidX命名空間中包含Android Jetpack 庫
  • AndroidX 代替Android Support Library
  • AAC(Android Architecture Component)中的元件納入AndroidX中
  • 谷歌把其他一些需要頻繁更新疊代的特性也都并入了AndroidX中,而不是放在SDK中,這樣便于快速更新疊代(船大好掉頭)

Lifecycle的誕生

Lifecycle是為了解耦系統元件(比如Activity)和普通元件(比如TextView)而誕生的。它包含兩個角色,分别是 LifecycleOwner 和 LifecycleObserver,即生命周期擁有者和生命周期觀察者。可以看到這麼多系統類都實作了LifecycleOwner:

Jetpack常用庫的簡單使用(一)

Lifecycle 解耦頁面(Activity)與普通元件

Jetpack常用庫的簡單使用(一)

下面我們通過一個計時器的案例比較傳統方式和使用 Lifecycle的差別在哪裡。先看看傳統的實作方式:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.os.SystemClock;
import android.widget.Chronometer;

/**
 * @author Luffy
 * @Description 傳統方式實作計時器
 * @Date 2022/3/8 23:28
 */
public class FirstStepActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  
  private Chronometer chronometer;
  /**
   * 從上一次進入頁面到退出頁面經曆的時間
   */
  private long elapsedTime;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_first);
    chronometer = findViewById(R.id.chronometer);
  }
  
  @Override
  protected void onResume() {
    super.onResume();
    // 設定基準時間: SystemClock.elapsedRealtime() --- 傳回自啟動以來的毫秒數,包括睡眠時間。
    chronometer.setBase(SystemClock.elapsedRealtime() - elapsedTime);
    chronometer.start();
  }
  
  @Override
  protected void onPause() {
    super.onPause();
    elapsedTime = SystemClock.elapsedRealtime() - chronometer.getBase();
    chronometer.stop();
  }
}      

再來看看使用 Lifecycle的實作方式 :

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

/**
 * @author Luffy
 * @Description 使用 Lifecycle
 * @Date 2022/3/8 23:28
 */
public class SecondStepActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  
  private MyChronometer mChronometer;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second_step);
    mChronometer = findViewById(R.id.chronometer);
    // 給目前 Activity添加觀察者
    getLifecycle().addObserver(mChronometer);
  }
}      
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.widget.Chronometer;

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

/**
 * @author Luffy
 * @Description 使用 Lifecycle 解耦界面群組件,通過 @OnLifecycleEvent注解可以讓 MyChronometer感覺與其綁定的系統元件生命周期
 * 的變化,也就是說在 LifeCycleOwner的生命周期産生變化的時候會調用 LifeCycleObserver中注解修飾的方法。
 * @Date 2022/3/8 23:32
 */
public class MyChronometer extends Chronometer implements LifecycleObserver {
  /**
   * 從上一次進入頁面到退出頁面經曆的時間
   */
  private long elapsedTime;
  
  public MyChronometer(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
  private void startMeter() {
    setBase(SystemClock.elapsedRealtime() - elapsedTime);
    start();
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
  private void stopMeter() {
    elapsedTime = SystemClock.elapsedRealtime() - getBase();
    stop();
  }
}      

我們發現用傳統的實作方式,會導緻元件與 Activity耦合度特别高,依賴于 Activity生命周期的 onResume 和 onPause方法,而使用 Lifecycle 則不需要重寫 Activity的 onResume 和 onPause等生命周期相關的方法,而是自定義元件在其内部管理自己的生命周期,自定義元件于上述案例中的 Activity而言,就是觀察者。

LifecycleService 解耦Service 與普通元件

首先我們建立一個 Activity,布局兩個按鈕分别用來啟動和停止服務:

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.Nullable;

/**
 * @author Luffy
 * @Description Lifecycle 解耦 service與元件案例
 * @Date 2022/3/9 0:12
 */
public class ThirdStepActivity extends Activity {
  private static final String TAG = "ThirdStepActivity";
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_third_step);
  }
  
  public void startService(View view) {
    Log.d(TAG, "startService: ");
    startService(new Intent(ThirdStepActivity.this, MyLocationService.class));
  }
  
  public void stopService(View view) {
    Log.d(TAG, "stopService: ");
    stopService(new Intent(ThirdStepActivity.this, MyLocationService.class));
  }
}      

先把觀察者建立出來:

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

/**
 * @author Luffy
 * @Description 位置資訊更新的觀察者
 * @Date 2022/3/9 0:14
 */
public class MyLocationObserver implements LifecycleObserver {
  private static final String TAG = "MyLocationObserver";
  private Context mContext;
  private LocationManager mLocationManager;
  private MyLocationListener mMyLocationListener;
  
  public MyLocationObserver(Context context) {
    mContext = context;
    mMyLocationListener = new MyLocationListener();
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
  private void startGetLocation() {
    Log.d(TAG, "startGetLocation: ");
    mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
    if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) !=
        PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mContext,
        Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
      return;
    }
    /*
     * 方法說明:
     * requestLocationUpdates:使用給定參數注冊來自給定提供者的位置更新。
     *
     * 參數說明:
     * String provider:通過什麼方式擷取GPS資訊
     * long minTimeMs:位置更新之間的最小時間間隔(以毫秒為機關)
     * float minDistanceM:位置更新之間的最小距離(以米為機關)
     * LocationListener listener:位置更新的監聽
     */
    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 1,
      mMyLocationListener);
    
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
  private void stopGetLocation() {
    Log.d(TAG, "stopGetLocation: ");
    mLocationManager.removeUpdates(mMyLocationListener);
  }
  
  static class MyLocationListener implements LocationListener {
    
    @Override
    public void onLocationChanged(@NonNull Location location) {
      Log.d(TAG, "onLocationChanged: location :" + location.toString());
    }
  }
}      

然後我們發現在Service類的構造方法中隻要寫一行關鍵代碼就OK了:

import android.util.Log;

import androidx.lifecycle.LifecycleService;

/**
 * @author Luffy
 * @Description 位置服務
 * @Date 2022/3/9 0:07
 */
public class MyLocationService extends LifecycleService {
  private static final String TAG = "MyLocationService";
  
  public MyLocationService() {
    Log.d(TAG, "MyLocationService: init.");
        // 給Service類添加觀察者
    getLifecycle().addObserver(new MyLocationObserver(this));
  }
}      

adb geo 改變虛拟機GPS資訊:

進入studioSDK\platform-tools 這個目錄下,然後cmd打開指令行視窗,輸入:adb -s emulator-5554 emu geo fix 121.4961236714487 31.24010934431376 

ProcessLifecycleOwner 監聽應用程式生命周期

import android.app.Application;

import androidx.lifecycle.ProcessLifecycleOwner;

/**
 * @author Luffy
 * @Description 給 ProcessLifecycleOwner(該類為整個應用程式程序提供生命周期) 添加觀察者實作監聽應用程式生命周期
 * @Date 2022/3/9 8:10
 */
public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    ProcessLifecycleOwner.get().getLifecycle().addObserver(new MyApplicationObserver());
  }
}      

監聽應用程式的每一個生命周期: 

import android.util.Log;

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

/**
 * @author Luffy
 * @Description
 * @Date 2022/3/9 8:12
 */
public class MyApplicationObserver implements LifecycleObserver {
  private static final String TAG = "MyApplicationObserver";
  
  @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
  private void onCreate() {
    Log.d(TAG, "Lifecycle.Event.ON_CREATE");
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_START)
  public void onStart() {
    Log.d(TAG, "Lifecycle.Event.ON_START");
  }
  
  
  @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
  public void onResume() {
    Log.d(TAG, "Lifecycle.Event.ON_RESUME");
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
  public void onPause() {
    Log.d(TAG, "Lifecycle.Event.ON_PAUSE");
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
  public void onStop() {
    Log.d(TAG, "Lifecycle.Event.ON_STOP");
  }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
  public void onDestroy() {
    Log.d(TAG, "Lifecycle.Event.ON_DESTROY");
  }
  
}      

ProcessLifecycleOwner 是針對整個應用程式的監聽,與 Activity 數量無關。

Lifecycle.Event.ON_CREATE隻會在應用啟動時調用一次,Lifecycle.Event.ON_DESTROY

永遠不會被調用。 

LifeCycle的好處

  • 幫助開發者建立可感覺生命周期的元件
  • 元件在其内部管理自己的生命周期,進而降低代碼耦合度
  • 降低記憶體洩漏發生的可能性(目前還沒有展現出來)
  • Activity、Fragment、Service、Application均有 LifeCycle支援

Lifecycle面試題

Lifecycle用來監聽生命周期的變化處理相應的操作,那元件的onStart、onStop、onPause不也可以處理相應的操作嗎?他們具體差别在哪呢?

确實是可以的。差別在于使用的友善程度和代碼的簡潔度。比如一個自定義控件,需要 onStart,onStop,onPause中處理一些邏輯。不使用 LifeCycle則需要在每個使用它的頁面中的 onStart,onStop,onPause中調用這個自定義控件的方法。如果使用LifeCycle,則隻需要把自定義控件作為觀察者與Activity綁定即可,自定義控件你内部會管理自己的生命周期。

ViewModel的誕生

ViewModel是介于View(視圖)和Model(資料模型)之間的橋梁,使視圖和資料能夠分離,也能保持通信。

Jetpack常用庫的簡單使用(一)

 經典案例:解決螢幕旋轉之後使用者操作資料丢失問題

import android.app.Application;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;

/**
 * @author Luffy
 * @Description ViewModel 案例:不要向 ViewModel中傳入Context,會導緻記憶體洩漏。如果非要使用Context,需使用 AndroidViewModel
 * 中的Application
 * @Date 2022/3/9 8:44
 */
public class MyViewModel extends /*ViewModel*/ AndroidViewModel {
  /**
   * 在 ViewModel 中聲明成員,即把資料與 ViewModel 關聯起來了
   */
  int number;
  
  public MyViewModel(@NonNull Application application) {
    super(application);
    Context applicationContext = application.getApplicationContext();
  }
}      

 在Activity中,操作的資料其實都是從 ViewModel中擷取的:

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  private TextView mTvNum;
  private MyViewModel mMyViewModel;
  private int mNumber;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.d(TAG, "onCreate: ");
    mTvNum = findViewById(R.id.tv_num);
    /* 通過 ViewModelProvider.get 擷取 ViewModel 對象:
    * 第一個參數:ViewModelStoreOwner --- 表示誰擁有這個 ViewModel 對象,其實最終是通過 ViewModelStore 保留 ViewModel
    * 第二個參數:Factory --- 用于執行個體化 ViewModel 的工廠
    */
    mMyViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()))
      .get(MyViewModel.class);
    mTvNum.setText(String.valueOf(mMyViewModel.number));
  }
  
  public void autoIncrement(View view) {
    mTvNum.setText(String.valueOf(++mMyViewModel.number));
  }
}      

 ViewModel的作用

  • 解決瞬時資料丢失
  • 解決異步調用的記憶體洩露
  • 解決類膨脹帶來的維護難度和測試難度

注意事項:

不要向ViewModel中傳入Context,會導緻記憶體洩漏。如果非要使用Context,請使用 AndroidViewModel中的Application 

LiveData的誕生

​​LiveData​​​ 是一種可觀察的資料存儲器類。與正常的可觀察類不同,LiveData 具有生命周期感覺能力,意指它遵循其他應用元件(如 Activity、Fragment 或 Service)的生命周期。這種感覺能力可確定 LiveData 僅更新處于活躍生命周期狀态()的應用元件觀察者。官方詳細介紹:​​LiveData 概覽​​

LiveData和ViewModel關系

一般情況下,我們會把 LiveData放在 ViewModel中,以便在 ViewModel中的資料發生變化時通知頁面更新UI。

Jetpack常用庫的簡單使用(一)

 案例一:秒表

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

/**
 * @author Luffy
 * @Description ViewModel 與 LiveData 關聯
 * @Date 2022/3/9 21:54
 */
public class MyViewModel extends ViewModel {
  // 記錄目前已經曆的秒數
  private MutableLiveData<Integer> currentSecond;
  
  public MutableLiveData<Integer> getCurrentLiveData() {
    if (currentSecond == null) {
      currentSecond = new MutableLiveData<>();
      // 設定初始值
      currentSecond.setValue(0);
    }
    return currentSecond;
  }
}      
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Luffy
 * @Description ViewModel + LiveData 資料實時更新
 * @Date 2022/3/9 21:50
 */
public class MainActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  
  private TextView mTvCurrentSecond;
  private MyViewModel mMyViewModel;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.d(TAG, "onCreate: ");
    mTvCurrentSecond = findViewById(R.id.tv_current_second);
    // 1.執行個體化 ViewModel
    mMyViewModel = new ViewModelProvider(this,
        new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
    // 2.從 ViewModel 執行個體中擷取資料,并設定到文本上
    mTvCurrentSecond.setText(String.valueOf(mMyViewModel.getCurrentLiveData()));
    
    // 2.給 MyViewModel 設定觀察者,當 MyViewModel 資料發生變化時,觀察者可以察覺到
    mMyViewModel.getCurrentLiveData().observe(this, integer ->
        mTvCurrentSecond.setText(String.valueOf(integer.intValue())));
    // 開啟計時器
    startTimer();
  }
  
  private void startTimer() {
    // 三個參數含義分别是:時間任務、延遲時間、間隔時間
    new Timer().schedule(new TimerTask() {
      @Override
      public void run() {
        // 非 UI 線程 postValue;UI 線程 setValue
        mMyViewModel.getCurrentLiveData().postValue(mMyViewModel.getCurrentLiveData().getValue() + 1);
      }
    }, 1000, 1000);
  }
}      

案例二:Fragment通信,實作兩個Fragment進度條同步更新的效果

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

/**
 * @author Luffy
 * @Description 将 SeekBar 的進度與 ViewModel 綁定
 * @Date 2022/3/9 22:12
 */
public class MyViewModel extends ViewModel {
  public MutableLiveData<Integer> progress;
  
  public MutableLiveData<Integer> getProgress() {
    if (progress == null) {
      progress = new MutableLiveData<>();
      progress.setValue(0);
    }
    return progress;
  }
}      
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;

import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;

/**
 * @author Luffy
 * @Description ViewModel + LiveData 實作 Fragment 間通信
 * @Date 2022/3/9 22:06
 */
public class FirstFragment extends Fragment {
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_first, container, false);
    SeekBar seekBar = rootView.findViewById(R.id.seekBar);
    
    // 1.執行個體化 MyViewModel
    MyViewModel mMyViewModel = new ViewModelProvider(getActivity(),
        new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);
    // 2.給 MyViewModel 設定觀察者,當 MyViewModel 資料發生變化時,觀察者可以察覺到,此處 seekBar是觀察者
    mMyViewModel.getProgress().observe(getActivity(), seekBar::setProgress);
    // 3.設定 SeekBar 進度更新的監聽
    seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        // 3.1 當 SeekBar 進度更新時,同步資料到對應的 ViewModel 上
        mMyViewModel.progress.setValue(seekBar.getProgress());
      }
      
      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
      
      }
      
      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
      
      }
    });
    
    return rootView;
  }
}      
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;

import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;

/**
 * @author Luffy
 * @Description 将 SeekBar 的進度與 ViewModel 綁定
 * @Date 2022/3/9 22:13
 */
public class SecondFragment extends Fragment {
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_second, container, false);
    SeekBar seekBar = rootView.findViewById(R.id.seekBar);
    
    MyViewModel mMyViewModel = new ViewModelProvider(getActivity(),
        new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);
    mMyViewModel.getProgress().observe(getActivity(), seekBar::setProgress);
    seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        mMyViewModel.progress.setValue(seekBar.getProgress());
      }
      
      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
      
      }
      
      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
      
      }
    });
    
    return rootView;
  }
}      

LiveData的優勢(先有個印象)

  • 確定界面符合資料狀态
  • 不會發生記憶體洩漏
  • 不會因Activity停止而導緻崩潰
  • 不再需要手動處理生命周期
  • 資料始終保持最新狀态
  • 适當的配置更改
  • 共享資源