GreenDAO 学习笔记
来源:互联网 发布:excel应用数据透视表 编辑:程序博客网 时间:2024/05/17 06:17
greenDAO
greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。它的本质就是提供一个面向对象的接口,使得开发者更加方便地将数据存储到数据库SQLite之中。我们只需要定义数据模型,greenDAO就会为我们生成实体类以及DAOs(data access objects),(在3.0之后不需要我们编写generator而是编写实体类并添加注解,greenDAO会为我们生成schema,以及DAOs)从而避免了开发者编写较多枯燥的数据存储和加载的代码。此外greenD还提供了一些高级功能,比如对话缓存(session cache),eager loading, active entities(这些还不懂)。
在greenDa中,默认会为每一个实体类建立一张数据表,实体类的每一个属性对应数据表中的一列。
本篇文章算是对greenDao官方文档的一个简单翻译,还是有很多不懂之处,写在这里仅供自己以后使用中做一个参考,其中掺杂一点自己的理解,可能会有不当之处,有错误之处请批评指正!
1. 基本使用方法
1. 配置
添加依赖:
buildscript { repositories { mavenCentral() } dependencies { classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0' }}apply plugin: 'org.greenrobot.greendao'dependencies { compile 'org.greenrobot:greendao:3.0.1'}
2. Entities
从3.0版本开始,greenDao不再需要编写Generator类,而是需要开发者编写带有注解的Entities实体类,该实体类就是需要持久化存储在数据库中的类。简单实例如下:
@Entitypublic class User { @Id private Long id; private String name; @Generated(hash = 873297011) public User(Long id, String name) { this.id = id; this.name = name; } @Generated(hash = 586692638) public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }}
@Entity注解可以表明该类是需要持久化的类,build->Make Project之后,会在build/generated/source/greendao下生成DAOMaster,DAOSession,实体类以及对应的DAO classes,在此例中生成DaoMaster, DaoSession, 和UserDao,使用这三个类就可以执行创建数据库,以及对数据库执行关于实体类的增删改查。
3. 数据库操作
public class MainActivity extends AppCompatActivity { UserDao userDao; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "recluse-db", null); SQLiteDatabase db = helper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); DaoSession daoSession = daoMaster.newSession(); userDao = daoSession.getUserDao(); User user = new User((long)1, "recluse"); userDao.insert(user); userDao.update(user); userDao.deleteByKey((long)1); userDao.delete(user); }}
如上例所示,通过DaoMaster, DaoSession, UserDao,就可以对数据进行增删改查操作了,其中查询是使用较多的操作,greenDAO中专门提供了Query和QueryBuilder类,便于查询的操作和优化。后面对此进行详细介绍。以上就是greenDao的基本使用步骤,就可以实现实体对象在数据库的持久化存储了。
2. 详细介绍
1. 实体类的注解
在Java代码中使用实体类代表我们需要持久化的数据。之前使用greenDAO是由开发者编写generator,定义schema并添加实体类的名称以及属性,由greenD为我们生成实体类。而在3.0以后是由我们编写实体类,并添加注解,由greenDAO识别schema并生成相应的DAOs。以下为注解介绍。
- @Entity,该实例是类的注解,告诉greenDAO这个类是需要持久化的实体类。此外它还可以配置一些参数,如下:
@Entity( // If you want to have more than one schema, you can tell greenDAO // to which schema an entity belongs (pick any string as a name). schema = "myschema", // Flag to make an entity "active": Active entities have update, // delete, and refresh methods. active = true, // Specifies the name of the table in the database. // By default, the name is based on the entities class name. nameInDb = "AWESOME_USERS", // Define indexes spanning multiple columns here. indexes = { @Index(value = "name DESC", unique = true) }, // Flag if the DAO should create the database table (default is true). // Set this to false, if you have multiple entities mapping to one table, // or the table creation is done outside of greenDAO. createInDb = false)public class User { ...}
- @ID, field注解, 表示选择一个long或Long类型的属性作为该实体所对应数据库中数据表的主键,参数可以设置(autoincreament=true)
- @Property field 注解,可以自定义该属性在数据表的列名,默认的列名为属性名大写,并由下划线隔开多个单词,@Property(nameInDb=”XXXX”)可以自定义列名。
- @NotNull对应着数据表中该列不能为空。
- @Transient 与Java中的Transient关键字类似,指不对该属性持久化,即不为该属性在数据表中创建相应列。
- @Index 为属性创建索引,有name和unique两个参数,name可以为索引指定名称,unique与数据表中建立索引的unique含义一致
- @Unique 含义与数据表中列的unique一致, 这种情况下,SQLite会自动为该列建立索引。
- @Generated greenDAO会根据开发者定义的实体类定义schema,并生成DAOs,而在生成过程中会为开发者编写的实体类的一些方法和域上添加@Generated注解,该注解只是提示开发者,避免对此域或方法代码的修改,如果想手动对其修改则需要改成@Keep注解,但是会影响后面的持久化过程。最好不要这样做。
此外还有@ToOne, @ToMany, @JoinEntity与多个数据表的关系有关,后面再对此详细介绍。
- @Generated greenDAO会根据开发者定义的实体类定义schema,并生成DAOs,而在生成过程中会为开发者编写的实体类的一些方法和域上添加@Generated注解,该注解只是提示开发者,避免对此域或方法代码的修改,如果想手动对其修改则需要改成@Keep注解,但是会影响后面的持久化过程。最好不要这样做。
2. Sessions
greenDAO为我们生成的类中包括DaoMaster,DaoSession和一些实体对应的DAOs,而前两个是greenDAO的机制中核心的接口。
首先每一个DaoMaster持有一个数据库连接,通过DaoMaster#newSession()方法可以实例化多个Session,这些Session对应同一个数据库连接,但是系统会为每一个Session分配内存,在这片内存中会为实体进行缓存。每一个Session对应一个Identity scope(这个名称空间不太懂,官方文档中提供了一个Hibernate中介绍Session和Identity Scope概念的链接)。
从DaoSession中可以获取各实体类的对应DAO,然后就可以进行增删改查的操作了,对于每一个Session中的查询操作都会对查到的实体类做缓存操作,所以对应同一个Session的多次查询操作,如果entity的对象在该Session中有对应缓存则直接使用,而不再从数据库中读取数据并构建新的实体类对象。
3. 查询
数据库的查询是最为频繁的操作,greenDAO对查询提供了更为方面的操作API和优化,此外greenDAO也支持纯SQL语句。但是最好使用greenDAO提供的QueryBuilder API,使用更为简单方面而且支持惰性加载。
QueryBuilder的基本使用方法
通过DAOs的queryBuilder()方法构建一个构建器对象,然后对该构建器设置查询,排序条件等,最后获取查询结果。如官方文档给出的实例:
QueryBuilder qb = userDao.queryBuilder(); //获取QueryBuilderqb.where(Properties.FirstName.eq("Joe")) //设置查询条件.orderAsc(Properties.LastName) //设置排序.list(); //返回查询结果
- 查询条件
where()方法中可以添加多个查询条件,即WhereCondition对象,多个条件取与的关系,也可以使用whereOr()方法,多个条件取或的关系,不过该方法至少有两个条件才可以。
另外qb对象还有or(), and()等方法连接多个条件,返回类型是WhereCondition, 从而可以组合出我们想要的查询条件。
这里注意Properties是userDao的一个静态内部类,负责持有各个属性,每个属性都是一个Property类型,该类型的对象都有like(), in()等操作方法,这部分在后面介绍生成的DAO部分再做介绍。
如下官方文档示例:
QueryBuilder qb = userDao.queryBuilder();qb.where(Properties.FirstName.eq("Joe"),qb.or(Properties.YearOfBirth.gt(1970),qb.and(Properties.YearOfBirth.eq(1970), Properties.MonthOfBirth.ge(10))));List youngJoes = qb.list();
返回以下条件的结果:First name is “Joe” AND (year of birth is greater than 1970 OR (year of birth is 1970 AND month of birth is equal to or greater than 10 (October).
- 限制条件
在有些情况下,可能只需要满足查询条件的一部分结果,这时候where()方法就无能为力了,greenDAO提供了limit(int)和offset(int)方法,设置返回结果的数量以及在所有结果中的偏移,返回结果时只构建这一部分数据对应的实体类对象,可以用于只返回满足条件的某一部分结果,从而节省内存。
- 返回查询结果
greenDAO提供Query类用于返回查询结果,通过QueryBuilder#build()方法返回这一次查询的Query对象,Query对象可以返回单个结果或者所有结果。
返回单个结果:使用unique()或uniqueOrThrow(),返回单个结果,如果没有满足条件的结果,前者返回null, 后者抛出异常
返回所有结果:使用list(): 在内存中构建所有满足条件的实体类对象,通常保存到ArrayList中(with no magic involved 不明白什么意思);listLazy(): 当其中的一个实体类对象使用时才加载,会缓存下来,之后可以继续使用;listLazyUncached():即不缓存的惰性加载;listIterator():也是惰性加载,返回一个迭代器,可以方便地遍历查询结果,没有缓存。
返回所有结果的后面三个方法都是用了greenDao的LazyList类,它是持有数据库的一个Cursor对象,所以在使用完以后需要关闭,释放空间。listLazy()和listIterator()方法返回的结果被遍历完,即所有结果都使用过以后会自动关闭cursor,但是如果有中途就结束的可能这应该由开发者负责关闭。
除此以外,如果不需要多次查询的情况下,可以直接通过queryBuilder调用这些返回结果的方法,无需Query对象,其实内部也是通过Query实现的,只不过节俭了代码量。
- 多次查询
返回的Query对象在没有改变查询条件的情况下可以多次查询,避免了多次构建Query对象,如果更查询条件可以调用setParameter()方法,如下官方实例:
- 多次查询
Query query = userDao.queryBuilder().where(Properties.FirstName.eq("Joe"), Properties.YearOfBirth.eq(1970)).build();List joesOf1970 = query.list();
这里想要更改这两个条件
query.setParameter(0, "Maria");query.setParameter(1, 1977);List mariasOf1977 = query.list();
即通过条件的序号更改查询条件。
- 多线程查询
如果在多线程中做查询操作,需要调用Query的forCurrentThread()返回Thread Local的Query对象。通过该对象可以执行线程安全的查询,无需锁(使用锁可能会导致死锁的可能,因为事务的原因)。在该线程中可以修改该查询对象的条件,而其他线程如果想要修改该查询对象则会抛出异常,保证了该Query对象在该线程中的安全性。
- 纯SQL查询
greenDAO支持SQL查询,可以使用where语句构建StringCondition,也可以执行queryRaw()或queryRawCreate(),如下官方实例:
Query query = userDao.queryBuilder().where(new StringCondition("_ID IN " +"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)")).build();
这个由字符串构建条件语句
Query query = userDao.queryRawCreate( ", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");
SQL语句的字符串为SELECT和Entities的列名之后的SQL语句
4. 连接
通常数据查询都需要多个数据表,也就是需要数据表的连接。首先看一个官方的实例,该实例可以查询住在某个位置的所有用户:
QueryBuilder<User> queryBuilder = userDao.queryBuilder();queryBuilder.join(Address.class, AddressDao.Properties.userId) .where(AddressDao.Properties.Street.eq("Sesame Street"));List<User> users = queryBuilder.list();
QueryBuilder Join API
由于可以默认连接某个数据表的主键,所以可以省略连接源或目的的属性,因此join()方法有如下三种重载形式:
/*** Expands the query to another entity type by using a JOIN. The primary key property of the primary entity for* this QueryBuilder is used to match the given destinationProperty.*/public <J> Join<T, J> join(Class<J> destinationEntityClass, Property destinationProperty)/** * Expands the query to another entity type by using a JOIN. The given sourceProperty is used to match the primary * key property of the given destinationEntity. */public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass)/** * Expands the query to another entity type by using a JOIN. The given sourceProperty is used to match the given * destinationProperty of the given destinationEntity. */public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass, Property destinationProperty)
链式连接
/** * Expands the query to another entity type by using a JOIN. The given sourceJoin's property is used to match the * given destinationProperty of the given destinationEntity. Note that destination entity of the given join is used * as the source for the new join to add. In this way, it is possible to compose complex "join of joins" across * several entities if required. */public <J> Join<T, J> join(Join<?, T> sourceJoin, Property sourceProperty, Class<J> destinationEntityClass, Property destinationProperty)
以下是官方实例:涉及三个实例City, Country, and Continent.查询欧洲的所有人数超过一百万的所有城市:
QueryBuilder qb = cityDao.queryBuilder().where(Properties.Population.ge(1000000));Join country = qb.join(Properties.CountryId, Country.class);Join continent = qb.join(country, CountryDao.Properties.ContinentId, Continent.class, ContinentDao.Properties.Id);continent.where(ContinentDao.Properties.Name.eq("Europe"));List<City> bigEuropeanCities = qb.list();
自连接
官方实例,查找所有人当中爷爷的名字为:”Lincoln”的
QueryBuilder qb = personDao.queryBuilder();Join father = qb.join(Person.class, Properties.FatherId);Join grandfather = qb.join(father, Properties.FatherId, Person.class, Properties.Id);grandfather.where(Properties.Name.eq("Lincoln"));List<Person> lincolnDescendants = qb.list();
5. Relations
在建立数据库时,每一个实体类会建立一张数据表,代表一个关系,而不同实体之间必然存在一定的关系,反映到数据表上也需要建立关系。
比如一个用户的账户都有对应的头像picture,且假设每个用户的头像Picture对象都不同,因此每个picture也对应一个用户,这就是一对一的关系,而在网上购物可以下订单,每个用户可以有多个订单Oder,而每个Oder都对应一个用户User,这是一对多的关系。在数据表的关系中可以通过外键表示这种一对一和一对多的关系。除此以外还有多对多的关系,在对象的结构中每个Oder包含多个物品Item,而每个Item也可能包含在多个Oder中,因此这是多对多的关系,在数据表中需要额外的表表示对应关系,如Oder_Item表,表示一个Oder与一个Item具有关系。
在greenDao中,使用@ToOne表示一对一的关系,使用@ToMany表示一对多的关系,而多对多的关系支持度不够好,目前还不完善,使用较为复杂。
- @ToOne
比如每个用户都有一个对应Picture属性,需要一个pictureId代表这个Picture属性,通过@ToOne(joinProperty = “XXXX”)指定pictureId,在数据表中则会有pictureId这一列作为外键,与Picture数据表建立联系,如果你没有指定pictureId, greenDAO也会在数据表中生成一列属性其作用与指定的pictureId相同,而实体类中则可以使用User的Picture属性,代码如下:
- @ToOne
@Entitypublic class User { @Id private Long id; private String name; private Long pictureId; @ToOne(joinProperty = "pictureId") private Picture picture; ....}
当然greenDAO会为我们生成其他的代码,如构造器,getter和setter等,在代码中可以直接使用user.getPicture()和setPicture,而在数据表中则是pictureId是指向Picture数据表的外键。Picture的实体类如下:
@Entitypublic class Picture { @Id private long pictureId; ...}
- @ToMany
一对多的关系,定义了一个实体对象对应着多个实体对象,比如一个用户对应多个Order, 在建立数据表示会在目标实体(即一对多的那个多的实体类)的数据表中建立外键,指向源实体类(一对多中的一那个实体类)的数据表。目标数据表中的外键属性由@ToMany(referencedJoinProperty = “XXXX”)指定。
- @ToMany
如User中添加List orders属性:
@Entitypublic class User { @Id private Long id; private String name; private Long pictureId; @ToOne(joinProperty = "pictureId") private Picture picture; @ToOne private Picture thumbnailPicture; @ToMany(referencedJoinProperty = "ownerId") private List<Order> oders; ...}
Order的实例代码为:
@Entitypublic class Order { @Id private long id; private long ownerId; ...}
在build->Make Project之后会生成对应代码,此时在代码中可以使用User#getOrders()获取user的所有对应的order的List. 在这种情况下, ownerId是在Order中的外键,指向User的主键Id。
另外,对于较为复杂的关系可以使用joinProperties属性,其值可以设置一系列的@JoinProperty(name = “XXXX”, referencedName = “YYYY”),可以建立目标实体类中YYYY属性指向源实体类XXXX的属性,其中YYYY为非空,XXXX为unique的,不一定是主键,这样的关系可以有多个。 如下为官方实例:
@Entitypublic class User { @Id private Long id; @Unique private String authorTag; @ToMany(joinProperties = { @JoinProperty(name = "authorTag", referencedName = "ownerTag") }) private List<Site> ownedSites;}@Entitypublic class Site { @Id private Long id; @NotNull private String ownerTag;}
3. 生成代码的解析
3.0以后实体类需要开发者编写并使用注解,greenDAO会根据注解解析schema,并生成一系列的类,主要包括DAOMaster,DAOSession以及相应的DAOs
1. DAOMaster
/** * Master of DAO (schema version 1): knows all DAOs. */public class DaoMaster extends AbstractDaoMaster { public static final int SCHEMA_VERSION = 1; /** Creates underlying database table using DAOs. */ public static void createAllTables(Database db, boolean ifNotExists) { OrderDao.createTable(db, ifNotExists); PictureDao.createTable(db, ifNotExists); UserDao.createTable(db, ifNotExists); } /** Drops underlying database table using DAOs. */ public static void dropAllTables(Database db, boolean ifExists) { OrderDao.dropTable(db, ifExists); PictureDao.dropTable(db, ifExists); UserDao.dropTable(db, ifExists); } /** * WARNING: Drops all table on Upgrade! Use only during development. * Convenience method using a {@link DevOpenHelper}. */ public static DaoSession newDevSession(Context context, String name) { Database db = new DevOpenHelper(context, name).getWritableDb(); DaoMaster daoMaster = new DaoMaster(db); return daoMaster.newSession(); } public DaoMaster(SQLiteDatabase db) { this(new StandardDatabase(db)); } public DaoMaster(Database db) { super(db, SCHEMA_VERSION); registerDaoClass(OrderDao.class); registerDaoClass(PictureDao.class); registerDaoClass(UserDao.class); } public DaoSession newSession() { return new DaoSession(db, IdentityScopeType.Session, daoConfigMap); } public DaoSession newSession(IdentityScopeType type) { return new DaoSession(db, type, daoConfigMap); } /** * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} - */ public static abstract class OpenHelper extends DatabaseOpenHelper { public OpenHelper(Context context, String name) { super(context, name, SCHEMA_VERSION); } public OpenHelper(Context context, String name, CursorFactory factory) { super(context, name, factory, SCHEMA_VERSION); } @Override public void onCreate(Database db) { Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION); createAllTables(db, false); } } /** WARNING: Drops all table on Upgrade! Use only during development. */ public static class DevOpenHelper extends OpenHelper { public DevOpenHelper(Context context, String name) { super(context, name); } public DevOpenHelper(Context context, String name, CursorFactory factory) { super(context, name, factory); } @Override public void onUpgrade(Database db, int oldVersion, int newVersion) { Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); dropAllTables(db, true); onCreate(db); } }}
首先看最后的两个静态内部类,在Android系统中为了便于对SQLiteDatabase做数据库操作提供了SQLiteOpenHelper, 这个帮助类是一个抽象方法,有两个抽象方法分别是onCreate()和onUpdate()分别在数据库建立和升级的时候回调。而在DAOMaster中添加了OpenHelper继承了DatabaseOpenHelper(DatabaseOpenHelper继承了SQLiteOpenHelper, 并保留了那两个抽象方法),并实现了onCreate()方法,并在该方法中调用createAllTables(db, false)创建所有的数据表。而DevOpenHelper则继续继承OpenHelper并实现onUpdate()方法,在方法中删除所有数据表并重新创建。createAllTables和dropAllTables方法比较简单,只是简单地调动DAOs的数据表创建和删除方法。
然后是DAOMaster的构造器,在构造器中注册数据表的实体类, AbstractDAOMaster#registerDaoClass()的代码如下:
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) { DaoConfig daoConfig = new DaoConfig(db, daoClass); daoConfigMap.put(daoClass, daoConfig); }
就是创建DaoConfig对象,注册一个实体类,并添加到DAOMaster的daoConfigMap对象中,以Map的形式保存信息,表示该实体类需要持久化,为其创建数据表。
接着就是两个重要方法newSession()的两个重载方法,每一个DaoMaster对象代表一个数据库的连接,而返回一个新的Session就代表一个会话,通过同一个会话中的DAOs进行的数据库操作,greenDao会对其优化,如查询操作会对结果缓存,避免每次都要从数据库中国读取数据并多次建立对象。
最后是一个静态方法newSession()是一个便捷方法,在该方法中可以看出就是新建一个数据库,并实例化一个master并返回一个new Session。与在基本使用方法中介绍的步骤完全一致,只不过这样一个静态方法集成了这几个步骤,使用更为方便而已。
2. DaoSession
public class DaoSession extends AbstractDaoSession { private final DaoConfig orderDaoConfig; private final DaoConfig pictureDaoConfig; private final DaoConfig userDaoConfig; private final OrderDao orderDao; private final PictureDao pictureDao; private final UserDao userDao; public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap) { super(db); orderDaoConfig = daoConfigMap.get(OrderDao.class).clone(); orderDaoConfig.initIdentityScope(type); pictureDaoConfig = daoConfigMap.get(PictureDao.class).clone(); pictureDaoConfig.initIdentityScope(type); userDaoConfig = daoConfigMap.get(UserDao.class).clone(); userDaoConfig.initIdentityScope(type); orderDao = new OrderDao(orderDaoConfig, this); pictureDao = new PictureDao(pictureDaoConfig, this); userDao = new UserDao(userDaoConfig, this); registerDao(Order.class, orderDao); registerDao(Picture.class, pictureDao); registerDao(User.class, userDao); } public void clear() { orderDaoConfig.getIdentityScope().clear(); pictureDaoConfig.getIdentityScope().clear(); userDaoConfig.getIdentityScope().clear(); } public OrderDao getOrderDao() { return orderDao; } public PictureDao getPictureDao() { return pictureDao; } public UserDao getUserDao() { return userDao; }}
DaoSession主要工作就是提供DAOs用于数据库操作,首先看构造器,每个DaoSession会从DaoMaster中克隆所有实体类的DaoConfig,并初始化Identity Scope(这个概念目前还不懂), 而且会在构造器中初始化各个实体类的Dao class,并注册Dao class。AbstractDaoSession#registerDao()的代码如下:
protected <T> void registerDao(Class<T> entityClass, AbstractDao<T, ?> dao) { entityToDao.put(entityClass, dao); }
entityToDao是一个DaoSession的一个Map属性,用于保存实体类与Dao的对应关系。
之后会有clear()方法,清空所有的Identity Scope,最后是所有实体类对应的Dao的getter方法。
DAOs
最后是各个实体类对应的Dao classes, 这些DAOs的逻辑较为复杂,通过他们可以实现数据库的增删改查操作。后续再对其做深入研究。
- GreenDao 学习笔记 1
- GreenDao 学习笔记 1
- GreenDao 学习笔记 2
- GreenDao 学习笔记 3
- GreenDao 学习笔记 4
- GreenDao 学习笔记 5
- GreenDao 学习笔记 6
- GreenDao 学习笔记 7
- greenDAO学习笔记
- GreenDAO 学习笔记
- GreenDao学习笔记
- GreenDao学习笔记
- android开发 greendao学习及使用笔记
- Android数据库框架greenDao学习笔记 2
- GreenDao笔记
- greenDao学习
- GreenDao 学习
- GreenDao学习
- Windows下利用MSSQL提权
- 关于JAVA的一些小知识
- python︱模块加载(pip安装)以及pycharm安装与报错解决方式
- 浅谈ZigBee消息机制
- 1.3 选择适合的Arduino
- GreenDAO 学习笔记
- 用于打开该连接的登录数据包的结构无效;该连接已关闭。 请与客户端库的供应商联系。
- Android基础知识
- PAT-A 1078. Hashing
- Mat, IplImage, CvMat, Cvarr关系及元素获取
- FastJson 常用序列化操作调用json上的静态方法
- linux常用命令一------文件目录
- 【POJ 1611 The Suspects】
- java8(中)