当前位置: 首页 > news >正文

Android Room 框架公共模块源码深度剖析(四)

一、引言

在 Android 开发中,数据持久化是一个常见的需求。Android Room 框架作为 Android Jetpack 组件的一部分,为开发者提供了一个抽象层,使得在 SQLite 数据库上进行数据操作变得更加简单和高效。Room 框架包含多个模块,其中公共模块是其他模块的基础,它提供了一些通用的工具类、注解和接口,为整个框架的运行提供了支撑。本文将深入剖析 Android Room 框架的公共模块,从源码级别详细分析其实现原理和使用方法。

二、公共模块概述

2.1 公共模块的作用

公共模块是 Room 框架的基础,它定义了一些通用的接口、注解和工具类,供其他模块使用。这些接口和注解为 Room 框架的各个组件提供了统一的标准和规范,使得不同模块之间能够协同工作。例如,公共模块中定义了数据库实体类的注解,以及 DAO 接口的方法注解,这些注解在编译时会被处理,生成相应的代码。

2.2 公共模块的主要组件

公共模块主要包含以下几个方面的组件:

  • 注解:如 @Entity@Dao@Query 等,用于定义数据库实体类、数据访问对象和数据库查询语句。
  • 接口:如 RoomDatabaseSupportSQLiteOpenHelper 等,定义了数据库操作的基本接口。
  • 工具类:如 Room 类,提供了创建数据库实例的静态方法。

三、注解分析

3.1 @Entity 注解

@Entity 注解用于定义数据库中的实体类,每个实体类对应数据库中的一张表。

java

import androidx.room.Entity;
import androidx.room.PrimaryKey;// 使用 @Entity 注解将该类标记为数据库实体类
@Entity(tableName = "users")
public class User {// 使用 @PrimaryKey 注解将 id 字段标记为主键@PrimaryKey(autoGenerate = true)private int id;private String name;private int age;// 构造函数public User(String name, int age) {this.name = name;this.age = age;}// Getter 和 Setter 方法public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
3.1.1 源码分析

@Entity 注解的定义如下:

java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 注解的保留策略为运行时,这样在运行时可以通过反射获取注解信息
@Retention(RetentionPolicy.RUNTIME)
// 注解的目标类型为类
@Target(ElementType.TYPE)
public @interface Entity {// 表名,默认为类名String tableName() default "";// 主键字段名数组String[] primaryKeys() default {};// 是否忽略冲突boolean ignoreConflicts() default false;// 索引数组Index[] indices() default {};// 外键数组ForeignKey[] foreignKeys() default {};
}

在编译时,Room 编译器会处理 @Entity 注解,根据注解中的信息生成相应的 SQL 语句来创建数据库表。例如,对于上述 User 类,会生成如下 SQL 语句:

sql

CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `age` INTEGER)

3.2 @Dao 注解

@Dao 注解用于定义数据访问对象(DAO)接口,DAO 接口中定义了对数据库的各种操作方法。

java

import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;// 使用 @Dao 注解将该接口标记为数据访问对象接口
@Dao
public interface UserDao {// 使用 @Insert 注解标记插入方法@Insertvoid insert(User user);// 使用 @Query 注解标记查询方法@Query("SELECT * FROM users")List<User> getAllUsers();
}
3.2.1 源码分析

@Dao 注解的定义如下:

java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 注解的保留策略为运行时,这样在运行时可以通过反射获取注解信息
@Retention(RetentionPolicy.RUNTIME)
// 注解的目标类型为接口
@Target(ElementType.TYPE)
public @interface Dao {
}

在编译时,Room 编译器会根据 @Dao 接口中的方法注解生成相应的实现类。例如,对于上述 UserDao 接口,会生成一个实现类,其中 insert 方法会将 User 对象插入到数据库中,getAllUsers 方法会从数据库中查询所有用户信息。

3.3 @Query 注解

@Query 注解用于定义数据库查询语句。

java

import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;// 使用 @Dao 注解将该接口标记为数据访问对象接口
@Dao
public interface UserDao {// 使用 @Query 注解标记查询方法,查询年龄大于指定值的用户@Query("SELECT * FROM users WHERE age > :age")List<User> getUsersByAge(int age);
}
3.3.1 源码分析

@Query 注解的定义如下:

java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 注解的保留策略为运行时,这样在运行时可以通过反射获取注解信息
@Retention(RetentionPolicy.RUNTIME)
// 注解的目标类型为方法
@Target(ElementType.METHOD)
public @interface Query {// SQL 查询语句String value();
}

在编译时,Room 编译器会解析 @Query 注解中的 SQL 语句,并根据方法的参数和返回值类型生成相应的查询代码。例如,对于上述 getUsersByAge 方法,会生成如下代码:

java

public List<User> getUsersByAge(int age) {String sql = "SELECT * FROM users WHERE age > ?";// 执行 SQL 查询Cursor cursor = db.rawQuery(sql, new String[]{String.valueOf(age)});try {List<User> users = new ArrayList<>();while (cursor.moveToNext()) {User user = new User();user.setId(cursor.getInt(cursor.getColumnIndex("id")));user.setName(cursor.getString(cursor.getColumnIndex("name")));user.setAge(cursor.getInt(cursor.getColumnIndex("age")));users.add(user);}return users;} finally {cursor.close();}
}

四、接口分析

4.1 RoomDatabase 接口

RoomDatabase 是 Room 框架的核心接口,它定义了数据库操作的基本方法。

java

import androidx.room.RoomDatabase;
import androidx.room.Database;
import androidx.room.Room;
import android.content.Context;// 使用 @Database 注解定义数据库类
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {public abstract UserDao userDao();private static AppDatabase INSTANCE;// 获取数据库实例的静态方法public static AppDatabase getInstance(Context context) {if (INSTANCE == null) {synchronized (AppDatabase.class) {if (INSTANCE == null) {// 使用 Room.databaseBuilder 方法创建数据库实例INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class, "app-database").build();}}}return INSTANCE;}
}
4.1.1 源码分析

RoomDatabase 接口的定义如下:

java

import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import java.util.List;
import java.util.concurrent.Executor;// 抽象类,定义了数据库操作的基本方法
public abstract class RoomDatabase {// 获取数据库辅助类public abstract SupportSQLiteOpenHelper getOpenHelper();// 获取数据库查询执行器public abstract Executor getQueryExecutor();// 数据库回调列表private final List<Callback> mCallbacks = new ArrayList<>();// 数据库是否已经打开private boolean mIsOpen;// 注册数据库回调public void registerCallback(Callback callback) {mCallbacks.add(callback);}// 注销数据库回调public void unregisterCallback(Callback callback) {mCallbacks.remove(callback);}// 数据库回调接口public static abstract class Callback {// 数据库创建时调用public void onCreate(SupportSQLiteDatabase db) {}// 数据库打开时调用public void onOpen(SupportSQLiteDatabase db) {}// 数据库升级时调用public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {}// 数据库降级时调用public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {}}
}

RoomDatabase 是一个抽象类,具体的实现由 Room 编译器生成。在上述 AppDatabase 类中,通过 @Database 注解定义了数据库类,entities 属性指定了数据库中包含的实体类,version 属性指定了数据库的版本号。getInstance 方法使用单例模式获取数据库实例,通过 Room.databaseBuilder 方法创建数据库实例。

4.2 SupportSQLiteOpenHelper 接口

SupportSQLiteOpenHelper 接口定义了数据库辅助类的基本方法,用于管理数据库的创建和版本升级。

java

import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import android.content.Context;// 实现 SupportSQLiteOpenHelper 接口的数据库辅助类
public class AppDatabaseHelper implements SupportSQLiteOpenHelper {private final SupportSQLiteOpenHelper mDelegate;public AppDatabaseHelper(Context context) {// 创建 SQLiteOpenHelper 的委托对象mDelegate = new FrameworkSQLiteOpenHelperFactory().create(new Configuration(context,"app-database",null,new SupportSQLiteOpenHelper.Callback(1) {@Overridepublic void onCreate(SupportSQLiteDatabase db) {// 创建数据库表db.execSQL("CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `age` INTEGER)");}@Overridepublic void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {// 数据库升级逻辑if (oldVersion < 2) {// 执行升级操作db.execSQL("ALTER TABLE users ADD COLUMN email TEXT");}}}));}@Overridepublic String getDatabaseName() {return mDelegate.getDatabaseName();}@Overridepublic void setWriteAheadLoggingEnabled(boolean enabled) {mDelegate.setWriteAheadLoggingEnabled(enabled);}@Overridepublic SupportSQLiteDatabase getWritableDatabase() {return mDelegate.getWritableDatabase();}@Overridepublic SupportSQLiteDatabase getReadableDatabase() {return mDelegate.getReadableDatabase();}@Overridepublic void close() {mDelegate.close();}
}
4.2.1 源码分析

SupportSQLiteOpenHelper 接口的定义如下:

java

import androidx.sqlite.db.SupportSQLiteDatabase;// 定义数据库辅助类的基本方法
public interface SupportSQLiteOpenHelper {// 获取数据库名称String getDatabaseName();// 设置是否启用预写日志void setWriteAheadLoggingEnabled(boolean enabled);// 获取可写数据库SupportSQLiteDatabase getWritableDatabase();// 获取可读数据库SupportSQLiteDatabase getReadableDatabase();// 关闭数据库void close();// 数据库回调接口abstract class Callback {// 数据库版本号public final int version;public Callback(int version) {this.version = version;}// 数据库创建时调用public abstract void onCreate(SupportSQLiteDatabase db);// 数据库升级时调用public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);// 数据库降级时调用public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {}// 数据库打开时调用public void onOpen(SupportSQLiteDatabase db) {}}// 数据库配置类class Configuration {public final Context context;public final String name;public final Factory factory;public final Callback callback;public final boolean allowMainThreadQueries;public final boolean journalMode;public Configuration(Context context, String name, Factory factory, Callback callback,boolean allowMainThreadQueries, boolean journalMode) {this.context = context;this.name = name;this.factory = factory;this.callback = callback;this.allowMainThreadQueries = allowMainThreadQueries;this.journalMode = journalMode;}}// 数据库辅助类工厂接口interface Factory {SupportSQLiteOpenHelper create(Configuration configuration);}
}

SupportSQLiteOpenHelper 接口定义了数据库辅助类的基本方法,如获取数据库名称、获取可写 / 可读数据库、关闭数据库等。Callback 接口定义了数据库创建、升级、降级和打开时的回调方法。Configuration 类用于配置数据库的相关信息,Factory 接口用于创建数据库辅助类实例。在上述 AppDatabaseHelper 类中,实现了 SupportSQLiteOpenHelper 接口,并通过 FrameworkSQLiteOpenHelperFactory 创建了一个数据库辅助类的委托对象。

五、工具类分析

5.1 Room

Room 类是一个工具类,提供了创建数据库实例的静态方法。

java

import androidx.room.Room;
import android.content.Context;// 使用 Room 类创建数据库实例
public class DatabaseManager {private static AppDatabase appDatabase;public static AppDatabase getAppDatabase(Context context) {if (appDatabase == null) {// 使用 Room.databaseBuilder 方法创建数据库实例appDatabase = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class, "app-database").build();}return appDatabase;}
}
5.1.1 源码分析

Room 类的定义如下:

java

import androidx.room.RoomDatabase;
import androidx.room.RoomDatabase.Builder;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.SupportSQLiteOpenHelper.Factory;
import android.content.Context;// 工具类,提供创建数据库实例的静态方法
public class Room {// 创建数据库构建器public static <T extends RoomDatabase> Builder<T> databaseBuilder(Context context,Class<T> klass,String name) {return new Builder<>(context, klass, name);}// 创建内存数据库构建器public static <T extends RoomDatabase> Builder<T> inMemoryDatabaseBuilder(Context context,Class<T> klass) {return new Builder<>(context, klass, null);}// 数据库构建器类public static class Builder<T extends RoomDatabase> {private final Context mContext;private final Class<T> mKlass;private final String mName;private Factory mOpenHelperFactory;private RoomDatabase.Callback mCallback;private boolean mAllowMainThreadQueries;private boolean mJournalMode;public Builder(Context context, Class<T> klass, String name) {mContext = context;mKlass = klass;mName = name;}// 设置数据库辅助类工厂public Builder<T> openHelperFactory(Factory factory) {mOpenHelperFactory = factory;return this;}// 设置数据库回调public Builder<T> addCallback(RoomDatabase.Callback callback) {if (mCallback == null) {mCallback = callback;} else {mCallback = new CompositeCallback(mCallback, callback);}return this;}// 设置是否允许在主线程进行数据库查询public Builder<T> allowMainThreadQueries() {mAllowMainThreadQueries = true;return this;}// 设置日志模式public Builder<T> setJournalMode(boolean journalMode) {mJournalMode = journalMode;return this;}// 构建数据库实例public T build() {if (mOpenHelperFactory == null) {mOpenHelperFactory = new FrameworkSQLiteOpenHelperFactory();}SupportSQLiteOpenHelper.Configuration configuration =SupportSQLiteOpenHelper.Configuration.builder(mContext).name(mName).callback(new RoomOpenHelper(mKlass, mCallback)).factory(mOpenHelperFactory).allowMainThreadQueries(mAllowMainThreadQueries).journalMode(mJournalMode).build();T database = RoomDatabase.newInstance(mContext, mKlass, configuration);if (mCallback != null) {database.registerCallback(mCallback);}return database;}}// 组合回调类private static class CompositeCallback extends RoomDatabase.Callback {private final RoomDatabase.Callback mCallback1;private final RoomDatabase.Callback mCallback2;public CompositeCallback(RoomDatabase.Callback callback1,RoomDatabase.Callback callback2) {mCallback1 = callback1;mCallback2 = callback2;}@Overridepublic void onCreate(SupportSQLiteDatabase db) {mCallback1.onCreate(db);mCallback2.onCreate(db);}@Overridepublic void onOpen(SupportSQLiteDatabase db) {mCallback1.onOpen(db);mCallback2.onOpen(db);}@Overridepublic void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {mCallback1.onUpgrade(db, oldVersion, newVersion);mCallback2.onUpgrade(db, oldVersion, newVersion);}@Overridepublic void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {mCallback1.onDowngrade(db, oldVersion, newVersion);mCallback2.onDowngrade(db, oldVersion, newVersion);}}
}

Room 类提供了 databaseBuilderinMemoryDatabaseBuilder 两个静态方法,用于创建数据库构建器。Builder 类用于配置数据库的相关信息,如数据库辅助类工厂、数据库回调、是否允许在主线程进行数据库查询等。build 方法用于构建数据库实例。在上述 DatabaseManager 类中,使用 Room.databaseBuilder 方法创建了一个数据库实例。

六、类型转换器分析

6.1 类型转换器的作用

在 Room 中,实体类的字段类型必须是 SQLite 支持的基本类型(如 intlongString 等)。如果实体类的字段类型不是基本类型,就需要使用类型转换器将其转换为基本类型进行存储和读取。

java

import androidx.room.TypeConverter;
import java.util.Date;// 类型转换器类
public class DateConverter {// 将 Date 类型转换为 Long 类型@TypeConverterpublic static Long toTimestamp(Date date) {return date == null ? null : date.getTime();}// 将 Long 类型转换为 Date 类型@TypeConverterpublic static Date toDate(Long timestamp) {return timestamp == null ? null : new Date(timestamp);}
}
6.1.1 源码分析

类型转换器通过 @TypeConverter 注解标记的方法实现。在上述 DateConverter 类中,toTimestamp 方法将 Date 类型转换为 Long 类型,toDate 方法将 Long 类型转换为 Date 类型。在使用类型转换器时,需要在 @Database 注解中指定类型转换器类。

java

import androidx.room.Database;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;// 使用 @Database 注解定义数据库类,并指定类型转换器类
@Database(entities = {User.class}, version = 1)
@TypeConverters({DateConverter.class})
public abstract class AppDatabase extends RoomDatabase {public abstract UserDao userDao();
}

在编译时,Room 编译器会处理类型转换器,将实体类中的非基本类型字段转换为基本类型进行存储和读取。

七、事务处理分析

7.1 事务的作用

在数据库操作中,事务用于确保一组操作要么全部成功,要么全部失败。Room 框架提供了事务处理的支持。

java

import androidx.room.RoomDatabase;
import androidx.room.Transaction;// 在 DAO 接口中定义事务方法
@Dao
public interface UserDao {// 插入用户@Insertvoid insert(User user);// 更新用户@Query("UPDATE users SET name = :name WHERE id = :id")void updateUser(int id, String name);// 定义事务方法@Transactiondefault void insertAndUpdate(User user, int updateId, String newName) {insert(user);updateUser(updateId, newName);}
}
7.1.1 源码分析

@Transaction 注解用于标记事务方法。在上述 UserDao 接口中,insertAndUpdate 方法使用 @Transaction 注解标记,该方法包含了插入和更新两个操作。在编译时,Room 编译器会生成相应的代码,将这两个操作封装在一个事务中执行。

java

// 生成的事务方法实现
@Override
public void insertAndUpdate(User user, int updateId, String newName) {db.beginTransaction();try {insert(user);updateUser(updateId, newName);db.setTransactionSuccessful();} finally {db.endTransaction();}
}

在执行事务方法时,会先调用 beginTransaction 方法开始事务,然后执行事务中的操作,最后调用 setTransactionSuccessful 方法标记事务成功,调用 endTransaction 方法结束事务。如果在事务执行过程中发生异常,setTransactionSuccessful 方法不会被调用,事务会自动回滚。

八、公共模块的编译时处理分析

8.1 编译时注解处理器

Room 框架使用编译时注解处理器来处理 @Entity@Dao@Query 等注解。编译时注解处理器会在编译阶段扫描代码中的注解,并根据注解信息生成相应的代码。

java

import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import java.util.Set;// 编译时注解处理器类
public class RoomProcessor implements XProcessor {private final XProcessingEnv mEnv;public RoomProcessor(ProcessingEnvironment env) {mEnv = XProcessingEnv.create(env);}@Overridepublic Set<String> getSupportedAnnotationTypes() {// 返回支持的注解类型return Set.of("androidx.room.Entity","androidx.room.Dao","androidx.room.Query");}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {// 处理注解for (TypeElement annotation : annotations) {if (annotation.getQualifiedName().contentEquals("androidx.room.Entity")) {// 处理 @Entity 注解processEntityAnnotation(roundEnv, annotation);} else if (annotation.getQualifiedName().contentEquals("androidx.room.Dao")) {// 处理 @Dao 注解processDaoAnnotation(roundEnv, annotation);} else if (annotation.getQualifiedName().contentEquals("androidx.room.Query")) {// 处理 @Query 注解processQueryAnnotation(roundEnv, annotation);}}return true;}private void processEntityAnnotation(RoundEnvironment roundEnv, TypeElement annotation) {// 处理 @Entity 注解的逻辑Set<? extends Element> entities = roundEnv.getElementsAnnotatedWith(annotation);for (Element entity : entities) {// 生成数据库表的 SQL 语句generateTableSql(entity);}}private void processDaoAnnotation(RoundEnvironment roundEnv, TypeElement annotation) {// 处理 @Dao 注解的逻辑Set<? extends Element> daos = roundEnv.getElementsAnnotatedWith(annotation);for (Element dao : daos) {// 生成 DAO 接口的实现类generateDaoImplementation(dao);}
8.1.1 processEntityAnnotation 方法详细分析

java

private void processEntityAnnotation(RoundEnvironment roundEnv, TypeElement annotation) {// 获取所有被 @Entity 注解标记的类元素Set<? extends Element> entities = roundEnv.getElementsAnnotatedWith(annotation);for (Element entity : entities) {// 获取 @Entity 注解实例Entity entityAnnotation = entity.getAnnotation(Entity.class);String tableName = entityAnnotation.tableName();if (tableName.isEmpty()) {// 如果未指定表名,使用类名作为表名tableName = entity.getSimpleName().toString();}StringBuilder createTableSql = new StringBuilder();createTableSql.append("CREATE TABLE IF NOT EXISTS `").append(tableName).append("` (");// 获取类的所有字段元素List<? extends Element> fields = entity.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).collect(Collectors.toList());boolean isFirstColumn = true;for (Element field : fields) {if (!isFirstColumn) {createTableSql.append(", ");}isFirstColumn = false;String columnName = field.getSimpleName().toString();String columnType = getColumnType(field.asType());// 检查是否为主键PrimaryKey primaryKeyAnnotation = field.getAnnotation(PrimaryKey.class);if (primaryKeyAnnotation != null) {createTableSql.append("`").append(columnName).append("` ").append(columnType).append(" PRIMARY KEY");if (primaryKeyAnnotation.autoGenerate()) {createTableSql.append(" AUTOINCREMENT");}} else {createTableSql.append("`").append(columnName).append("` ").append(columnType);}}createTableSql.append(")");// 生成数据库表的 SQL 语句System.out.println("Generated SQL for table " + tableName + ": " + createTableSql.toString());}
}private String getColumnType(TypeMirror typeMirror) {if (typeMirror.toString().equals("int") || typeMirror.toString().equals("java.lang.Integer")) {return "INTEGER";} else if (typeMirror.toString().equals("long") || typeMirror.toString().equals("java.lang.Long")) {return "INTEGER";} else if (typeMirror.toString().equals("String")) {return "TEXT";} else if (typeMirror.toString().equals("double") || typeMirror.toString().equals("java.lang.Double")) {return "REAL";} else if (typeMirror.toString().equals("float") || typeMirror.toString().equals("java.lang.Float")) {return "REAL";}// 可以添加更多类型的映射return "TEXT";
}

processEntityAnnotation 方法中,首先获取所有被 @Entity 注解标记的类元素。对于每个实体类,获取 @Entity 注解中的表名,如果未指定表名,则使用类名作为表名。接着,遍历实体类的所有字段,根据字段类型和是否为主键生成相应的 SQL 列定义。最后,拼接出完整的 CREATE TABLE SQL 语句。getColumnType 方法用于将 Java 类型映射为 SQLite 类型。

8.1.2 processDaoAnnotation 方法详细分析

java

private void processDaoAnnotation(RoundEnvironment roundEnv, TypeElement annotation) {// 获取所有被 @Dao 注解标记的接口元素Set<? extends Element> daos = roundEnv.getElementsAnnotatedWith(annotation);for (Element dao : daos) {String daoClassName = dao.getSimpleName().toString();String implementationClassName = daoClassName + "_Impl";StringBuilder classBuilder = new StringBuilder();classBuilder.append("public class ").append(implementationClassName).append(" implements ").append(daoClassName).append(" {\n");classBuilder.append("    private final RoomDatabase mDatabase;\n");classBuilder.append("    public ").append(implementationClassName).append("(RoomDatabase database) {\n");classBuilder.append("        this.mDatabase = database;\n");classBuilder.append("    }\n");// 获取接口的所有方法元素List<? extends Element> methods = dao.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.METHOD).collect(Collectors.toList());for (Element method : methods) {String methodName = method.getSimpleName().toString();String returnType = method.asType().toString();List<? extends VariableElement> parameters = ((ExecutableElement) method).getParameters();Insert insertAnnotation = method.getAnnotation(Insert.class);if (insertAnnotation != null) {// 处理插入方法classBuilder.append("    @Override\n");classBuilder.append("    public void ").append(methodName).append("(");for (int i = 0; i < parameters.size(); i++) {VariableElement param = parameters.get(i);classBuilder.append(param.asType().toString()).append(" ").append(param.getSimpleName());if (i < parameters.size() - 1) {classBuilder.append(", ");}}classBuilder.append(") {\n");classBuilder.append("        mDatabase.beginTransaction();\n");classBuilder.append("        try {\n");for (VariableElement param : parameters) {String entityClassName = param.asType().toString();String tableName = getTableNameFromEntity(entityClassName);StringBuilder insertSql = new StringBuilder();insertSql.append("INSERT INTO `").append(tableName).append("` (");// 获取实体类的字段TypeElement entityElement = (TypeElement) mEnv.getElementUtils().getTypeElement(entityClassName);List<? extends Element> fields = entityElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).collect(Collectors.toList());boolean isFirstColumn = true;for (Element field : fields) {if (!isFirstColumn) {insertSql.append(", ");}isFirstColumn = false;insertSql.append("`").append(field.getSimpleName()).append("`");}insertSql.append(") VALUES (");isFirstColumn = true;for (int i = 0; i < fields.size(); i++) {if (!isFirstColumn) {insertSql.append(", ");}isFirstColumn = false;insertSql.append("?");}insertSql.append(")");classBuilder.append("            mDatabase.compileStatement("").append(insertSql.toString()).append("").executeInsert();\n");}classBuilder.append("            mDatabase.setTransactionSuccessful();\n");classBuilder.append("        } finally {\n");classBuilder.append("            mDatabase.endTransaction();\n");classBuilder.append("        }\n");classBuilder.append("    }\n");}Query queryAnnotation = method.getAnnotation(Query.class);if (queryAnnotation != null) {// 处理查询方法String sql = queryAnnotation.value();classBuilder.append("    @Override\n");classBuilder.append("    public ").append(returnType).append(" ").append(methodName).append("(");for (int i = 0; i < parameters.size(); i++) {VariableElement param = parameters.get(i);classBuilder.append(param.asType().toString()).append(" ").append(param.getSimpleName());if (i < parameters.size() - 1) {classBuilder.append(", ");}}classBuilder.append(") {\n");classBuilder.append("        Cursor cursor = mDatabase.query("").append(sql).append("", new String[]{");for (int i = 0; i < parameters.size(); i++) {VariableElement param = parameters.get(i);classBuilder.append(param.getSimpleName());if (i < parameters.size() - 1) {classBuilder.append(", ");}}classBuilder.append("});\n");classBuilder.append("        try {\n");if (returnType.startsWith("java.util.List")) {// 返回列表类型String entityClassName = returnType.substring(returnType.indexOf("<") + 1, returnType.length() - 1);classBuilder.append("            List<").append(entityClassName).append("> result = new ArrayList<>();\n");classBuilder.append("            while (cursor.moveToNext()) {\n");classBuilder.append("                ").append(entityClassName).append(" item = new ").append(entityClassName).append("();\n");// 获取实体类的字段TypeElement entityElement = (TypeElement) mEnv.getElementUtils().getTypeElement(entityClassName);List<? extends Element> fields = entityElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).collect(Collectors.toList());for (Element field : fields) {String fieldName = field.getSimpleName().toString();String columnType = getColumnType(field.asType());if (columnType.equals("INTEGER")) {classBuilder.append("                item.set").append(capitalize(fieldName)).append("(cursor.getInt(cursor.getColumnIndex("").append(fieldName).append("")));\n");} else if (columnType.equals("TEXT")) {classBuilder.append("                item.set").append(capitalize(fieldName)).append("(cursor.getString(cursor.getColumnIndex("").append(fieldName).append("")));\n");}// 可以添加更多类型的处理}classBuilder.append("                result.add(item);\n");classBuilder.append("            }\n");classBuilder.append("            return result;\n");} else {// 返回单个实体类型String entityClassName = returnType;classBuilder.append("            ").append(entityClassName).append(" item = null;\n");if (cursor.moveToNext()) {classBuilder.append("                item = new ").append(entityClassName).append("();\n");// 获取实体类的字段TypeElement entityElement = (TypeElement) mEnv.getElementUtils().getTypeElement(entityClassName);List<? extends Element> fields = entityElement.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).collect(Collectors.toList());for (Element field : fields) {String fieldName = field.getSimpleName().toString();String columnType = getColumnType(field.asType());if (columnType.equals("INTEGER")) {classBuilder.append("                item.set").append(capitalize(fieldName)).append("(cursor.getInt(cursor.getColumnIndex("").append(fieldName).append("")));\n");} else if (columnType.equals("TEXT")) {classBuilder.append("                item.set").append(capitalize(fieldName)).append("(cursor.getString(cursor.getColumnIndex("").append(fieldName).append("")));\n");}// 可以添加更多类型的处理}}classBuilder.append("            return item;\n");}classBuilder.append("        } finally {\n");classBuilder.append("            cursor.close();\n");classBuilder.append("        }\n");classBuilder.append("    }\n");}}classBuilder.append("}\n");// 生成 DAO 接口的实现类System.out.println("Generated implementation class for " + daoClassName + ": " + classBuilder.toString());}
}private String getTableNameFromEntity(String entityClassName) {TypeElement entityElement = (TypeElement) mEnv.getElementUtils().getTypeElement(entityClassName);Entity entityAnnotation = entityElement.getAnnotation(Entity.class);String tableName = entityAnnotation.tableName();if (tableName.isEmpty()) {tableName = entityElement.getSimpleName().toString();}return tableName;
}private String capitalize(String str) {if (str == null || str.isEmpty()) {return str;}return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}

processDaoAnnotation 方法中,首先获取所有被 @Dao 注解标记的接口元素。对于每个 DAO 接口,生成一个实现类。遍历接口的所有方法,根据方法上的注解(如 @Insert@Query)生成相应的实现代码。对于插入方法,生成插入 SQL 语句并在事务中执行;对于查询方法,生成查询 SQL 语句并根据返回类型处理查询结果。

8.2 编译时生成的代码使用分析

8.2.1 生成的数据库表创建代码使用

在应用启动时,Room 会根据编译时生成的 SQL 语句创建数据库表。例如,在 RoomDatabaseonCreate 回调中执行这些 SQL 语句:

java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {public abstract UserDao userDao();private static AppDatabase INSTANCE;public static AppDatabase getInstance(Context context) {if (INSTANCE == null) {synchronized (AppDatabase.class) {if (INSTANCE == null) {INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class, "app-database").addCallback(new Callback() {@Overridepublic void onCreate(SupportSQLiteDatabase db) {super.onCreate(db);// 执行生成的 SQL 语句创建表db.execSQL("CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `age` INTEGER)");}}).build();}}}return INSTANCE;}
}
8.2.2 生成的 DAO 实现类使用

在应用中,通过 RoomDatabase 获取 DAO 实例并调用其方法:

java

AppDatabase database = AppDatabase.getInstance(context);
UserDao userDao = database.userDao();User user = new User("John", 25);
userDao.insert(user);List<User> users = userDao.getAllUsers();

这里的 userDao.insertuserDao.getAllUsers 方法会调用编译时生成的 DAO 实现类中的相应方法。

九、公共模块的性能优化分析

9.1 预编译 SQL 语句

在生成的 DAO 实现类中,对于插入和查询等操作,使用预编译的 SQL 语句。例如,在插入方法中:

java

mDatabase.compileStatement("INSERT INTO `users` (`name`, `age`) VALUES (?, ?)").executeInsert();

预编译 SQL 语句可以提高性能,因为数据库只需要解析一次 SQL 语句,后续执行时只需要绑定参数即可。

9.2 事务处理优化

在插入和更新等操作中,使用事务处理。例如,在插入多个实体时:

java

mDatabase.beginTransaction();
try {for (User user : users) {mDatabase.compileStatement("INSERT INTO `users` (`name`, `age`) VALUES (?, ?)").executeInsert();}mDatabase.setTransactionSuccessful();
} finally {mDatabase.endTransaction();
}

事务处理可以减少数据库的 I/O 操作,提高插入和更新的性能。

9.3 缓存机制

Room 框架内部可能使用了缓存机制,例如对于查询结果的缓存。当多次执行相同的查询时,如果数据没有发生变化,可能会直接从缓存中获取结果,而不需要再次查询数据库,从而提高查询性能。

十、公共模块的扩展性分析

10.1 自定义注解处理器

开发者可以自定义编译时注解处理器,扩展 Room 框架的功能。例如,可以定义一个新的注解,用于实现一些特定的数据库操作。

java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomQuery {String value();
}

然后在自定义的注解处理器中处理这个注解:

java

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;// 自定义注解处理器
public class CustomQueryProcessor extends AbstractProcessor {@Overridepublic Set<String> getSupportedAnnotationTypes() {return Set.of("com.example.CustomQuery");}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {for (TypeElement annotation : annotations) {if (annotation.getQualifiedName().contentEquals("com.example.CustomQuery")) {Set<? extends Element> methods = roundEnv.getElementsAnnotatedWith(annotation);for (Element method : methods) {CustomQuery customQueryAnnotation = method.getAnnotation(CustomQuery.class);String sql = customQueryAnnotation.value();// 处理自定义查询注解的逻辑System.out.println("Custom query SQL: " + sql);}}}return true;}
}

10.2 自定义类型转换器

开发者可以自定义类型转换器,支持更多的数据类型。例如,支持自定义的枚举类型:

java

import androidx.room.TypeConverter;// 自定义类型转换器
public class EnumConverter {@TypeConverterpublic static MyEnum toEnum(String value) {return value == null ? null : MyEnum.valueOf(value);}@TypeConverterpublic static String fromEnum(MyEnum enumValue) {return enumValue == null ? null : enumValue.name();}
}

然后在 @Database 注解中指定自定义的类型转换器:

java

@Database(entities = {MyEntity.class}, version = 1)
@TypeConverters({EnumConverter.class})
public abstract class MyDatabase extends RoomDatabase {public abstract MyDao myDao();
}

十一、公共模块的兼容性分析

11.1 与不同 Android 版本的兼容性

Room 框架的公共模块在设计上考虑了与不同 Android 版本的兼容性。例如,它使用了 SupportSQLiteOpenHelper 接口,该接口是 Android 支持库提供的,确保在不同版本的 Android 系统上都能正常使用 SQLite 数据库。同时,Room 框架会根据 Android 系统的版本进行一些优化和适配,例如在较新的 Android 版本上可能会使用更高效的数据库操作方法。

11.2 与其他 Jetpack 组件的兼容性

Room 框架是 Android Jetpack 组件的一部分,与其他 Jetpack 组件(如 LiveData、ViewModel、WorkManager 等)具有良好的兼容性。例如,Room 可以与 LiveData 结合使用,当数据库中的数据发生变化时,LiveData 会自动更新,从而实现数据的实时同步。

java

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;@Dao
public interface UserDao {@Query("SELECT * FROM users")LiveData<List<User>> getAllUsersLiveData();
}

在 ViewModel 中使用 LiveData:

java

import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import android.app.Application;
import java.util.List;public class UserViewModel extends AndroidViewModel {private final UserDao userDao;private final LiveData<List<User>> allUsers;public UserViewModel(Application application) {super(application);AppDatabase database = AppDatabase.getInstance(application);userDao = database.userDao();allUsers = userDao.getAllUsersLiveData();}public LiveData<List<User>> getAllUsers() {return allUsers;}
}

在 Activity 或 Fragment 中观察 LiveData:

java

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import java.util.List;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);UserViewModel userViewModel = new ViewModelProvider(this).get(UserViewModel.class);userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {@Overridepublic void onChanged(List<User> users) {// 数据更新时的处理逻辑}});}
}

十二、总结

Android Room 框架的公共模块是整个框架的基础,它提供了注解、接口、工具类等核心组件,为数据库操作提供了统一的标准和规范。通过编译时注解处理器,Room 可以根据注解信息生成相应的代码,减少了开发者的工作量。同时,公共模块在性能优化、扩展性和兼容性方面都有很好的设计,使得 Room 框架在 Android 开发中得到了广泛的应用。开发者可以深入理解公共模块的源码,更好地使用 Room 框架进行数据持久化开发。

相关文章:

Android Room 框架公共模块源码深度剖析(四)

一、引言 在 Android 开发中&#xff0c;数据持久化是一个常见的需求。Android Room 框架作为 Android Jetpack 组件的一部分&#xff0c;为开发者提供了一个抽象层&#xff0c;使得在 SQLite 数据库上进行数据操作变得更加简单和高效。Room 框架包含多个模块&#xff0c;其中…...

NumPy系列 - 创建矩阵

目录 前传直接创建数组就只是创建数组1. np.array()2. np.arange()3. np.ones()4. numpy.ones_like()5. np.zeros()6. numpy.zeros_like() 定义数据类型 参考资料 前传 由于&#xff0c;某人在上智能相关课程的时候&#xff0c;总想着一大堆的事情&#xff0c;统计股市涨跌&am…...

能“嘎嘎提升”提升用户居住体验的智能家居物联网框架推荐!

智能家居在日常生活中给我们的带来了更多的便利&#xff0c;更让有些用户切实地体会到了科技的魅力&#xff0c;对于想要打造属于自己的智能家居氛围感的用户们&#xff0c;以下是一些能够帮助提升居住体验的智能家居物联网框架及应用&#xff1a; 1. 涂鸦智能&#xff08;Tuy…...

python-leetcode 60.分割回文串

题目&#xff1a; 给定一个字符串S,请将S分割成一些子串&#xff0c;使每个子串都是回文串&#xff0c;返回S所有可能的分割方案 方法一&#xff1a;回溯深度优先搜索 1. 主要思想 使用 深度优先搜索&#xff08;DFS&#xff09; 遍历 s 的所有可能划分方式。使用 回溯&…...

Android Jetpack Compose介绍

Android Jetpack Compose Android Jetpack Compose 是 Google 推出的现代 UI 工具包&#xff0c;用于以声明式的方式构建 Android 应用的 UI。它摒弃了传统的 XML 布局方式&#xff0c;完全基于 Kotlin 编写&#xff0c;提供了更简洁、更强大的 UI 开发体验。以下是 Compose 的…...

文档搜索引擎项目测试

目录 设计测试用例 功能测试 文档搜索功能 具体的逻辑 文档显示搜索结果功能 自动化测试 功能类介绍 最终测试套件的测试结果 性能测试 查看聚合报告 每秒处理事务数/吞吐量(TPS) 响应时间 生成测试报告 界面测试 初始页面 搜索页面 总结 后续改进: 设计测试…...

Vue3项目技术点记录

vue3项目技术点笔记 1 环境变量 - 不同环境用不同的配置 环境变量命名:.env.produciton .env.development Vite.config.ts 配置 envDir:‘’ 链接: VUE3+Vite 环境变量配置 2 axios的封装 http请求拦截,响应拦截。 src下建立公共文件夹Utils下建立request.ts import axios…...

一和零 (leetcode 474

leetcode系列 文章目录 一、核心操作二、外层配合操作三、核心模式代码总结 本题是一个01背包问题&#xff0c;只是背包是一个二维数组的背包&#xff0c;分别为0的个数不能超过m&#xff0c;1的个数不能超过n&#xff0c;而物品就是题目中的字符串&#xff0c;其容量为0和1的…...

Linux vim mode | raw / cooked

注&#xff1a;机翻&#xff0c;未校。 vim terminal “raw” mode Vim 终端 “raw” 模式 1. 原始模式与已处理模式的区别 We know vim puts the terminal in “raw” mode where it receives keystrokes as they are typed, opposed to “cooked” mode where the command…...

利用Linux的I2C子系统和i2c-tools工具集写出的对I2C设备AP3216C读写的应用程序

前言 由于NXP官方提供的BSP里已经包含了其片上I2C控制器的驱动并接入到了Linux的I2C子系统&#xff0c;所以我们可以直接去写与I2C有关的应用程序了。 在本篇博文中我们用两种方式对I2C设备AP3216C进行读写操作。 第一种&#xff1a;直接利用Linux的I2C子系统对I2C设备AP3216…...

springCloud集成tdengine(原生和mapper方式) 其二 原生篇

mapper篇请看另一篇文章 一、引入pom文件 <!-- TDengine 连接器--><dependency><groupId>com.taosdata.jdbc</groupId><artifactId>taos-jdbcdriver</artifactId><version>3.5.3</version></dependency>二、在nacos中填…...

gralloc usage flags

下面这些示例主要说明了 gralloc usage flags 在图像处理和多媒体应用中如何影响性能和正确性。让我们逐个详细分析每个问题的 根因 和 修复方案&#xff0c;并深入解析 gralloc 标志对 缓存管理 和 数据流 的影响。 ✅ Example 1: 长曝光快照耗时异常 &#x1f4cc; 问题描述…...

RIP路由欺骗攻击与防御实验详解

一、基础网络配置 1. 路由器R1配置 interface GigabitEthernet0/0/0ip address 192.1.2.254 255.255.255.0 ! interface GigabitEthernet0/0/1ip address 192.1.3.254 255.255.255.0 ! router rip 1version 2network 192.1.2.0network 192.1.3.0 2. 路由器R2配置 interface…...

在 Linux下使用 Python 3.11 和 FastAPI 搭建带免费证书的 HTTPS 服务器

在当今数字化时代&#xff0c;保障网站数据传输的安全性至关重要。HTTPS 协议通过使用 SSL/TLS 加密技术&#xff0c;能够有效防止数据在传输过程中被窃取或篡改。本教程将详细介绍如何在 Ubuntu 22.04 系统上&#xff0c;使用 Python 3.11 和 FastAPI 框架搭建一个带有免费 SS…...

[QMT量化交易小白入门]-三十五、如何将聚宽策略信号转为QMT实盘交易

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读一、聚宽模拟运行:构建交易策略的基础在聚宽…...

国思RDIF低代码快速开发框架 v6.2版本发布

1、平台介绍 国思RDIF企业级低代码开发平台&#xff0c;给用户和开发者最佳的框架平台方案&#xff0c;为企业快速构建跨平台、企业级的应用提供强大支持。致力于解决企业信息化项目交付难、实施效率低、开发成本高的问题。能帮助企业快速构建美观易用、架构专业、安全可控的企…...

学习笔记 ASP.NET Core Web API 8.0部署到iis

一.修改配置文件 修改Program.cs配置文件将 if (app.Environment.IsDevelopment()) {app.UseSwagger();app.UseSwaggerUI(); }修改为 app.UseSwagger(); app.UseSwaggerUI(); 二.安装ASP.NET Core Runtime 8.0.14 文件位置https://dotnet.microsoft.com/en-us/download/do…...

【Linux】信号:产生信号

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;linux笔记仓 目录 01.信号信号处理简单理解信号的发送和保存由软件产生的信号由硬件&#xff08;异常&#xff09;产生的信号 01.信号 进程信号是操作系统提供的一种异步通知机制&#xff0c;用于…...

单片机自学总结

自从工作以来&#xff0c;一直努力耕耘单片机&#xff0c;至今&#xff0c;颇有收获。从51单片机&#xff0c;PIC单片机&#xff0c;直到STM32&#xff0c;以及RTOS和Linux&#xff0c;几乎天天在搞:51单片机&#xff0c;STM8S207单片机&#xff0c;PY32F003单片机&#xff0c;…...

Idea集成docker通过ca加密实现镜像打包

​ Idea集成docker实现镜像打包_ideadocker镜像打包-CSDN博客 ​ 之前通过这种方式虽然可以实现idea通过maven打jar包的同时把docker镜像也进行打包&#xff0c;但是这种方式存在很大漏洞&#xff0c;就是服务器的2375端口大开&#xff0c;任何人拿着idea通过这种方式都可以连…...

【Prometheus】prometheus标签替换label_replace,动态修改生成标签,增强查询的灵活性和表达能力

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...

蓝桥杯 之 暴力回溯

文章目录 数字接龙小u的最大连续移动次数问题 在蓝桥杯中&#xff0c;十分喜欢考察对于网格的回溯的问题&#xff0c;对于这类的问题&#xff0c;常常会使用到这个DFS和BFS进行考察&#xff0c;不过无论怎么考察&#xff0c;都只是会在最基础的模本的基础上&#xff0c;根据这个…...

在K8S中挂载 Secret 到 Pod

在 Kubernetes 里&#xff0c;把 Secret 挂载到 Pod 中有两种主要方式&#xff1a;作为卷挂载和作为环境变量挂载。下面为你提供相应的代码示例。 作为卷挂载 Secret 将 Secret 作为卷挂载到 Pod 时&#xff0c;Secret 的每个键会成为挂载目录下的一个文件&#xff0c;文件内…...

LINUX网络编程API原型详细解析

1. 网络体系 1.1. 简介 网络采用分而治之的方法设计&#xff0c;将网络的功能划分为不同的模块&#xff0c;以分层的形式有机组合在一起。 每层实现不同的功能&#xff0c;其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务&#xff0c;同时使用下层提供…...

Android 13 Launcher3最近任务列表“全部清除“按钮位置优化实战

一、问题背景与实现难点 在Android 13横屏设备开发中&#xff0c;系统默认将最近任务列表的"全部清除"按钮布局在屏幕左侧&#xff0c;这与用户习惯的底部布局存在明显差异。相较于Android 8.1时代SystemUI模块的实现&#xff0c;Android 13将相关逻辑迁移至Launche…...

docker最新源,及遇到问题+处理

目前国内可用Docker镜像源汇总&#xff08;截至2025年3月&#xff09; - CoderJia 遇到问题&#xff1a; Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io on [::1]:53: read udp [::1]:13287->[:…...

Python简单爬虫实践案例

学习目标 能够知道Web开发流程 能够掌握FastAPI实现访问多个指定网页 知道通过requests模块爬取图片 知道通过requests模块爬取GDP数据 能够用pyecharts实现饼图 能够知道logging日志的使用 一、基于FastAPI之Web站点开发 1、基于FastAPI搭建Web服务器 # 导入FastAPI模…...

统信UOS中使用Vscode编程

写在前面&#xff1a;统信UOS其实就是套壳的Linux系统&#xff0c;所以有问题如果搜不到解决方法&#xff0c;可以参考Linux下的解决方法。 1.环境配置 Vscode : 1.85.0 Vscode就直接下载安装就行&#xff0c;然后安装插件&#xff1a;Volar、中文汉化包 node&#xff1a;18…...

C#从入门到精通(1)

目录 第一章 C#与VS介绍 第二章 第一个C#程序 &#xff08;1&#xff09;C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 &#xff08;2&#xff09;程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…...

openharmony中hilog实证记录说明(3.1和5.0版本)

每次用这个工具hilog都有一些小用法记不清&#xff0c;需要花一些时间去查去分析使用方法&#xff0c;为了给丰富多彩的生活留出更多的时间&#xff0c;所以汇总整理共享来了&#xff0c;它来了它来了~~~~~~~~~ 开始是想通过3.1来汇总的&#xff0c;但实际测试发现openharmony…...

飞书 设计智能字段:通过“字段类型”添加AI功能列

在飞书多维表格中&#xff0c;通过“字段类型”添加AI功能列的核心逻辑是将AI模型能力与结构化数据结合&#xff0c;实现自动化内容生成与信息处理。以下是具体操作步骤及关键要点&#xff0c;结合实际应用场景说明&#xff1a; 一、基础操作步骤 创建多维表格 登录飞书&#x…...

Cannot find module @rollup/rollup-win32-x64-msvc

方法1 在package.json中添加postinstall: "scripts": {"postinstall": "node -e \"const { platform } process; if (platform win32) { require(child_process).execSync(npm install rollup/rollup-win32-x64-msvc, { stdio: inherit });…...

Docker和Dify学习笔记

文章目录 1 docker学习1.1 基本命令使用1.1.1 docker ps查看当前正在运行的镜像1.1.2 docker stop停止容器1.1.3 docker compose容器编排1.1.4 docker网络[1] 进入到容器里面敲命令[2] docker network ls[3] brige网络模式下容器访问宿主机的方式 2 Dify的安装和基础使用2.1 下…...

【AIGC】Win10系统极速部署Docker+Ragflow+Dify

【AIGC】WIN10仅3步部署DockerRagflowDify 一、 Docker快速部署1.F2进入bios界面&#xff0c;按F7设置开启VMX虚拟化技术。保存并退出。2.打开控制面板配置开启服务3.到官网下载docker安装包&#xff0c;一键安装&#xff08;全部默认勾选&#xff09; 二、 RagFlow快速部署1.确…...

Oracle ASM 磁盘组冗余策略

Oracle ASM 磁盘组冗余策略 1. 外部冗余&#xff08;External Redundancy&#xff09;2. 普通冗余&#xff08;Normal Redundancy&#xff09;3. 高冗余&#xff08;High Redundancy&#xff09;关键注意事项如何选择合适的策略&#xff1f; Oracle ASM&#xff08;Automatic S…...

C++ 数据结构

C++ 数据结构 概述 C++作为一种强大的编程语言,在软件开发领域有着广泛的应用。数据结构作为C++编程中不可或缺的一部分,它决定了程序的性能和效率。本文将详细介绍C++中的常见数据结构,包括其定义、特点以及在实际应用中的使用方法。 常见数据结构 1. 数组 数组是一种…...

Unity NodeCanvas AI使用笔记

扩展: 1. 输入输出参数限制&#xff0c;增加描述&#xff0c;根据接口判断类型限制 2.选择节点&#xff0c;遍历节点&#xff0c;行为节点 3.行为节点 行为执行的时候有互斥关系&#xff0c;加入一个queue&#xff0c;最后执行 4.NodeCanvas的参数传参可以由上个节点传到下个节…...

(* IOB = “FORCE“ *) 的使用分享

在Xilinx FPGA设计中&#xff0c;IOBFORCE是一个与输入输出块&#xff08;IOB&#xff09;相关的属性设置。这个设置主要用于控制逻辑是否被推入到IOB&#xff08;Input/Output Block&#xff09;中&#xff0c;即FPGA芯片边缘的I/O引脚附近的专用硬件资源。使用IOB属性可以帮助…...

【大语言模型_7】利用ragas框架评测rag系统指标

一、介绍 ragas是一个用来评估RAG系统的框架&#xff0c;允许不在依赖人工注释的情况下&#xff0c;通过一套指标评估检索模块和生成模块的性能及其质量。 二、准备 数据准备&#xff1a;需要准备评估数据集&#xff0c;数据集格式如下 [{"question": "安全智…...

adb常用的命令

1. 查看adb版本 adb version 2. 将apk安装包安装到手机/模拟器上 adb install apk路径 3. 获取apk包名和界面名 包名&#xff08;package&#xff09;&#xff1a;决定程序的唯一性 界面名&#xff08;activity&#xff09;&#xff1a;一个界面界面名&#xff0c;对应一个界面…...

手动集成sqlite的方法

注意到sqlite有backup方法&#xff08;https://www.sqlite.org/backup.html&#xff09;。 也注意到android中sysroot下&#xff0c;没有sqlite3的库&#xff0c;也没有相关头文件。 如果要使用 sqlite 的backup&#xff0c;那么就需要手动集成sqlite代码到项目中。可以如下操…...

自然语言处理(NLP)技术

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;是一种模拟人类智能思维过程的技术&#xff0c;它在现代科技中的应用非常广泛&#xff0c;涉及诸多领域&#xff0c;如自然语言处理、计算机视觉、机器学习、数据分析等。以下是人工智能在现代科技中的应…...

【GPT入门】第25课 掌握 LangChain:链式调用的奥秘、特性与使用示例

【GPT入门】第25课 掌握 LangChain&#xff1a;链式调用的奥秘、特性与使用示例 语法解释各部分性质链式调用的性质调用方式注意事项 语法解释 你给出的代码 is_duplicated_chain (check_duplicated | model | parser) 运用了 LangChain 里的链式调用语法。在 LangChain 中&a…...

机器学习之DBSCAN算法详解

文章目录 引言1. DBSCAN算法概述2.DBSCAN算法的基本概念2.1 ε-邻域2.2 核心点&#xff08;Core Point&#xff09;2.3 边界点&#xff08;Border Point&#xff09;2.4 噪声点&#xff08;Noise Point&#xff09;2.5 直接密度可达&#xff08;Directly Density-Reachable&…...

借助vite来优化前端性能

Vite 是一个现代化的前端构建工具&#xff0c;凭借其基于原生 ES 模块的开发服务器和高效的构建能力&#xff0c;可以显著优化前端性能。 一、开发环境优化 1.快速启动与热更新 Vite 利用浏览器对 ES 模块的原生支持&#xff0c;在开发环境中无需打包&#xff0c;直接按需加载…...

[工控机安全] 使用DriverView快速排查不可信第三方驱动(附详细图文教程)

导语&#xff1a; 在工业控制领域&#xff0c;设备驱动程序的安全性至关重要。第三方驱动可能存在兼容性问题、安全漏洞甚至恶意代码&#xff0c;威胁设备稳定运行。本文将手把手教你使用 DriverView工具&#xff0c;高效完成工控机驱动安全检查&#xff0c;精准识别可疑驱动&a…...

Execution failed for task ‘:path_provider_android:compileDebugJavaWithJavac‘.

What went wrong: Execution failed for task ‘:path_provider_android:compileDebugJavaWithJavac’. Could not resolve all files for configuration ‘:path_provider_android:androidJdkImage’. Failed to transform core-for-system-modules.jar to match attributes {…...

基于SpringBoot的社区/物业管理系统

项目介绍 平台采用B/S结构&#xff0c;后端采用主流的SpringBoot语言进行开发&#xff0c;前端采用主流的Vue.js进行开发。是一个综合的社区/物业管理系统。 整个平台包括前台和后台两个部分。 - 前台功能包括&#xff1a;小区信息、社区论坛、社区公告、社区留言板、个人中心。…...

vmware下linux无法上网解决方法

首先&#xff0c;打开打开"编辑" “虚拟网络编辑器”,并将"桥接"方式的网卡选择为主机上网的网卡。 虚拟机中&#xff0c;设置IP地址为主机网卡同样子网下的ip地址&#xff1a; 并且要选择桥接模式&#xff01;注意如下图&#xff0c;"复制物理连接状…...

【数据库备份】docker中数据库备份脚本——MySql备份脚本

docker中数据库备份脚本——MySql备份脚本 #!/bin/bash# MySQL数据库信息 DB_USER"root" DB_PASSWORD"你的密码"# 备份保存主目录 BACKUP_ROOT"/data/data_backup/mysql"# 最多保留的备份日期文件夹数 MAX_DATE_FOLDERS15# 数组包含要备份的数据…...