5,Room
Android采用SQLite作為數(shù)據(jù)庫存儲,開源社區(qū)常見的ORM(Object Relation Mapping)庫有ORMLite,Green DAO等,Room和其他庫一樣,也是SQLite上提供了一層封裝。
Room重要的三個概念
- Entity:實體類,對應(yīng)的是數(shù)據(jù)庫的一張表結(jié)構(gòu),使用注釋@Entity標(biāo)記。(相當(dāng)于java Bean)
- Dao:包含訪問一系列訪問數(shù)據(jù)庫的方法v,使用注釋@Dao標(biāo)記
- Database:數(shù)據(jù)庫持有者,作為與應(yīng)用持久化相關(guān)數(shù)據(jù)的低層連接的主要接入點。使用注解@Database標(biāo)記。另外需要滿足以下條件:定義的類必須是一個繼承了RoomDatabase的抽象類,在注解中需要定義與數(shù)據(jù)庫相關(guān)聯(lián)的實體類表。包含一個沒有參數(shù)的抽象方法并且返回一個Dao對象。
首先build中
implementation 'androidx.room:room-runtime:2.2.5'
annotationProcessor 'androidx.room:room-compiler:2.2.5'
@Entity(tableName = "student")
public class Student {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
public int id;
@ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
public String name;
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
public int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Ignore
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Ignore
public Student(int id) {
this.id = id;
}
}
@Dao
public interface StudentDao {
@Insert
void insertStudent(Student... students);
@Delete
void deleteStudent(Student... students);
@Update
void updateStudent(Student... students);
@Query("SELECT * FROM student")
List<Student> getAllStudent();
@Query("SELECT * FROM student WHERE id = :id")
List<Student> getStudentById(int id);
}
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "my_db.db";
private static MyDatabase mInstance;
//private MyDatabase(){}
public static synchronized MyDatabase getInstance(Context context){
if(mInstance == null){
mInstance = Room.databaseBuilder(context.getApplicationContext(),
MyDatabase.class,
DATABASE_NAME)
//.allowMainThreadQueries()
.build();
}
return mInstance;
}
public abstract StudentDao getStudentDao();
}
在Activity的代碼
public class MainActivity extends AppCompatActivity {
private StudentRecyclerViewAdapter adapter;
private StudentDao studentDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recycleView = findViewById(R.id.recycleView);
recycleView.setLayoutManager(new LinearLayoutManager(this));
List<Student> students = new ArrayList<>();
adapter = new StudentRecyclerViewAdapter(students);
recycleView.setAdapter(adapter);
MyDatabase database = MyDatabase.getInstance(this);
studentDao = database.getStudentDao();
}
public void mInsert(View view) {
Student s1 = new Student("Jack", 20);
Student s2 = new Student("Rose", 18);
new InsertStudentTask(studentDao).execute(s1,s2);
}
class InsertStudentTask extends AsyncTask<Student, Void, Void> {
private StudentDao studentDao;
public InsertStudentTask(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
protected Void doInBackground(Student... students) {
studentDao.insertStudent(students);
return null;
}
}
public void mDelete(View view) {
Student s1 = new Student(2);
new DeleteStudentTask(studentDao).execute(s1);
}
class DeleteStudentTask extends AsyncTask<Student, Void, Void> {
private StudentDao studentDao;
public DeleteStudentTask(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
protected Void doInBackground(Student... students) {
studentDao.deleteStudent(students);
return null;
}
}
public void mUpdate(View view) {
Student s1 = new Student(3,"Jason", 21);
new UpdateStudentTask(studentDao).execute(s1);
}
class UpdateStudentTask extends AsyncTask<Student, Void, Void> {
private StudentDao studentDao;
public UpdateStudentTask(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
protected Void doInBackground(Student... students) {
studentDao.updateStudent(students);
return null;
}
}
public void mQuery(View view) {
new GetAllStudentTask(studentDao).execute();
}
class GetAllStudentTask extends AsyncTask<Void,Void,Void>{
private StudentDao studentDao;
public GetAllStudentTask(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
protected Void doInBackground(Void... voids) {
List<Student> students = studentDao.getAllStudent();
adapter.setStudents(students);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
adapter.notifyDataSetChanged();
}
}
}
怎樣升級數(shù)據(jù)庫:
使用Migration升級數(shù)據(jù)庫
Room會先判斷當(dāng)前有沒有直接從1到3的升級方案,如果有,就直接執(zhí)行從1到3的升級方案,如果沒有,那么Room會按照順序先后執(zhí)行Migration(1,2),Migration(2,3)以完成升級
修改MyDatabase的代碼如下
public static synchronized MyDatabase getInstance(Context context){
if(mInstance == null){
mInstance = Room.databaseBuilder(context.getApplicationContext(),
MyDatabase.class,
DATABASE_NAME)
//.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2,MIGRATION_2_3,MIGRATION_3_4)
//.fallbackToDestructiveMigration()
.createFromAsset("prestudent.db")
.build();
}
return mInstance;
}
static final Migration MIGRATION_1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1");
}
};
static final Migration MIGRATION_2_3 = new Migration(2,3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE student ADD COLUMN bar_data INTEGER NOT NULL DEFAULT 1");
}
};
static final Migration MIGRATION_3_4 = new Migration(3,4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE temp_student (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"+
"name TEXT,"+
"age INTEGER NOT NULL,"+
"sex TEXT DEFAULT 'M',"+
"bar_data INTEGER NOT NULL DEFAULT 1)");
database.execSQL("INSERT INTO temp_student (name,age,sex,bar_data)" +
"SELECT name,age,sex,bar_data FROM student");
database.execSQL("DROP TABLE student");
database.execSQL("ALTER TABLE temp_student RENAME TO student");
}
};
Schema文件:Room在每次數(shù)據(jù)升級過程中,都會導(dǎo)出一個Schema文件,這是一個json格式的文件,其中包含了數(shù)據(jù)庫的基本信息,有了該文件能清楚的知道數(shù)據(jù)庫的歷次變更情況,極大地方便了開發(fā)者排查問題。


6,Navigation
Navigation的誕生是方便我們管理頁面的App Bar。
- 優(yōu)勢如下:
1,可視化的頁面導(dǎo)航圖,類似于Apple Xcode中的StoryBoard,便于我們理清頁面關(guān)系。
2,通過destination和action完成頁面導(dǎo)航。
3,方便添加頁面切換動畫。
4,頁面間類型安全的參數(shù)傳遞。
5,通過Navigation UI,對菜單,底部導(dǎo)航,抽屜菜單導(dǎo)航進(jìn)行統(tǒng)一的處理。
6,支持深層鏈接DeepLink。 - 主要元素
1,Navigation Graph,一種新的XML資源文件,包含應(yīng)用程序所有的頁面,以及頁面間的關(guān)系。
2,NavHostFragment,一個特殊的Fragment,可以將它看成其他Fragment的容器,Navigation Graph中的Fragment正是通過NavHostFragment進(jìn)行展示的。
3,NavController,用于在代碼中完成Navigation Graph中具體的頁面切換工作。
三者之間的關(guān)系:當(dāng)你想切換Fragment時,使用NavController對象,告訴它你想要去Navigation Graph中的哪個Fragment,NavController會將你想去的Fragment展示NavHostFragment中。 -
NavigationUI的作用
Fragment的切換,除了Fragment頁面本身的切換,通常還伴有App bar的變化。為了方便統(tǒng)一管理,Navigation組件引入了NavigationUI類。
示例代碼如下:
首先創(chuàng)建一個navigation資源文件,位置如圖所示
navigation
其中代碼如下
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.dongnaoedu.navigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim" />
<argument
android:name="user_name"
app:argType="string"
android:defaultValue="unknown"/>
<argument
android:name="age"
app:argType="integer"
android:defaultValue="0"/>
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.dongnaoedu.navigation.DetailFragment"
android:label="fragment_detail"
tools:layout="@layout/fragment_detail" >
<action
android:id="@+id/action_detailFragment_to_homeFragment"
app:destination="@id/homeFragment" />
</fragment>
</navigation>
activity_main.xml的布局如下
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/my_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity中的代碼如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavController navController = Navigation.findNavController(this, R.id.fragment);
NavigationUI.setupActionBarWithNavController(this,navController);
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.fragment);
return navController.navigateUp();
}
}
如果兩個Fragment之間要通信的話代碼如下
public class HomeFragment extends Fragment {
public HomeFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button button = getView().findViewById(R.id.button);
button.setOnClickListener((v)->{
/*Bundle args = new Bundle();
args.putString("user_name","jack");*/
Bundle args = new HomeFragmentArgs.Builder()
.setUserName("rose")
.setAge(18)
.build().toBundle();
NavController navController = Navigation.findNavController(v);
navController.navigate(R.id.action_homeFragment_to_detailFragment,args);
});
}
}
public class DetailFragment extends Fragment {
public DetailFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_detail, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button button = getView().findViewById(R.id.button2);
/*Bundle args = getArguments();
String userName = args.getString("user_name");*/
//Log.d("ning","userName:"+userName);
HomeFragmentArgs args = HomeFragmentArgs.fromBundle(getArguments());
String userName = args.getUserName();
int age = args.getAge();
Log.d("ning",userName+","+age);
button.setOnClickListener((v)->{
NavController navController = Navigation.findNavController(v);
navController.navigate(R.id.action_detailFragment_to_homeFragment);
});
}
}
- 深層連接DeepLink
PendingIntent方式
當(dāng)App收到某個通知推送,我們希望用戶在點擊該通知時,能夠直接跳轉(zhuǎn)到展示該通知內(nèi)容的頁面,可以通過PendingIntent來完成。
URL方式
當(dāng)用戶通過手機瀏覽器瀏覽網(wǎng)站上某個頁面時,可以在網(wǎng)頁上放置一個類似于“在應(yīng)用內(nèi)打開”的按鈕,如果用戶已經(jīng)安裝有我們app,那么通過DeepLink就能打開相應(yīng)的頁面;如果用戶沒有安裝,那么網(wǎng)站可以導(dǎo)航到應(yīng)用程序的下載頁面,引導(dǎo)用戶安裝應(yīng)用程序。
adb shell am start -a android.intent.action.VIEW -d"http:// "
7,WorkMarager
WorkMarager的作用:在后臺執(zhí)行任務(wù),可能會消耗大量電量,WorkMarager為應(yīng)用程序中那些不需要及時完成的任務(wù),提供了一個統(tǒng)一的解決方案,以便在設(shè)備電量和用戶體驗之間達(dá)到一個較好的平衡。
特點:
不需要及時完成的任務(wù)。
保證任務(wù)一定會執(zhí)行。
兼容范圍廣。最低兼容API14
使用方法:
1,添加依賴
2,使用Work類定義任務(wù)
3,使用WorkRequests配置任務(wù)
設(shè)置任務(wù)觸發(fā)條件
將任務(wù)觸發(fā)條件設(shè)置到WorkRequest
設(shè)置延遲執(zhí)行任務(wù)
設(shè)置指數(shù)退避策略
為任務(wù)設(shè)置tag標(biāo)簽
4,將任務(wù)提交給系統(tǒng)
5,觀察任務(wù)的狀態(tài)
6,取消任務(wù)
7,參數(shù)傳遞
8,周期性任務(wù)
9,任務(wù)鏈
注意:WorkManager在原生系統(tǒng)執(zhí)行是沒問題的,在真機,如小米,華為等是不一定執(zhí)行的,因為不同廠家對系統(tǒng)的修改都不一樣,所以在真機上測試不一定有效,要做一定的適配。
添加依賴
implementation 'androidx.work:work-runtime:2.4.0-alpha03'
自定義MyWork
public class MyWork extends Worker {
public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
String inputData = getInputData().getString("input_data");
Log.d("ning","inputData:"+inputData);
//SystemClock.sleep(2000);
Log.d("ning","MyWork doWork");
//任務(wù)執(zhí)行完之后,返回數(shù)據(jù)
Data outputData = new Data.Builder()
.putString("output_data", "執(zhí)行成功")
.build();
return Result.success(outputData);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void mAddWork(View view) {
//設(shè)置觸發(fā)條件
Constraints constraints = new Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)//網(wǎng)絡(luò)連接時執(zhí)行
// .setRequiresBatteryNotLow(true) //不在電量不足執(zhí)行
// .setRequiresCharging(true) //在充電時執(zhí)行
// .setRequiresStorageNotLow(true) //不在存儲容量不足時執(zhí)行
// .setRequiresDeviceIdle(true) //在待機狀態(tài)下執(zhí)行 調(diào)用需要API級別最低為23
// NetworkType.NOT_REQUIRED:對網(wǎng)絡(luò)沒有要求
// NetworkType.CONNECTED:網(wǎng)絡(luò)連接的時候執(zhí)行
// NetworkType.UNMETERED:不計費的網(wǎng)絡(luò)比如WIFI下執(zhí)行
// NetworkType.NOT_ROAMING:非漫游網(wǎng)絡(luò)狀態(tài)
// NetworkType.METERED:計費網(wǎng)絡(luò)比如3G,4G下執(zhí)行。
//注意:不代表恢復(fù)網(wǎng)絡(luò)了,就立馬執(zhí)行
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.build();
Data inputData = new Data.Builder()
.putString("input_data","jack")
.build();
//配置任務(wù)
//一次性執(zhí)行的任務(wù)
OneTimeWorkRequest workRequest1 = new OneTimeWorkRequest.Builder(MyWork.class)
//設(shè)置觸發(fā)條件
.setConstraints(constraints)
//設(shè)置延遲執(zhí)行
.setInitialDelay(5, TimeUnit.SECONDS)
//指數(shù)退避策略
.setBackoffCriteria(BackoffPolicy.LINEAR, Duration.ofSeconds(2))
//設(shè)置tag標(biāo)簽
.addTag("workRequest1")
//參數(shù)傳遞
.setInputData(inputData)
.build();
//周期性任務(wù)
//不能少于15分鐘
PeriodicWorkRequest workRequest2 = new PeriodicWorkRequest.Builder(MyWork.class,Duration.ofMinutes(15))
.build();
//任務(wù)提交給WorkManager
WorkManager workManager = WorkManager.getInstance(this);
workManager.enqueue(workRequest1);
//觀察任務(wù)狀態(tài)
workManager.getWorkInfoByIdLiveData(workRequest1.getId()).observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(WorkInfo workInfo) {
Log.d("ning",workInfo.toString());
if(workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED){
String outputData = workInfo.getOutputData().getString("output_data");
Log.d("ning","outputData:"+outputData);
}
}
});
//取消任務(wù)
/*new Timer().schedule(new TimerTask() {
@Override
public void run() {
workManager.cancelWorkById(workRequest1.getId());
}
}, 2000);*/
}
}
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void mAddWork(View view) {
OneTimeWorkRequest workA = new OneTimeWorkRequest.Builder(AWorker.class)
.build();
OneTimeWorkRequest workB = new OneTimeWorkRequest.Builder(BWorker.class)
.build();
OneTimeWorkRequest workC = new OneTimeWorkRequest.Builder(CWorker.class)
.build();
OneTimeWorkRequest workD = new OneTimeWorkRequest.Builder(DWorker.class)
.build();
OneTimeWorkRequest workE = new OneTimeWorkRequest.Builder(EWorker.class)
.build();
//任務(wù)組合
WorkContinuation workContinuation1 = WorkManager.getInstance(this)
.beginWith(workA)
.then(workB);
WorkContinuation workContinuation2 = WorkManager.getInstance(this)
.beginWith(workC)
.then(workD);
List<WorkContinuation> taskList = new ArrayList<>();
taskList.add(workContinuation1);
taskList.add(workContinuation2);
WorkContinuation.combine(taskList)
.then(workE)
.enqueue();
//任務(wù)鏈
/*WorkManager.getInstance(this)
.beginWith(workA)
.then(workB)
.enqueue();*/
}
}
8,Paging
Paging是為了方便開發(fā)者完成分頁加載而設(shè)計的一個組件,它為幾種常見的分頁機制提供了統(tǒng)一的解決方案。
Paging有3個核心類
- PageListAdapter
RecyclerView需要搭配適配器使用,如果希望使用Paging組件,適配器需要繼承自PageListAdapter - PageList
負(fù)責(zé)通知DataSource何時獲取數(shù)據(jù),以及如何獲取數(shù)據(jù)。從DataSource獲取的數(shù)據(jù)將存儲在PageList中。 - DataSource
有三種PositionDataSource,PageKeyedDataSource,ItemKeyedDataSource
執(zhí)行具體的數(shù)據(jù)加載工作,數(shù)據(jù)可以來源網(wǎng)絡(luò),數(shù)據(jù)庫,網(wǎng)絡(luò)+數(shù)據(jù)庫。數(shù)據(jù)的載入需要在子線程中進(jìn)行。
1,PositionDataSource
適用于可通過任意位置加載數(shù)據(jù),且目標(biāo)數(shù)據(jù)源數(shù)固定的情況。
2,PageKeyedDataSource
適用于數(shù)據(jù)源已頁的方式進(jìn)行請求的情況。
3,ItemKeyedDataSource
適用于當(dāng)目標(biāo)數(shù)據(jù)的下一頁需要依賴上一頁數(shù)據(jù)中最后一個對象中的某個字段最為key的情況,此類分頁形式常見于評論功能的實現(xiàn)。
