greenDao:操作数据库的开源框架
greenDAO: Android ORM for your SQLite database
1. greenDao库获取
英文标题借鉴的是greendrobot官网介绍greenDao时给出的Title,链接:http://greenrobot.org/greendao/,有兴趣的可以点进去(不用外网),里面有使用说明及相关资料的下载接口。greenDao目前版本已经更新到3.x.x,与2.x.x相比支持的功能发生了很大的改变(后面会提到),所以强烈推荐使用最新的版本。如3.1.1版本的获取方法如下:
a. 通过链接进入官网,点击右边的greenDao按钮进入其GitHub页面:
b. greenDao GitHub页面上的“Add greenDAO to your project”模块是针对Android Studio开发者的引用库添加说明,在app和project build.gradle文件中分别编写以下代码并进行同步(Studio界面上有一个同步按钮,不会的自己查)就可以使用greenDao提供的方法来操作SQLite数据库了:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.1.0'
}
}
//上面是在project的build.gradle文件中,下面是在app的build.gradle文件中,具体见后面实例
apply plugin: 'org.greenrobot.greendao' dependencies {
compile 'org.greenrobot:greendao:3.1.0'
}
当然,人家也说了“Please ensure that you are using the latest versions by checking here and here”,即点进去瞧瞧版本更新到多少了,条件允许的话尽量用最新的。目前这两个链接分别对应版本3.1.1和3.1.0,如果在Studio中想用3.1,1,就将上述代码中的3.1.0改为3.1.1。
c. 以版本3.1.1为例,点击checking here后会进入其资源下载页面(包括Eclipse开发者喜爱的jar包文件):
如果使用的集成开发环境是Eclipse,那么会与Studio不同,在导入库方面还是较原始(需要自行下载jar包并添加到项目中,有些jar包难找的时候体会最深),点击图中的jar按钮即可开始下载。
d. 想偷懒的可以直接点击链接进行jar包的获取:http://files.cnblogs.com/files/tgyf/greendao-3.1.1.rar,下载后不用解压,直接将后缀改为“.jar”即可。
e. 注意,还需要用同样的方法下载jar包freemarker和greendao-generator,同样在上面的下载页面搜索就好:
http://files.cnblogs.com/files/tgyf/freemarker-1.19.2.rar,
http://files.cnblogs.com/files/tgyf/greendao-generator-3.1.0.rar,下载后处理方式如上面红色字体描述——改后缀名"rar"->"jar"。
这三个jar包的作用分别是:
greendao——SQLite数据库操作核心,对一些常用的方法进行了封装;
generator——根据需求表对应实体类生成相关的greendao类,如上面提到的DaoSession等;
freemarker——将自动生成的类以文本形式输出;
至于在开发中用哪个版本合适,视实际情况而定。能用Google亲生的Studio最好不过了,不用下载jar包,还有人家对eclipse已经不进行新特性的支持了。
2. greenDao操作SQLite数据库
前面提到,新版本与老版本在支持的功能上有了很大的提升,最明显的地方就是gen目录下文件的生成时机或者说是方法。有老版本使用经验的小伙伴应该清楚,要想在android中利用greenDao相关类来处理SQLite数据库,必须先在另一个Java工程中建立需求表的实体类,生成gen目录下的文件(如xxxDao、DaoSession以及DaoMaster等),然后将gen目录拷入android工程中方能使用。当然,也可以将gen目录的生成路径直接定位到android工程中,免得每次修改实体类并重新编译后都需要拷贝这些文件。
不过,这些问题在新版中就不存在了,因为实体类的定义与gen目录的生成都可以直接在android工程中完成。下面就来看看Studio中具体是怎么回事吧,至于对旧版本的用法感兴趣的自己去研究咯。
2.1 在Studio中新建一个project greendaoTest,过程中根据需求设置各种属性,默认布局为显示一个字串“Hello World!”。其实如果只是对greenDao框架进行学习与测试,可以利用数据库查询工具或log打印内容来检测操作结果,不是必须将内容显示在布局组件中。
2.2 在项目中添加库依赖代码,完整文件代码分别如下:
project build.gradle:
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'org.greenrobot:greendao-gradle-plugin:3.1.1' //greendao // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
} allprojects {
repositories {
jcenter()
}
} task clean(type: Delete) {
delete rootProject.buildDir
}
app build.gradle文件:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' //greendao android {
compileSdkVersion 24
buildToolsVersion "24.0.0" greendao{ //greendao
schemaVersion 1
targetGenDir 'src/main/java'
}
defaultConfig {
applicationId "com.learn.greendaotest"
minSdkVersion 22
targetSdkVersion 24
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.0.0'
compile 'org.greenrobot:greendao:3.1.1' //greendao
}
和greenDao相关的语句,末尾都加了注释“greenDao”,方便区分与维护。可以看出,这里导入的是最新的版本3.1.1。
2.3 在与主类文件MainActivity.java同一目录下定义表——实体类Student,代码如下:
package com.learn.greendaotest; import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Property; @Entity
public class Student {
@Id
private Long id;
@Property(nameInDb = "NAME")
private String name;
@Property(nameInDb = "AGE")
private int age;
}
简单起见,声明了三个属性:id、name、age,分别表示学生的序号、名字、年龄,其中序号是唯一的。当然,如果需要,可以设置序号的顺序、属性的非空等限制条件。定义好实体类之后,只需要运行程序(或者点击build菜单中的make project/make module 'app'选项)就可以生成相关的greenDao操作类了。
首先,仍旧看Student这个类,发现多了以下代码:
@Generated(hash = 352757281)
public Student(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Generated(hash = 1556870573)
public Student() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
代码并不陌生,带参数与不带参数的构造函数,以及三个属性的set/get方法,但是这些是自动生成的(包括接下来要讲的三个类,会贴出部分关键代码,所有的大家可以下载文末提供的源码)。
表操作类StudentDao,可以说是greenDao生成的和Student类最亲近的一个类了,关键的地方有以下几个:
a. 内部类——属性类Properties,关于属性Property,greenDao还提供了一系列好用的方法,后面会提到。
public static class Properties {
public final static Property Id = new Property(0, Long.class, "id", true, "_id");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
public final static Property Age = new Property(2, int.class, "age", false, "AGE");
}
b. 表创建与删除函数——createTable(Database db, boolean ifNotExists)和dropTable(Database db, boolean ifExists),操作时是否做表存在判断可以通过传入参数“ifExists”来决定。由于调用对象就是表对应的类本身,所以指定表所属数据库对象就好(不用关心表名是什么)。
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
"\"_id\" INTEGER PRIMARY KEY ," + // 0: id
"\"NAME\" TEXT," + // 1: name
"\"AGE\" INTEGER NOT NULL );"); // 2: age
} /** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"STUDENT\"";
db.execSQL(sql);
}
可以看出,自动生成的表创建代码将属性id、name、age类型设置为了数据库中的类型INTEGER、TEXT、INTEGER,id唯一PRIMARY KEY,age NOT NULL,而大写的名字是我们在定义Student类时自己声明的,如name:@Property(nameInDb = "NAME")。
c. 实体对象获取方法,提供了两种方式:返回临时引用和直接给引用参数赋值,而后者对属性操作调用的是Student类中的set(Object object)方法。
@Override
public Student readEntity(Cursor cursor, int offset) {
Student entity = new Student( //
cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id
cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // name
cursor.getInt(offset + 2) // age
);
return entity;
} @Override
public void readEntity(Cursor cursor, Student entity, int offset) {
entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0));
entity.setName(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1));
entity.setAge(cursor.getInt(offset + 2));
}
类DaoSession,简单点说就是用来获取类StudentDao实例的,留意其构造函数和实例返回方法即可。
public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {
super(db); studentDaoConfig = daoConfigMap.get(StudentDao.class).clone();
studentDaoConfig.initIdentityScope(type); studentDao = new StudentDao(studentDaoConfig, this); registerDao(Student.class, studentDao);
} public StudentDao getStudentDao() {
return studentDao;
}
类DaoMaster,是和org.greenrobot.greendao.database.DatabaseOpenHelper最亲近的类,那么到这里自定义的实体类Student和greenDao封装的数据库处理类终于联系上了。该类中又定义了两个静态内部类OpenHelper和DevOpenHelper,前者重载了类DatabaseOpenHelper的表建立方法onCreate(Database db),后者重载了表更新方法onUpgrade(Database db, int oldVersion, int newVersion)。
/**
* 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);
}
}
还定义了删除所有表的方法dropAllTables(Database db, boolean ifExists),由于我们之前只定义了一个实体类(表),所以这里只有StudentDao类删除表的语句。
/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
StudentDao.dropTable(db, ifExists);
}
创建表也是如此:
/** Creates underlying database table using DAOs. */
public static void createAllTables(Database db, boolean ifNotExists) {
StudentDao.createTable(db, ifNotExists);
}
以及和DaoSession类获取StudentSao实例相似的newDevSession(Context context, String name)方法,用以获取DaoSession类实例。
public static DaoSession newDevSession(Context context, String name) {
Database db = new DevOpenHelper(context, name).getWritableDb();
DaoMaster daoMaster = new DaoMaster(db);
return daoMaster.newSession();
}
而方法newSession()继而会调用DaoSession类的三参构造函数:
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
所以,不用担心获取DaoSession实例时该怎样传入参数,因为DaoMaster及其父类都已经完成了。如第三个参数daoConfigMap,在DaoMaster类中找不到,其父类AbstractDaoMaster完成了声明与初始化工作。
protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap; public AbstractDaoMaster(Database db, int schemaVersion) {
this.db = db;
this.schemaVersion = schemaVersion; daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
}
2.4 准备工作差不多了,可以开始操作数据库了。由于只是进行简单的测试,所以不涉及界面,直接通过log打印的形式来描述结果了。
2.4.1 建立数据库及实体类对应的表,第二个参数指定了数据库的名称,第三个参数(类型CursorFactory)一般为null即可。
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(getApplicationContext(), "student.db", null);
结合之前给出的DaoMaster类代码,这句代码其实做了很多事情。执行DevOpenHelper类构造函数DevOpenHelper(Context context, String name, CursorFactory factory)时会调用OpenHelper类构造函数OpenHelper(Context context, String name, CursorFactory factory),进而又会调用基类DatabaseOpenHelper构造函数DatabaseOpenHelper(Context context, String name, CursorFactory factory, int version)与被重载方法onCreate(Database db),最后调用DaoMaster类的createAllTables(Database db, boolean ifNotExists)建立所有表。
数据库与表格文件都建立好了,下面一步到位获取StudentDao类实例:
DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb());
DaoSession daoSession = daoMaster.newSession();
StudentDao studentDao = daoSession.getStudentDao();
2.4.2 插入两条数据,一般情况下第一个参数id是不需要特殊指定的(null即可),会自动从1开始增序填入表中。
Student studentLl = new Student(null, "Lilei", 21);
Student studentHmm = new Student(null, "Hanmeimei", 21);
studentDao.insert(studentLl);
studentDao.insert(studentHmm);
关于insert(T entity)方法,在AbstractDao类中的定义如下:
/**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement(), true);
}
再往下追踪能看得到不同层次的源码,这里不打算全部列一遍。关注一下该方法的最后一句注释:返回新插入实体在数据表中的行号,所以在对表中数据进行查询之前,可以通过打印返回值来判断插入是否成功。修改代码,添加返回值获取与打印:
long resultLl = studentDao.insert(studentLl);
long resultHmm = studentDao.insert(studentHmm);
showLog("row id of insert Lilei: "+resultLl);
showLog("row id of insert Hanmeimei: "+resultHmm);
打印出的结果符合预期,表明插入成功了,因为之前已经执行过一次(row id为1和2),所以这里往后递增为3和4。还可以看出id是从1开始的,而不像熟悉的数组下标是从0开始。
08-29 03:12:29.624 25326-25326/com.learn.greendaotest I/STUDENT: row id of insert Lilei: 3
08-29 03:12:29.624 25326-25326/com.learn.greendaotest I/STUDENT: row id of insert Hanmeimei: 4
showLog是什么鬼?自定义的一个用来打印log的方法,免得每次都需要传入TAG标记以及必须传入String类型参数值,适当的时候利用封装偷偷懒。
private final String TAG = "STUDENT";
private void showLog(Object message) {
Log.i(TAG, ""+message);
}
同样地,Toast提示语可以封装成:
private void showToast(Object message) {
Toast.makeText(MainActivity.this, ""+message, Toast.LENGTH_SHORT).show();
}
2.4.3 数据查询,并将结果打印出来,看看是否真的插入了四个实体值。
List<Student> listQuery = studentDao.queryBuilder().where(StudentDao.Properties.Id.between(1, 8)).limit(5).build().list();
for (Student student : listQuery) {
showLog("id: "+student.getId());
showLog("name: "+student.getName());
showLog("age: "+student.getAge());
}
08-29 03:36:28.418 13742-13742/com.learn.greendaotest I/STUDENT: id: 1
08-29 03:36:28.418 13742-13742/com.learn.greendaotest I/STUDENT: name: Lilei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 2
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 3
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Lilei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: id: 4
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:36:28.419 13742-13742/com.learn.greendaotest I/STUDENT: age: 21
解释一下where(WhereCondition condition)和limit(int limit)这两个方法,其实看代码也能明白大概了。
where():添加数据查询的筛选条件,除了这里的ID范围,还可以是字串包含或不包含某个字符/字串等;
limit():只将查询结果中的前五条赋给List对象,其余的忽略;
前面提到过,属性类Property有很多好用的方法。比如这里的between(Object value1, Object value2)限定值的区间,查看该类的定义还可以发现eq(Object value)方法是用来比较值是否相等,等等返回值为WhereCondition实例的方法,说它是为了where()方法而生也不为过。
2.4.4 删除数据,过程和查询类似,只不过将符合条件的实体值删除而已。
List<Student> listDelete = (List<Student>) studentDao.queryBuilder().where(StudentDao.Properties.Id.le(3)).build().list();
for (Student student : listDelete) {
studentDao.delete(student);
}
以上代码目的为删除id小于等于3的行,那么再次输出结果只剩下一条数据了:
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: id: 4
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: name: Hanmeimei
08-29 03:59:10.631 2257-2257/com.learn.greendaotest I/STUDENT: age: 21
2.4.5 更新数据,目前Student表中还剩下一条数据,那就将它的name字段给为Lilei吧。
Student stu = studentDao.queryBuilder()
.where(StudentDao.Properties.Id.ge(4), StudentDao.Properties.Name.like("%mei%")).build().unique();
if (stu == null) {
showToast("实体不存在!");
}else{
stu.setName("Lilei");
studentDao.update(stu);
}
注意,这里调用where()方法时传入了两个条件参数,其实还可以传更多。参数的意义分别为id大于等于4,name字段中间部分的值为“mei”。最后的unique()方法使结果唯一,即查询到一条马上停止查询过程并返回结果Student类实例,而不是List<Student>。如果没有找到则弹出Toast字串提醒用户,否则进行名字的修改。现在的查询结果就变成下面这样了:
08-29 04:08:48.788 10993-10993/? I/STUDENT: id: 4
08-29 04:08:48.788 10993-10993/? I/STUDENT: name: Lilei
08-29 04:08:48.788 10993-10993/? I/STUDENT: age: 21
2.4.6 升级数据库,涉及到的重载方法onUpgrade(Database db, int oldVersion, int newVersion)已经在类DaoMaster中实现了,接下来需要做的是改变数据库版本与实体类属性。
在app build.gradle文件将数据库版本值由1改为2:
greendao{ //greendao
schemaVersion 2
targetGenDir 'src/main/java'
}
在实体类Student中添加性别属性sex(String型),
@Id
private Long id;
@Property(nameInDb = "NAME")
private String name;
@Property(nameInDb = "AGE")
private int age;
@Property
private String sex;
接着执行程序即可。可以发现:性别属性没有指定其在StudentDao->Properties类中的别名(在数据表中的列名),而自动生成的属性别名为“SEX”。可知若没有显式声明,那么就按全大写来处理,否则按照指定的赋值,但是id除外(别名为_id)。
3. 总结
本文中的测试案例都是很简单的情况,实际开发时需要处理的数据肯定要多得多。熟悉了greenDao框架的思想及用法之后,处理一般性的数据集时会简单、高效很多,通过面向对象的思想为实体类生成对应的操作类,结构清晰,易维护。
最后给出项目下载链接:http://files.cnblogs.com/files/tgyf/greendaoTest.rar。
greenDao:操作数据库的开源框架的更多相关文章
- greendao操作数据库的使用方法
第一步:把greendao-1.3.0-beta-1,greendao-generator-1.3.1两个jar包加载到工程的lib的文件夹中,一定要右键点击Add As Library后才能使用. ...
- JavaWeb_(Mybatis框架)JDBC操作数据库和Mybatis框架操作数据库区别_一
系列博文: JavaWeb_(Mybatis框架)JDBC操作数据库和Mybatis框架操作数据库区别_一 传送门 JavaWeb_(Mybatis框架)使用Mybatis对表进行增.删.改.查操作_ ...
- 分享一个以前写的基于C#语言操作数据库的小框架
一:前言 这个是以前写的操作MySQL数据库的小型框架,如果是中小型项目用起来也是很不错的,里面提供Filter.ModelPart.Relationship等机制实现操作数据库时的SQL语句的拼接和 ...
- 让我们一起用开源数据库和开源框架废弃Access
一.为什么要废弃Access? 1.客户的机子上需要安装access的驱动 ps:这个比较烦人,大家都知道部署越简单越好,安装这个对用户来说太繁琐了. 2.操作时性能不佳 using System; ...
- [ 转]Android快速开发–使用ORMLite操作数据库
OrmLite是一个数据库操作辅助的开源框架,主要面向Java语言.在Android面向数据库开发中,是一个比较流行的开源框架,方便操作而且功能强大,今天来学习一下,最近的项目中也有所涉及,写个博客来 ...
- 开源框架GreenDao的操作
1.为什么需要GreenDao?Google原生API不方便 @1手动组拼SQL语句 @2需要自己写操作数据库代码 @3不能把数据库中的数据映射成对象 @4没有实现关联查询 2.GreenDao是什么 ...
- IT观察】网络通信、图片显示、数据库操作……Android程序员如何利用开源框架
每个Android 程序员都不是Android应用开发之路上孤军奋战的一个人,GitHub上浩如烟海的开源框架或类库就是前人为我们发明的轮子,有的轮子能提高软件性能,而有的轮子似乎是以牺牲性能为代价换 ...
- 数据库开源框架GreenDao的使用解析
数据库开源框架GreenDao的使用解析 1,GreenDao概述 1),greenDao是一个当下十分火热的数据库开源框架,或者说是一个帮助Android开发者将数据存到SQLite中的一个开源项目 ...
- 数据库开源框架之GreenDAO
主页: https://github.com/greenrobot/greenDAO 配置: 添加以下依赖 * compile 'de.greenrobot:greendao:2.1.0' * com ...
随机推荐
- .NET 各种框架
基于.NET平台常用的框架整理 分布式缓存框架: Microsoft Velocity:微软自家分布式缓存服务框架. Memcahed:一套分布式的高速缓存系统,目前被许多网站使用以提升网站的访问速度 ...
- jQuery siteslider 动画幻灯片
在线实例 效果一 效果二 使用方法 <div class="container demo-1"> <div id="slider ...
- html5快速入门(四)—— JavaScript
前言: 1.HTML5的发展非常迅速,可以说已经是前端开发人员的标配,在电商类型的APP中更是运用广泛,这个系列的文章是本人自己整理,尽量将开发中不常用到的剔除,将经常使用的拿出来,使需要的朋友能够真 ...
- Android Studio 恢复小窗口停靠模式(Docked Mode)
安卓studio在使用小窗口时,如果我们点击取消了窗口的docked mode模式,窗口就会变成,你一旦触发窗口以外的区域,窗口就会龟缩回去.此时,如果你想要恢复回原来的docked mode的话,具 ...
- 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)
声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...
- 设置 TabBarItem 选中时的图片及文字颜色
TabBarController 是在 ios 开发过程中使用较为频繁的一个 Controller,但是在使用过程中经常会遇到一些问题,例如本文所要解决的,如何修改 TabBar 选中时文字及图片的颜 ...
- 敏捷开发与jira之研发管理模式
以IPD方法论为基础,采用原型+迭代的开发模式,并以质量优先为原则,持续对用户做价值交付. 使用JIRA+WIKI+SVN管理整个的研发过程:JIRA管理任务和进度:SVN管理代码和过程文档:WIKI ...
- linux版基金看板
程序员的吊丝们,还在害怕上班时偷偷看基金被老板发现吗?今天你们的福利来了,专属程序员吊丝一族的礼物,linux版基金看板. 优点: 1.自定义设置关注基金 2.linux系统,让别人可以以为你一直都在 ...
- 常用API——日期型函数Date
上图 ·声明 var myDate = new Date(); //系统当前时间 var myDate = new Date(yyyy, mm, dd, hh, mm, ss); var myDate ...
- html经验汇总
1.点击radio的文字时,自动选中.可以在input里放置label,然后for属性关联input的id <input type="radio" id="male ...