5,Room
Android采用SQLite作为数据库存储,开源社区常见的ORM(Object Relation Mapping)库有ORMLite,Green DAO等,Room和其他库一样,也是SQLite上提供了一层封装。
Room重要的三个概念
- Entity:实体类,对应的是数据库的一张表结构,使用注释@Entity标记。(相当于java Bean)
- Dao:包含访问一系列访问数据库的方法v,使用注释@Dao标记
- Database:数据库持有者,作为与应用持久化相关数据的低层连接的主要接入点。使用注解@Database标记。另外需要满足以下条件:定义的类必须是一个继承了RoomDatabase的抽象类,在注解中需要定义与数据库相关联的实体类表。包含一个没有参数的抽象方法并且返回一个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 getAllStudent();
@Query("SELECT * FROM student WHERE id = :id")
List 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 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 {
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 {
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 {
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{
private StudentDao studentDao;
public GetAllStudentTask(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
protected Void doInBackground(Void... voids) {
List students = studentDao.getAllStudent();
adapter.setStudents(students);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
adapter.notifyDataSetChanged();
}
}
}
怎样升级数据库:
使用Migration升级数据库
Room会先判断当前有没有直接从1到3的升级方案,如果有,就直接执行从1到3的升级方案,如果没有,那么Room会按照顺序先后执行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在每次数据升级过程中,都会导出一个Schema文件,这是一个json格式的文件,其中包含了数据库的基本信息,有了该文件能清楚的知道数据库的历次变更情况,极大地方便了开发者排查问题。
6,Navigation
Navigation的诞生是方便我们管理页面的App Bar。
- 优势如下:
1,可视化的页面导航图,类似于Apple Xcode中的StoryBoard,便于我们理清页面关系。
2,通过destination和action完成页面导航。
3,方便添加页面切换动画。
4,页面间类型安全的参数传递。
5,通过Navigation UI,对菜单,底部导航,抽屉菜单导航进行统一的处理。
6,支持深层链接DeepLink。 - 主要元素
1,Navigation Graph,一种新的XML资源文件,包含应用程序所有的页面,以及页面间的关系。
2,NavHostFragment,一个特殊的Fragment,可以将它看成其他Fragment的容器,Navigation Graph中的Fragment正是通过NavHostFragment进行展示的。
3,NavController,用于在代码中完成Navigation Graph中具体的页面切换工作。
三者之间的关系:当你想切换Fragment时,使用NavController对象,告诉它你想要去Navigation Graph中的哪个Fragment,NavController会将你想去的Fragment展示NavHostFragment中。 -
NavigationUI的作用
Fragment的切换,除了Fragment页面本身的切换,通常还伴有App bar的变化。为了方便统一管理,Navigation组件引入了NavigationUI类。
示例代码如下:
首先创建一个navigation资源文件,位置如图所示
其中代码如下
activity_main.xml的布局如下
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方式
当App收到某个通知推送,我们希望用户在点击该通知时,能够直接跳转到展示该通知内容的页面,可以通过PendingIntent来完成。
URL方式
当用户通过手机浏览器浏览网站上某个页面时,可以在网页上放置一个类似于“在应用内打开”的按钮,如果用户已经安装有我们app,那么通过DeepLink就能打开相应的页面;如果用户没有安装,那么网站可以导航到应用程序的下载页面,引导用户安装应用程序。
adb shell am start -a android.intent.action.VIEW -d"http:// "
7,WorkMarager
WorkMarager的作用:在后台执行任务,可能会消耗大量电量,WorkMarager为应用程序中那些不需要及时完成的任务,提供了一个统一的解决方案,以便在设备电量和用户体验之间达到一个较好的平衡。
特点:
不需要及时完成的任务。
保证任务一定会执行。
兼容范围广。最低兼容API14
使用方法:
1,添加依赖
2,使用Work类定义任务
3,使用WorkRequests配置任务
设置任务触发条件
将任务触发条件设置到WorkRequest
设置延迟执行任务
设置指数退避策略
为任务设置tag标签
4,将任务提交给系统
5,观察任务的状态
6,取消任务
7,参数传递
8,周期性任务
9,任务链
注意:WorkManager在原生系统执行是没问题的,在真机,如小米,华为等是不一定执行的,因为不同厂家对系统的修改都不一样,所以在真机上测试不一定有效,要做一定的适配。
添加依赖
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");
//任务执行完之后,返回数据
Data outputData = new Data.Builder()
.putString("output_data", "执行成功")
.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) {
//设置触发条件
Constraints constraints = new Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)//网络连接时执行
// .setRequiresBatteryNotLow(true) //不在电量不足执行
// .setRequiresCharging(true) //在充电时执行
// .setRequiresStorageNotLow(true) //不在存储容量不足时执行
// .setRequiresDeviceIdle(true) //在待机状态下执行 调用需要API级别最低为23
// NetworkType.NOT_REQUIRED:对网络没有要求
// NetworkType.CONNECTED:网络连接的时候执行
// NetworkType.UNMETERED:不计费的网络比如WIFI下执行
// NetworkType.NOT_ROAMING:非漫游网络状态
// NetworkType.METERED:计费网络比如3G,4G下执行。
//注意:不代表恢复网络了,就立马执行
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.build();
Data inputData = new Data.Builder()
.putString("input_data","jack")
.build();
//配置任务
//一次性执行的任务
OneTimeWorkRequest workRequest1 = new OneTimeWorkRequest.Builder(MyWork.class)
//设置触发条件
.setConstraints(constraints)
//设置延迟执行
.setInitialDelay(5, TimeUnit.SECONDS)
//指数退避策略
.setBackoffCriteria(BackoffPolicy.LINEAR, Duration.ofSeconds(2))
//设置tag标签
.addTag("workRequest1")
//参数传递
.setInputData(inputData)
.build();
//周期性任务
//不能少于15分钟
PeriodicWorkRequest workRequest2 = new PeriodicWorkRequest.Builder(MyWork.class,Duration.ofMinutes(15))
.build();
//任务提交给WorkManager
WorkManager workManager = WorkManager.getInstance(this);
workManager.enqueue(workRequest1);
//观察任务状态
workManager.getWorkInfoByIdLiveData(workRequest1.getId()).observe(this, new Observer() {
@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);
}
}
});
//取消任务
/*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();
//任务组合
WorkContinuation workContinuation1 = WorkManager.getInstance(this)
.beginWith(workA)
.then(workB);
WorkContinuation workContinuation2 = WorkManager.getInstance(this)
.beginWith(workC)
.then(workD);
List taskList = new ArrayList<>();
taskList.add(workContinuation1);
taskList.add(workContinuation2);
WorkContinuation.combine(taskList)
.then(workE)
.enqueue();
//任务链
/*WorkManager.getInstance(this)
.beginWith(workA)
.then(workB)
.enqueue();*/
}
}
8,Paging
Paging是为了方便开发者完成分页加载而设计的一个组件,它为几种常见的分页机制提供了统一的解决方案。
Paging有3个核心类
- PageListAdapter
RecyclerView需要搭配适配器使用,如果希望使用Paging组件,适配器需要继承自PageListAdapter - PageList
负责通知DataSource何时获取数据,以及如何获取数据。从DataSource获取的数据将存储在PageList中。 - DataSource
有三种PositionDataSource,PageKeyedDataSource,ItemKeyedDataSource
执行具体的数据加载工作,数据可以来源网络,数据库,网络+数据库。数据的载入需要在子线程中进行。
1,PositionDataSource
适用于可通过任意位置加载数据,且目标数据源数固定的情况。
2,PageKeyedDataSource
适用于数据源已页的方式进行请求的情况。
3,ItemKeyedDataSource
适用于当目标数据的下一页需要依赖上一页数据中最后一个对象中的某个字段最为key的情况,此类分页形式常见于评论功能的实现。