5,Room
Android采用SQLite作為數(shù)據(jù)庫(kù)存儲(chǔ),開(kāi)源社區(qū)常見(jiàn)的ORM(Object Relation Mapping)庫(kù)有ORMLite,Green DAO等善玫,Room和其他庫(kù)一樣,也是SQLite上提供了一層封裝密强。
Room重要的三個(gè)概念
- Entity:實(shí)體類(lèi)茅郎,對(duì)應(yīng)的是數(shù)據(jù)庫(kù)的一張表結(jié)構(gòu),使用注釋@Entity標(biāo)記或渤。(相當(dāng)于java Bean)
- Dao:包含訪(fǎng)問(wèn)一系列訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的方法v系冗,使用注釋@Dao標(biāo)記
- Database:數(shù)據(jù)庫(kù)持有者,作為與應(yīng)用持久化相關(guān)數(shù)據(jù)的低層連接的主要接入點(diǎn)薪鹦。使用注解@Database標(biāo)記掌敬。另外需要滿(mǎn)足以下條件:定義的類(lèi)必須是一個(gè)繼承了RoomDatabase的抽象類(lèi)惯豆,在注解中需要定義與數(shù)據(jù)庫(kù)相關(guān)聯(lián)的實(shí)體類(lèi)表。包含一個(gè)沒(méi)有參數(shù)的抽象方法并且返回一個(gè)Dao對(duì)象奔害。
首先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();
}
在A(yíng)ctivity的代碼
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();
}
}
}
怎樣升級(jí)數(shù)據(jù)庫(kù):
使用Migration升級(jí)數(shù)據(jù)庫(kù)
Room會(huì)先判斷當(dāng)前有沒(méi)有直接從1到3的升級(jí)方案楷兽,如果有,就直接執(zhí)行從1到3的升級(jí)方案华临,如果沒(méi)有芯杀,那么Room會(huì)按照順序先后執(zhí)行Migration(1,2)雅潭,Migration(2揭厚,3)以完成升級(jí)
修改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ù)升級(jí)過(guò)程中,都會(huì)導(dǎo)出一個(gè)Schema文件寻馏,這是一個(gè)json格式的文件棋弥,其中包含了數(shù)據(jù)庫(kù)的基本信息,有了該文件能清楚的知道數(shù)據(jù)庫(kù)的歷次變更情況诚欠,極大地方便了開(kāi)發(fā)者排查問(wèn)題。
6漾岳,Navigation
Navigation的誕生是方便我們管理頁(yè)面的App Bar轰绵。
- 優(yōu)勢(shì)如下:
1,可視化的頁(yè)面導(dǎo)航圖尼荆,類(lèi)似于A(yíng)pple Xcode中的StoryBoard左腔,便于我們理清頁(yè)面關(guān)系。
2捅儒,通過(guò)destination和action完成頁(yè)面導(dǎo)航液样。
3,方便添加頁(yè)面切換動(dòng)畫(huà)巧还。
4鞭莽,頁(yè)面間類(lèi)型安全的參數(shù)傳遞。
5麸祷,通過(guò)Navigation UI澎怒,對(duì)菜單,底部導(dǎo)航阶牍,抽屜菜單導(dǎo)航進(jìn)行統(tǒng)一的處理喷面。
6,支持深層鏈接DeepLink走孽。 - 主要元素
1诫欠,Navigation Graph,一種新的XML資源文件锐极,包含應(yīng)用程序所有的頁(yè)面沿猜,以及頁(yè)面間的關(guān)系。
2,NavHostFragment县昂,一個(gè)特殊的Fragment肮柜,可以將它看成其他Fragment的容器,Navigation Graph中的Fragment正是通過(guò)NavHostFragment進(jìn)行展示的倒彰。
3审洞,NavController,用于在代碼中完成Navigation Graph中具體的頁(yè)面切換工作待讳。
三者之間的關(guān)系:當(dāng)你想切換Fragment時(shí)芒澜,使用NavController對(duì)象,告訴它你想要去Navigation Graph中的哪個(gè)Fragment创淡,NavController會(huì)將你想去的Fragment展示NavHostFragment中痴晦。 -
NavigationUI的作用
Fragment的切換,除了Fragment頁(yè)面本身的切換琳彩,通常還伴有App bar的變化誊酌。為了方便統(tǒng)一管理,Navigation組件引入了NavigationUI類(lèi)露乏。
示例代碼如下:
首先創(chuàng)建一個(gè)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();
}
}
如果兩個(gè)Fragment之間要通信的話(huà)代碼如下
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收到某個(gè)通知推送,我們希望用戶(hù)在點(diǎn)擊該通知時(shí)瘟仿,能夠直接跳轉(zhuǎn)到展示該通知內(nèi)容的頁(yè)面箱锐,可以通過(guò)PendingIntent來(lái)完成。
URL方式
當(dāng)用戶(hù)通過(guò)手機(jī)瀏覽器瀏覽網(wǎng)站上某個(gè)頁(yè)面時(shí)劳较,可以在網(wǎng)頁(yè)上放置一個(gè)類(lèi)似于“在應(yīng)用內(nèi)打開(kāi)”的按鈕驹止,如果用戶(hù)已經(jīng)安裝有我們app,那么通過(guò)DeepLink就能打開(kāi)相應(yīng)的頁(yè)面观蜗;如果用戶(hù)沒(méi)有安裝臊恋,那么網(wǎng)站可以導(dǎo)航到應(yīng)用程序的下載頁(yè)面,引導(dǎo)用戶(hù)安裝應(yīng)用程序嫂便。
adb shell am start -a android.intent.action.VIEW -d"http:// "
7捞镰,WorkMarager
WorkMarager的作用:在后臺(tái)執(zhí)行任務(wù),可能會(huì)消耗大量電量毙替,WorkMarager為應(yīng)用程序中那些不需要及時(shí)完成的任務(wù)岸售,提供了一個(gè)統(tǒng)一的解決方案,以便在設(shè)備電量和用戶(hù)體驗(yàn)之間達(dá)到一個(gè)較好的平衡厂画。
特點(diǎn):
不需要及時(shí)完成的任務(wù)凸丸。
保證任務(wù)一定會(huì)執(zhí)行。
兼容范圍廣袱院。最低兼容API14
使用方法:
1屎慢,添加依賴(lài)
2瞭稼,使用Work類(lèi)定義任務(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环肘,觀(guān)察任務(wù)的狀態(tài)
6,取消任務(wù)
7集灌,參數(shù)傳遞
8悔雹,周期性任務(wù)
9,任務(wù)鏈
注意:WorkManager在原生系統(tǒng)執(zhí)行是沒(méi)問(wèn)題的欣喧,在真機(jī)腌零,如小米,華為等是不一定執(zhí)行的唆阿,因?yàn)椴煌瑥S(chǎng)家對(duì)系統(tǒng)的修改都不一樣益涧,所以在真機(jī)上測(cè)試不一定有效,要做一定的適配驯鳖。
添加依賴(lài)
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í)行完之后闲询,返回?cái)?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ò)連接時(shí)執(zhí)行
// .setRequiresBatteryNotLow(true) //不在電量不足執(zhí)行
// .setRequiresCharging(true) //在充電時(shí)執(zhí)行
// .setRequiresStorageNotLow(true) //不在存儲(chǔ)容量不足時(shí)執(zhí)行
// .setRequiresDeviceIdle(true) //在待機(jī)狀態(tài)下執(zhí)行 調(diào)用需要API級(jí)別最低為23
// NetworkType.NOT_REQUIRED:對(duì)網(wǎng)絡(luò)沒(méi)有要求
// NetworkType.CONNECTED:網(wǎng)絡(luò)連接的時(shí)候執(zhí)行
// NetworkType.UNMETERED:不計(jì)費(fèi)的網(wǎng)絡(luò)比如WIFI下執(zhí)行
// NetworkType.NOT_ROAMING:非漫游網(wǎng)絡(luò)狀態(tài)
// NetworkType.METERED:計(jì)費(fèi)網(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);
//觀(guān)察任務(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是為了方便開(kāi)發(fā)者完成分頁(yè)加載而設(shè)計(jì)的一個(gè)組件摔握,它為幾種常見(jiàn)的分頁(yè)機(jī)制提供了統(tǒng)一的解決方案。
Paging有3個(gè)核心類(lèi)
- PageListAdapter
RecyclerView需要搭配適配器使用丁寄,如果希望使用Paging組件氨淌,適配器需要繼承自PageListAdapter - PageList
負(fù)責(zé)通知DataSource何時(shí)獲取數(shù)據(jù),以及如何獲取數(shù)據(jù)伊磺。從DataSource獲取的數(shù)據(jù)將存儲(chǔ)在PageList中盛正。 - DataSource
有三種PositionDataSource,PageKeyedDataSource屑埋,ItemKeyedDataSource
執(zhí)行具體的數(shù)據(jù)加載工作豪筝,數(shù)據(jù)可以來(lái)源網(wǎng)絡(luò),數(shù)據(jù)庫(kù)摘能,網(wǎng)絡(luò)+數(shù)據(jù)庫(kù)续崖。數(shù)據(jù)的載入需要在子線(xiàn)程中進(jìn)行。
1团搞,PositionDataSource
適用于可通過(guò)任意位置加載數(shù)據(jù)严望,且目標(biāo)數(shù)據(jù)源數(shù)固定的情況。
2逻恐,PageKeyedDataSource
適用于數(shù)據(jù)源已頁(yè)的方式進(jìn)行請(qǐng)求的情況像吻。
3峻黍,ItemKeyedDataSource
適用于當(dāng)目標(biāo)數(shù)據(jù)的下一頁(yè)需要依賴(lài)上一頁(yè)數(shù)據(jù)中最后一個(gè)對(duì)象中的某個(gè)字段最為key的情況,此類(lèi)分頁(yè)形式常見(jiàn)于評(píng)論功能的實(shí)現(xiàn)拨匆。