Android ORM框架之-ActiveAndroid的简单分析
最后更新于:2022-04-01 15:49:18
题外话,这篇博客和之前的博客差这么长时间,是因为我来北京实习了。但是,实习归实习,学习不能停止不是么。在目前所做的东西当中,ORM框架用的是ActiveAndroid,以前我写过GreenDao的,那么现在就来记录下ActiveAndroid的吧。[github地址](https://github.com/pardom/ActiveAndroid/), 这一篇文章就不在多说如何使用了,github wiki上面很详细。
### 初始化过程
初始化的过程很简单。我们只需要在Application中调用
~~~
ActiveAndroid.initialize(this);
~~~
就可以完成初始化,(PS:我这里并没有像文档说的一样在配置文件中配置什么的,但是还能用,希望知道的小伙伴指点我一下)。
当然,上面的方法会使用默认的参数来创建出数据库来,我们也可以配置。代码如下
~~~
Configuration configuration = new Configuration.Builder(this)
.setDatabaseName("guilei.db")
.setDatabaseVersion(2)
.create();
ActiveAndroid.initialize(configuration);
~~~
我这里只是设置了下数据库名和版本号,当然也可以设置其他东西,如下图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768086e666.jpg "")
有缓存大小,models,类型等等(我们想要转化的类型,如Data)
这里就不在多说了。我们看看ActiveAndroid是如何完成初始化的吧。
~~~
public static void initialize(Configuration configuration, boolean loggingEnabled) {
// Set logging enabled first
setLoggingEnabled(loggingEnabled);
Cache.initialize(configuration);
}
~~~
我们看Cache的initialize方法。
~~~
public static synchronized void initialize(Configuration configuration) {
if (sIsInitialized) {
Log.v("ActiveAndroid already initialized.");
return;
}
sContext = configuration.getContext();
sModelInfo = new ModelInfo(configuration);
sDatabaseHelper = new DatabaseHelper(configuration);
// TODO: It would be nice to override sizeOf here and calculate the memory
// actually used, however at this point it seems like the reflection
// required would be too costly to be of any benefit. We'll just set a max
// object size instead.
sEntities = new LruCache<String, Model>(configuration.getCacheSize());
openDatabase();
sIsInitialized = true;
Log.v("ActiveAndroid initialized successfully.");
}
~~~
我们先来看着一行代码
~~~
sModelInfo = new ModelInfo(configuration);
~~~
我们得看看这是干什么的。这个最终会调用ModelInfo的scanForModel方法,从名字上就知道是扫描Model的。这个方法会扫描dex文件,并调用scanForModelClasses方法,来扫描出继承Model的类,也就是我们的实体类。关键代码如下。
~~~
Class<?> discoveredClass = Class.forName(className, false, classLoader);
if (ReflectionUtils.isModel(discoveredClass)) {
@SuppressWarnings("unchecked")
Class<? extends Model> modelClass = (Class<? extends Model>) discoveredClass;
mTableInfos.put(modelClass, new TableInfo(modelClass));
}
else if (ReflectionUtils.isTypeSerializer(discoveredClass)) {
TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance();
mTypeSerializers.put(instance.getDeserializedType(), instance);
}
~~~
接下来看下面这一行代码,也是非常关键的代码。
~~~
sDatabaseHelper = new DatabaseHelper(configuration)
~~~
~~~
public DatabaseHelper(Configuration configuration) {
super(configuration.getContext(), configuration.getDatabaseName(), null, configuration.getDatabaseVersion());
copyAttachedDatabase(configuration.getContext(), configuration.getDatabaseName());
mSqlParser = configuration.getSqlParser();
}
~~~
看到其中的copyAttachedDatabase了么。这个方法会将Assets目录下的数据库,拷贝到databases目录下。
~~~
final InputStream inputStream = context.getAssets().open(databaseName);
final OutputStream output = new FileOutputStream(dbPath);
byte[] buffer = new byte[8192];
int length;
while ((length = inputStream.read(buffer, 0, 8192)) > 0) {
output.write(buffer, 0, length);
}
~~~
这个类继承只SQLiteOpenHelper,我们看下他的OnCreate方法。
~~~
public void onCreate(SQLiteDatabase db) {
executePragmas(db);
executeCreate(db);
executeMigrations(db, -1, db.getVersion());
executeCreateIndex(db);
}
~~~
继续看executeCreate方法。
~~~
private void executeCreate(SQLiteDatabase db) {
db.beginTransaction();
try {
for (TableInfo tableInfo : Cache.getTableInfos()) {
db.execSQL(SQLiteUtils.createTableDefinition(tableInfo));
}
db.setTransactionSuccessful();
}
finally {
db.endTransaction();
}
}
~~~
这个方法就会把我们扫描出来的Model类转化为数据库。我们看下创建数据表的具体方法。
~~~
public static String createTableDefinition(TableInfo tableInfo) {
final ArrayList<String> definitions = new ArrayList<String>();
for (Field field : tableInfo.getFields()) {
String definition = createColumnDefinition(tableInfo, field);
if (!TextUtils.isEmpty(definition)) {
definitions.add(definition);
}
}
definitions.addAll(createUniqueDefinition(tableInfo));
return String.format("CREATE TABLE IF NOT EXISTS %s (%s);", tableInfo.getTableName(),
TextUtils.join(", ", definitions));
}
~~~
这个方法会将model类中的成员抽成数据表对应的字段。具体的代码我们就不往深处看了。我们看下数据库更新吧。
~~~
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
executePragmas(db);
executeCreate(db);
executeMigrations(db, oldVersion, newVersion);
}
~~~
可以看到,和GreenDao不同的是这里是可以实现数据库更新的。
### CURD 过程
在这里,我以查询为例。来简要的说明一下。
一个简单的查询过程是下面这个样子
~~~
Item result = new Select().from(Item.class).executeSingle();
~~~
Select().from(Model)是要告诉我们从那张表找,接下来的一些列条件则交给Form来负责。
~~~
public From from(Class<? extends Model> table) {
return new From(table, this);
}
~~~
我们去看下From类。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680881c2d.jpg "")
方法很多啊,我承认我数据库忘的差不多了,不过我们可以知道的是,这么多的方法是为了让我们拼接出一条完整的sql语句。可以看到其中的as or and where 等等都是sql语句中一个关键子,最后带哦用execXXXX就可以到查询了。这里的代码就不给大家分析了。
### 总结
总的来说,ActiveAndroid用起来还是很简单的,更多的用法还是看官方的文档吧,[文档地址](https://github.com/pardom/ActiveAndroid/wiki/Getting-started)
到现在我也看来很多的开源框架的源代码了,感觉自己的技术并没有得到很好的提升,在坐的小伙伴有什么好的办法,还希望你们能告诉我。
RxJava的简单学习(学习自扔物线)
最后更新于:2022-04-01 15:49:15
首先说明下面这个是看[扔物线大大](http://gank.io/post/560e15be2dca930e00da1083)的学习笔记,请直接前往[ 这里看极其详细的入门版](http://gank.io/post/560e15be2dca930e00da1083)这里先给出这个歌开源库的[github地址](https://github.com/ReactiveX)
### 前言
当前RxJava可是越来越火,也越来越多的人开始学习RxJava,越来越多的项目开始使用RxJava,那么我们就有必要来学习下RxJava。
### RxJava是什么
### Rx是什么
RX(Reactive Extensions)原来是由微软提出的一个综合了异步和机遇事件驱动的库包,使用开观察序列和LINQ-style查询操作。那么RX有什么特点呢?Rx最显著的特性是使用可观察集合(Observable Collection)来达到集成异步(composing asynchronous)和基于事件(event-based)的编程的效果。当然,RX当中的序列为数据流。这些我们不多说了。[RX介绍](http://www.jdon.com/45833)。
### RxJava是什么
RxJava是由ReactiveX开发并维护的一个开源项目。先来看看ReactiveX的介绍。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768081c143.jpg "")
很明显异步编程,接下来我们在看看RxJava的介绍。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680842b5e.jpg "")
一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库。有点抽象,这句话说白了就是观察者模式、异步、事件流。RxJava是一种响应式编程。[响应式编程](http://www.tuicool.com/articles/BBNRRf)
- 观察者模式,举个例子?比如说你睡午觉了,舍友不睡,你就说某某,到了几点叫我,然后时间一到,他叫你起床了,这就是个很简单的例子。关于观察者模式,就不细说了。
- 异步 什么?你不知道异步是什么?那你怎么学编程的。异步就是不用等待结果,即可继续执行,这里就又牵出一个回调的概念,不多少了、
- 事件流 就是一系列有序事件
### RxJava怎么用
现在AS中引入。
~~~
compile 'io.reactivex:rxjava:1.0.16'
compile 'io.reactivex:rxandroid:1.0.1'
~~~
这里引入RxAndroid的原因是 这里是android程序。
### 1.Observer观察者
有下面2中方式。
~~~
public static Observer getObserver(){
Observer<String> observer = new Observer<String>() {
@Override
public void onCompleted() {
Log.e(TAG, "onCompleted: " );
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: "+e.getMessage() );
}
@Override
public void onNext(String s) {
Log.e(TAG, "onNext: "+s );
}
};
return observer;
}
~~~
~~~
public static Observer getSubscriber(){
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onCompleted() {
Log.e(TAG, "onCompleted: " );
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " );
}
@Override
public void onNext(String s) {
Log.e(TAG, "onNext: "+s );
}
};
return subscriber;
}
~~~
2者的关系如下
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680858e96.jpg "")
- onCompleted 事件流结束
- onError 出错
- onNext 事件正常
这三个就是事件发生时触发的回调。在android中,回调一般发生在UI线程。
### 2.Observable被观察者
Observable的源码1w+行,我们一般通过下面三种方式来创建被观察者。
#### 2.1.Create
~~~
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("hello");
subscriber.onNext("world");
subscriber.onNext("my name is quanshijie");
subscriber.onCompleted();
}
});
~~~
其中的onNext就是一个一个的事件。这种方式必须以onCompleted结束。
#### 2.2.just
~~~
Observable observable = Observable.just("hello", "world", "my name is quanshijie");
~~~
just中就是一个一个的事件
#### 2.3.form
~~~
String[] words = {"hello","world","my name is quanshijie"};
Observable observable = Observable.from(words);
~~~
### 3.Subscribe 订阅
Observable.subscribe(Observable或者Subscriber)
也可采用如下方法。
~~~
observable.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.e(TAG, "call: " + s);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.e(TAG, "call: " + throwable.getMessage());
}
}, new Action0() {
@Override
public void call() {
Log.e(TAG, "call: "+"completed" );
}
}
);
~~~
### 4.线程调度
在android当中,线程之间切换是很频繁的,UI线程不能进行耗时操作,而android中耗时操作还是很多的。我们来看下如何进行线程调度。看如下例子。
~~~
Observable.just(1,2,3,4)
.subscribeOn(Schedulers.io()) //subscribe发生在IO线程
.observeOn(AndroidSchedulers.mainThread()) //指定回调发生在主线程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
Log.e(TAG, "call: " + integer);
}
});
~~~
我们假设1,2,3,4为耗时操作,如操作数据库或者网络请求或者其他,我们通过subscribeOn来指定这些事件发生的线程,通过observeOn来指定回调的线程,
- Scheduler.io() io线程
- Scheduler.newThread 新线程
- Scheduler.immediate 当前线程
- AndroidSchedulers.mainThread() androidUI线程
### 5.变换
#### 5.1 map变换
~~~
Observable.just("xxx")
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String s) {
return BitmapFactory.decodeFile(s);
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
// showBitmap(bitmap);
}
});
~~~
Funcx函数来实现类型变换。这里是一对一转换
#### 5.2 flatMap转换
实现多对多转化。以抛物线的例子来看。
~~~
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
@Override
public void onNext(Course course) {
Log.d(tag, course.getName());
}
...
};
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>() {
@Override
public Observable<Course> call(Student student) {
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
~~~
### RxJava何时用
说了那么多RxJava的用法,然而并没有什么乱用。我们说那么多,到头来没什么用岂不是很二。用法请移步[大头鬼RxJava使用小结](http://blog.csdn.net/lzyzsd/article/details/50120801) 或者[github上的这个rxdemo](https://github.com/kaushikgopal/RxJava-Android-Samples)
### 总结
若是RxJava+retrofit+lambda+RxBinding等其他现成的Observable,会使我们的代码变得简洁干净,条理清晰。
Android 网络开源库之-retrofit
最后更新于:2022-04-01 15:49:13
### 前言
当前的网络开源库有许多,如volley,okhttp,retrofit等,这三个库当前是比较火的,其中,okhttp和retrofit由square团队开发。关于这三个库的区别,请移步[stackoverflow](http://stackoverflow.com/questions/16902716/comparison-of-android-networking-libraries-okhttp-retrofit-volley/)或者[知乎](http://www.zhihu.com/question/35189851)查看。开发过程中选择什么样的开源库需要更具我们APP来做出选择。我们选出stackoverflow中的一段话来看下。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076806beecb.jpg "")
上面说,需要与web service通信的时候,我们使用retrofit。[百度百科 web service介绍](http://baike.baidu.com/link?url=FeGwtLhZW7pjuwc-o8gavLwVe6LRnC4tGK-JAw42uHD6GbwI7ex7NN0Ac3Q17NYnFhDBxHhXMANQp3mjLpCpyq),那么我们见天就来了解下retrofit。
### 什么是retrofit
关于什么是retrofit,[官网文档](http://square.github.io/retrofit/)上们有一句话。A type-safe HTTP client for Android and Java。额,似乎什么也看出去来,就知道是一个类型安全的http client库。那么什么是类型安全呢?类型安全代码指访问被授权可以访问的内存位置。例如,类型安全代码不能从其他对象的私有字段读取值。它只从定义完善的允许方式访问类型才能读取。类型安全的代码具备定义良好的数据类型。更多内容[百度百科-类型安全](http://baike.baidu.com/link?url=LDmTGCvQfcX8SZB8DZb9GrHK-9uoqf3CadgGjNGjZPVZCIDEVO2uhgDBb4GIB8ktqms3HFLGjO9YVE8H__JHMq),关于这里还引用上面知乎的一句话。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076806dd9fe.jpg "")
[RESTful-百度百科](http://baike.baidu.com/link?url=07gpSp66HaU7PDIcsUpY7U6wV8ic3zzuL0QrElchvTqHjk4lTILGotJO3BNk_OOc2V-Bn1gjLQ_ay70lg_dHF_)
[rest-百度百科](http://baike.baidu.com/link?url=5FvkIdS0cA_BIKKGhCEb6t8rNQgClAGxx1w6Mwnt4oEUAlvDKbWJmi5GZArR8cUapol07jimkCqF1wQVmYXCYgkOl9DWoWjovULf8SN78AO)
上面这么多抽象的概念太抽象,我们不管他。我们只需要知道retrofit确实是个很好的开源库就可以了。
### 入门打老虎
开源库是不错,但是,你文档能不能写的详细点。入门就是个大老虎。怎么说呢,文档给出的代码精辟,但是不能运行。那么,我们来看看具体的,真正的入门步骤。
### 1.在gradle脚本中添加
~~~
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076807000c9.jpg "")
retrofit是中有用的okhttp,添加gson库是为了将返回数据转化为实体类。
### 2.将http api转化为java接口
以下称这货为http接口
比如我们想去这个网址上获取json数据。[https://api.github.com/users/Guolei1130](https://api.github.com/users/Guolei1130)
你们也可以将Guolei1130替换为你们自己的github。
~~~
public interface gitapi {
@GET("/users/{user}")
Call<gitmodel> getFeed(@Path("user") String user);
}
~~~
这里使用注解,@GET表示我们的请求方式是get请求,@GET(“STR”),标明这里的请求地址为BASEURL+STR,{user},这里会在后面被getFeed的user参数替换,最后就拼接成了最终的请求路径。返回的数据室什么呢。Call< gitmodel>,这里返回的Call是用来让我们执行请求的。需要注意的是:Call< T>表示返回体中的数据,我们看下上面那个网址的返回数据。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076807132be.jpg "")
,很明显是个json对象。
但是如果是返回数据为json数组的话我们就需要注意,这里具体怎么用还要根据返回数据以及我们的model来确定,倘若model对应json中的对象,便用Call< List< gitmodel>>,倘若对应json数组,用Call< gitmodel>。多的不说了,自己体会体会。
### 3.model的编写
只需要将json对象的键值,编写model对应的成员变量,在生成get set方法就好。
如图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768073226b.jpg "")
### 4.执行请求
~~~
Retrofit retrofit= new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
gitapi service = retrofit.create(gitapi.class);
Call<gitmodel> model = service.getFeed("Guolei1130");
model.enqueue(new Callback<gitmodel>() {
@Override
public void onResponse(Response<gitmodel> response, Retrofit retrofit) {
Log.e(TAG, "onResponse: "+response.body().getLogin() );
}
@Override
public void onFailure(Throwable t) {
Log.e(TAG, "onFailure: " + t.getMessage());
}
});
~~~
这里我们先构造Retrofit对象,由于我们将返回的json数据转化成了model对象,所以在构造Retrofit对象的时候,通过addConverterFactory来添加转化器来完成数据转化。
然后利用http接口的方法生成Call对象,最后用Call对象来执行http请求(异步和同步,后面会说到)。别忘记添加权限。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768075263f.jpg "")
好,到这里入门老虎就被我们打死了。
### 注解爽歪歪
我们既然知道了retrofit是通过注解将HTTP转化为java接口,那么我们就需要了解下都有哪些注解,该怎么用。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680764571.jpg "")
### 请求方式
GET, POST, PUT, DELETE, 和 HEAD,我们平常开发中经常用的也就get和post。关于HTTP请求方式,这里就不再说了,网上有很多介绍HTTP协议的文章,都特别详细。
我们知道get请求方式,参数是放在路径当中的。看下图的路径。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680779106.jpg "")
,
是不是很长,不过没关系,用retrofit一样可以将这么长的串拼接到路径中,怎么做呢?
这里就用到了@Query(一个键值对)和@QueryMap(多对键值对)。
~~~
// 假设 baseurl = "http://baidu.com"
@GET("/s")
Call<gitmodel> onekey(@Query("wd") String wdvalue);
~~~
上面代码拼接出来的 = “[http://baidu.com/s?wd=wdvalue](http://baidu.com/s?wd=wdvalue)“.
上面只是一个参数的时候,很多时候我们有许多参数,这个时候就需要我们使用@QueryMap 了
~~~
Call<gitmodel> manykey(@QueryMap Map<String, String> options);
~~~
上面的即可将多对键值对拼接到路径当中。
### 请求体
我们知道post和get的区别当中有一点就是参数的位置,get放在url路径当中,post放在请求体当中。
注意:前方高能,请仔细阅读。前方高能,请仔细阅读。前方高能,请仔细阅读。下面我们模拟一个登录,请求参数为用户名和密码,返回参数为我们的用户名。
#### 1.转化为接口
~~~
@POST("/index.php")
Call<Des> post(@Body User user);
~~~
#### 2.编写User和Des,用 来转化数据
~~~
public class Des {
public String des;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
}
~~~
~~~
public class User {
public String username;
public String password;
public User(String username,String password){
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
~~~
#### 3.发送请求
~~~
Retrofit retrofit= new Retrofit.Builder()
// .baseUrl("https://api.github.com")
.baseUrl("http://192.168.1.214")
.addConverterFactory(GsonConverterFactory.create())
.build();
gitapi service = retrofit.create(gitapi.class);
Call<Des> model = service.post(new User("guolei","123456"));
model.enqueue(new Callback<Des>() {
@Override
public void onResponse(Response<Des> response, Retrofit retrofit) {
Log.e(TAG, "onResponse: "+response.body().getDes());
}
@Override
public void onFailure(Throwable t) {
Log.e(TAG, "onFailure: "+t.getMessage() );
}
});
~~~
客户端的代码很简单,在这里就不做过多的解释。重点在于客户端和服务器之间的交互。妈蛋,坑死我了。官方文档上有一句话是这样说的。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768078cdce.jpg "")
意思就是请求体对象也会转化为json,刚开始我们并不知道会转化为json,我们还傻傻的在php代码中$_POST[‘username’],试了几次没效果之后。果断拿出工具来分析。
[抓包神器Charles](http://pan.baidu.com/s/1c08brfq),关于如何使用我这里不说,我这里只说下如何设置android端代理。长按链接的网络-》修改 网络-》将代理设为手动,输入IP和端口。如下图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768079fe76.jpg "")
。
接下来,我们利用抓包工具去看看服务器接受的数据。如下图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076807b9e3b.jpg "")
,
果然,是json数据,但是我擦,我们明显感觉这种数据我们没法通过$_POST来接收。怎么办呢,不着急,咱们可以通过如下代码来接收。php完整代码如下(ps:只是演示,没几行)
~~~
<?php
$var=file_get_contents("php://input");
$obj= json_decode($var,true);
$des = $obj['username'];
$arr = array("des"=>$des);
echo json_encode($arr);
~~~
恩,就这么少,关于如下安装php开发环境就不说了,so easy不是么。最后,我们来看下效果图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076807cc10b.jpg "")
### 表单编码和多part
什么叫表单,我想大家都应该知道,表单中有很多元素,我们这里也不例外。在html代码中,我们经常通过form表单来提交。在上面的请求体中,我们明显感觉那玩意十分貌似有点难用。不过没关系,我们有表单。
还是和上个例子一样。
~~~
@FormUrlEncoded
@POST("/index.php")
Call<Des> form(@Field("username") String username,@Field("password") String password);
~~~
~~~
Call<Des> model = service.form("guolei","123456");
~~~
看下输出结果;
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076807e445e.jpg "")
这个多part是什么呢,就是将请求提分为多个部分,这个就没啥好说了的。
### 请求头
我们知道http是有请求头的,有些时候我们是需要填写或者配置一下请求头的,比如说,文件上传,或者cookie保持。这里的请求头支持动态配置和静态配置。
#### 1.静态配置
通过@Headers注解。如:下面这段是官方文档上的。
~~~
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
~~~
#### 2.动态配置
也来一段官方文档上面的吧。
~~~
@GET("/user")
Call<User> getUser(@Header("Authorization") String authorization)
~~~
这些都相对简单的,没啥好说的,事实上,在开发过程中需要我们配置请求头的地方也不多。
### 执行方式
这里支持异步和同步。上面的例子中我们都是用的异步。那么我们看下如何执行同步请求,也很简单。
~~~
//同步请求
//model.execute().body().getLogin();
~~~
### 混淆代码
~~~
-dontwarn retrofit.**
-keep class retrofit.** { *; }
-keepattributes Signature
-keepattributes Exceptions
~~~
### retrofit+rxjava
在抛物线大大的RxJava入门当中,我们知道了retrofit和rxjava可以结合使用,那么我们现在便来看看如何使用。
### 1.添加
~~~
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'io.reactivex:rxjava:1.0.16'
compile 'io.reactivex:rxandroid:1.0.1'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
}
~~~
### 2.http api接口
~~~
@FormUrlEncoded
@POST("/index.php")
public Observable<Des> rxpost(@Field("username") String username,@Field("password") String password);
~~~
### 3.在retrofit中添加RxJavaCallAdapterFactory
~~~
Retrofit retrofit= new Retrofit.Builder()
// .baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl("http://192.168.1.214")
.build();
~~~
### 4.执行
~~~
gitapi service = retrofit.create(gitapi.class);
Observable<Des> observable = service.rxpost("quanshijie", "123456");
observable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(new Action1<Des>() {
@Override
public void call(Des des) {
Log.e(TAG, "call: " + des.getDes().toString());
mText.setText(des.getDes().toString());
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.e(TAG, "call: " + throwable.getLocalizedMessage());
}
});
~~~
在这里我们要做好线程调度,现在网络上那些坑爹的代码,少一行代码,把我坑了好长时间。喵了个咪的。最后看下效果图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768080237c.jpg "")
### 总结
到这里,还差我们网络请求的其他需求,比如说文件上传下载,cookie保持,https协议支持等。在okhttp中,这些东西都可以,retrofit作为okhttp的兄弟,我想也不会太差,还得继续学习啊。
不过,在查了一些资料之后,我们知道,网络库的选择要根据需求,一个很大的项目中用一个网络库很显然是不可能的。那么,okhttp+retrofit+图片缓存库+rxjava会不会成为日后开发的主流呢。还是期待吧。
好累啊,到现在饭都没吃。retrofit的坑还是有的。我估计剩下的三点地方坑更大,不扯了,收工。
Android ORM数据库框架之-greenDao(四)
最后更新于:2022-04-01 15:49:11
本篇是greenDao的最后一篇,这一篇带大家看下greenDao的源码。
- dao的初始化过程
这一过程非常的复杂,容易绕晕,那么我就来带大家梳理一下。首先看看我们初始化dao的方法。
~~~
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this,"persons-db",null);
db = helper.getWritableDatabase();
Log.e("tag","this is db version ->"+db.getVersion());
// 该数据库连接属于DaoMaster,所以多个Session指的是想用的数据库连接
daoMaster = new DaoMaster(db);
daoSession =daoMaster.newSession();
return daoSession.getPersonDao();
~~~
我在这里返回的是PersionDao。首先看下helper的初始化 过程。
~~~
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
~~~
调用父类的构造方法。
~~~
public static abstract class OpenHelper extends SQLiteOpenHelper {
public OpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory, SCHEMA_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
createAllTables(db, false);
}
}
~~~
在父类中完成数据表的创建。
~~~
public static void createAllTables(SQLiteDatabase db, boolean ifNotExists) {
PersonDao.createTable(db, ifNotExists);
PeopleDao.createTable(db, ifNotExists);
IdCardDao.createTable(db, ifNotExists);
OrderDao.createTable(db, ifNotExists);
CourseDao.createTable(db, ifNotExists);
}
~~~
~~~
public static void createTable(SQLiteDatabase db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"PERSON\" (" + //
"\"_id\" INTEGER PRIMARY KEY ," + // 0: id
"\"NAME\" TEXT NOT NULL ," + // 1: name
"\"AGE\" INTEGER NOT NULL ," + // 2: age
"\"CARD\" TEXT);"); // 3: card
}
~~~
这么一来,表的创建过程就理清楚了。接下来看DaoMaster的初始化。
~~~
public DaoMaster(SQLiteDatabase db) {
super(db, SCHEMA_VERSION);
registerDaoClass(PersonDao.class);
registerDaoClass(PeopleDao.class);
registerDaoClass(IdCardDao.class);
registerDaoClass(OrderDao.class);
registerDaoClass(CourseDao.class);
}
~~~
显示调用父类的构造方法,接着registDaoClass()
~~~
public AbstractDaoMaster(SQLiteDatabase db, int schemaVersion) {
this.db = db;
this.schemaVersion = schemaVersion;
daoConfigMap = new HashMap<Class<? extends AbstractDao<?, ?>>, DaoConfig>();
}
protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
DaoConfig daoConfig = new DaoConfig(db, daoClass);
daoConfigMap.put(daoClass, daoConfig);
}
~~~
看到,上面的一句很关键。new DaoConfig();
~~~
public DaoConfig(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>> daoClass) {
this.db = db;
try {
this.tablename = (String) daoClass.getField("TABLENAME").get(null);
Property[] properties = reflectProperties(daoClass);
this.properties = properties;
allColumns = new String[properties.length];
List<String> pkColumnList = new ArrayList<String>();
List<String> nonPkColumnList = new ArrayList<String>();
Property lastPkProperty = null;
for (int i = 0; i < properties.length; i++) {
Property property = properties[i];
String name = property.columnName;
allColumns[i] = name;
if (property.primaryKey) {
pkColumnList.add(name);
lastPkProperty = property;
} else {
nonPkColumnList.add(name);
}
}
String[] nonPkColumnsArray = new String[nonPkColumnList.size()];
nonPkColumns = nonPkColumnList.toArray(nonPkColumnsArray);
String[] pkColumnsArray = new String[pkColumnList.size()];
pkColumns = pkColumnList.toArray(pkColumnsArray);
pkProperty = pkColumns.length == 1 ? lastPkProperty : null;
statements = new TableStatements(db, tablename, allColumns, pkColumns);
if (pkProperty != null) {
Class<?> type = pkProperty.type;
keyIsNumeric = type.equals(long.class) || type.equals(Long.class) || type.equals(int.class)
|| type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class)
|| type.equals(byte.class) || type.equals(Byte.class);
} else {
keyIsNumeric = false;
}
} catch (Exception e) {
throw new DaoException("Could not init DAOConfig", e);
}
}
~~~
这个方法就是完成DaoConfig的配置的,通过反射机制,获取到我们的Dao类,比如说PersonClass,具体的代码大家去看,就是通过反射,很好理解。注意statements是TableStatements类型的。
继续,newSession();
~~~
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
~~~
~~~
public DaoSession(SQLiteDatabase db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {
super(db);
personDaoConfig = daoConfigMap.get(PersonDao.class).clone();
personDaoConfig.initIdentityScope(type);
peopleDaoConfig = daoConfigMap.get(PeopleDao.class).clone();
peopleDaoConfig.initIdentityScope(type);
idCardDaoConfig = daoConfigMap.get(IdCardDao.class).clone();
idCardDaoConfig.initIdentityScope(type);
orderDaoConfig = daoConfigMap.get(OrderDao.class).clone();
orderDaoConfig.initIdentityScope(type);
courseDaoConfig = daoConfigMap.get(CourseDao.class).clone();
courseDaoConfig.initIdentityScope(type);
personDao = new PersonDao(personDaoConfig, this);
peopleDao = new PeopleDao(peopleDaoConfig, this);
idCardDao = new IdCardDao(idCardDaoConfig, this);
orderDao = new OrderDao(orderDaoConfig, this);
courseDao = new CourseDao(courseDaoConfig, this);
registerDao(Person.class, personDao);
registerDao(People.class, peopleDao);
registerDao(IdCard.class, idCardDao);
registerDao(Order.class, orderDao);
registerDao(Course.class, courseDao);
}
~~~
~~~
public void initIdentityScope(IdentityScopeType type) {
if (type == IdentityScopeType.None) {
identityScope = null;
} else if (type == IdentityScopeType.Session) {
if (keyIsNumeric) {
identityScope = new IdentityScopeLong();
} else {
identityScope = new IdentityScopeObject();
}
} else {
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
~~~
这个函数就是判断,类型范围的。一般我们不需要管。看到在DaoSession的构造函数中,根据在DaoMaster初始化的config,经过范围类型判断,在DaoSession中也初始化了。至此,初始化过程完毕。
- CURD过程 我们以insert为例
~~~
dao.insert(person);
~~~
dao对象是我们初始化后得到的,person是一个Person实体对象。
~~~
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement());
}
~~~
上面的一段代码是AbstractDao类,这是一个抽象类,我们的Persondao就是继承的他。
~~~
statements.getInsertStatement()
~~~
通过statments对象实例获取SQLiteStatement对象,在(TableStatements类中)
~~~
public SQLiteStatement getInsertStatement() {
if (insertStatement == null) {
String sql = SqlUtils.createSqlInsert("INSERT INTO ", tablename, allColumns);
insertStatement = db.compileStatement(sql);
}
return insertStatement;
}
~~~
这样我们就获取到了一个SQLiteStatement对象。继续,看插入数据的过程。
~~~
private long executeInsert(T entity, SQLiteStatement stmt) {
long rowId;
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
updateKeyAfterInsertAndAttach(entity, rowId, true);
return rowId;
}
~~~
看到上面会判断是否在当前线程,不在的话会开启事务。总之,还是很安全的。就这么多吧,更多的源码还是大家自己看吧。真的感觉这个牛,坐等更新+上数据库更新
Android ORM数据库框架之-greenDao(三)
最后更新于:2022-04-01 15:49:08
关于上篇说到的数据库更新问题,我正在找国外大牛的二次封装的github代码。找到会贴出来。
咱们这篇,小小地分析下greendao-generator的源码,和大家一起了解下,代码的生成。
咱们写的java项目代码很简单,就是个初始化Schema——>添加Entity
——>生成的过程。
- Schema
我们看下我们写的代码
~~~
Schema schema = new Schema(2,"gl.com.greendaodemo");
~~~
很简单,就是版本号+生成代码包名。
我们看下Schema的部分源码。
~~~
private final int version;
private final String defaultJavaPackage;
private String defaultJavaPackageDao;
private String defaultJavaPackageTest;
private final List<Entity> entities;
private Map<PropertyType, String> propertyToDbType;
private Map<PropertyType, String> propertyToJavaTypeNotNull;
private Map<PropertyType, String> propertyToJavaTypeNullable;
private boolean hasKeepSectionsByDefault;
private boolean useActiveEntitiesByDefault;
public Schema(int version, String defaultJavaPackage) {
this.version = version;
this.defaultJavaPackage = defaultJavaPackage;
this.entities = new ArrayList<Entity>();
initTypeMappings();
}
~~~
看得出,构造函数就是初始化了数据库版本、包名、实体list以及属性类型(initTypeMappings()来完成属性类型初始化),下面贴出这个函数的部分代码
~~~
propertyToDbType = new HashMap<PropertyType, String>();
propertyToDbType.put(PropertyType.Boolean, "INTEGER");
propertyToDbType.put(PropertyType.Byte, "INTEGER");
propertyToDbType.put(PropertyType.Short, "INTEGER");
propertyToDbType.put(PropertyType.Int, "INTEGER");
propertyToDbType.put(PropertyType.Long, "INTEGER");
propertyToDbType.put(PropertyType.Float, "REAL");
propertyToDbType.put(PropertyType.Double, "REAL");
propertyToDbType.put(PropertyType.String, "TEXT");
propertyToDbType.put(PropertyType.ByteArray, "BLOB");
propertyToDbType.put(PropertyType.Date, "INTEGER");
~~~
Schema的初始化看完了,接下来我们看下如何添加实体
- Entity以及addEntity
~~~
Entity people = schema.addEntity("People");
people.addStringProperty("name").primaryKey(); //名字
people.addIntProperty("age"); //年龄
~~~
上面是添加一个实体的过程,我们瞅瞅addEntity();函数
~~~
public Entity addEntity(String className) {
Entity entity = new Entity(this, className);
entities.add(entity);
return entity;
}
~~~
嗯,简单 ,就是给list添加了一个对象。。。那么,给实体添加约束的源码又是什么呢?我们以addIdProperty()为例。
~~~
public PropertyBuilder addIdProperty() {
PropertyBuilder builder = addLongProperty("id");
builder.columnName("_id").primaryKey();
return builder;
}
~~~
可以看到,这里直接将给了个_id的列并作为主键存在。上面有用到PropertyBuilder这个类,这个是干什么的?
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076806a69e5.jpg "")
,偶,这个类就是给数据库中的字段设置约束的。看到,有自增、非空、主键等等。
~~~
person.addStringProperty("name")
~~~
我们看看如何给字段指定类型。上面 的哪一行代码 最终会调用 下面这个构造函数。可以看到,这里就有了字段类型了,那么字段类型又有哪些呢,还记得我们在初始化Schema的时候的代码么,没错,就是那些。但是,光那些是不够用的,greendao还支持我们自定义。请移步[官方介绍](http://greendao-orm.com/documentation/custom-types/)
~~~
public Property(Schema schema, Entity entity, PropertyType propertyType, String propertyName) {
this.schema = schema;
this.entity = entity;
this.propertyName = propertyName;
this.propertyType = propertyType;
}
~~~
接下来便是重头戏,代码生成部分
- 代码生成
-
~~~
new DaoGenerator().generateAll(schema, "/Users/mac/Desktop/GLandroidstudy/AS/greendaodemo/src/main/java-gen");
~~~
我们看看DaoGenerator的构造函数
~~~
public DaoGenerator() throws IOException {
System.out.println("greenDAO Generator");
System.out.println("Copyright 2011-2015 Markus Junginger, greenrobot.de. Licensed under GPL V3.");
System.out.println("This program comes with ABSOLUTELY NO WARRANTY");
patternKeepIncludes = compilePattern("INCLUDES");
patternKeepFields = compilePattern("FIELDS");
patternKeepMethods = compilePattern("METHODS");
Configuration config = new Configuration();
config.setClassForTemplateLoading(this.getClass(), "/");
config.setObjectWrapper(new DefaultObjectWrapper());
templateDao = config.getTemplate("dao.ftl");
templateDaoMaster = config.getTemplate("dao-master.ftl");
templateDaoSession = config.getTemplate("dao-session.ftl");
templateEntity = config.getTemplate("entity.ftl");
templateDaoUnitTest = config.getTemplate("dao-unit-test.ftl");
templateContentProvider = config.getTemplate("content-provider.ftl");
}
~~~
那个.ftl文件是什么呢?.ftl是Freemarker文件的后缀名,是个模版语言引擎。关于Freemarker更多介绍,自行百度。我们以entity.ftl为例,简单介绍几行。
~~~
public class ${entity.className}<#if
entity.superclass?has_content> extends ${entity.superclass} </#if><#if
entity.interfacesToImplement?has_content> implements <#list entity.interfacesToImplement
as ifc>${ifc}<#if ifc_has_next>, </#if></#list></#if> {
<#list entity.properties as property>
<#if property.notNull && complexTypes?seq_contains(property.propertyType)>
/** Not-null value. */
</#if>
<#if property.codeBeforeField ??>
${property.codeBeforeField}
</#if>
private ${property.javaTypeInEntity} ${property.propertyName};
</#list>
~~~
上面的结果就是
~~~
private class classname (extends supperclass )(implements interface){
private type property;
...
}
~~~
就是输出类似上面的东西,其实语法很简单,就是根据传进来的entity实体,根据entity实体的内容来讲${}部分用对应的东西替代,最后就输出成我们的文件了。好,就这么多把,我们再来看下generateAll()的代码。
~~~
public void generateAll(Schema schema, String outDir, String outDirEntity, String outDirTest) throws Exception {
long start = System.currentTimeMillis();
File outDirFile = toFileForceExists(outDir);
File outDirEntityFile = outDirEntity != null? toFileForceExists(outDirEntity): outDirFile;
File outDirTestFile = outDirTest != null ? toFileForceExists(outDirTest) : null;
schema.init2ndPass();
schema.init3rdPass();
System.out.println("Processing schema version " + schema.getVersion() + "...");
List<Entity> entities = schema.getEntities();
for (Entity entity : entities) {
generate(templateDao, outDirFile, entity.getJavaPackageDao(), entity.getClassNameDao(), schema, entity);
if (!entity.isProtobuf() && !entity.isSkipGeneration()) {
generate(templateEntity, outDirEntityFile, entity.getJavaPackage(), entity.getClassName(), schema, entity);
}
if (outDirTestFile != null && !entity.isSkipGenerationTest()) {
String javaPackageTest = entity.getJavaPackageTest();
String classNameTest = entity.getClassNameTest();
File javaFilename = toJavaFilename(outDirTestFile, javaPackageTest, classNameTest);
if (!javaFilename.exists()) {
generate(templateDaoUnitTest, outDirTestFile, javaPackageTest, classNameTest, schema, entity);
} else {
System.out.println("Skipped " + javaFilename.getCanonicalPath());
}
}
for (ContentProvider contentProvider : entity.getContentProviders()) {
Map<String, Object> additionalObjectsForTemplate = new HashMap<String, Object>();
additionalObjectsForTemplate.put("contentProvider", contentProvider);
generate(templateContentProvider, outDirFile, entity.getJavaPackage(), entity.getClassName()
+ "ContentProvider", schema, entity, additionalObjectsForTemplate);
}
}
generate(templateDaoMaster, outDirFile, schema.getDefaultJavaPackageDao(), "DaoMaster", schema, null);
generate(templateDaoSession, outDirFile, schema.getDefaultJavaPackageDao(), "DaoSession", schema, null);
long time = System.currentTimeMillis() - start;
System.out.println("Processed " + entities.size() + " entities in " + time + "ms");
}
~~~
最后都会调用上面的一段代码,上面的代码在做什么呢。显示创建几个文件夹,然后遍历List< Entity>,输出内容的
~~~
generate(templateEntity, outDirEntityFile, entity.getJavaPackage(), entity.getClassName(), schema, entity);
~~~
就是根据传进来的末班,包名,类名,schema,实体,替换掉模板中对应的,输出。关于具体输出的源码,实在是太长了,童鞋们自己看吧。
Android ORM数据库框架之-greenDao(二)
最后更新于:2022-04-01 15:49:06
这一篇将带给大家greenDao的一些比较高级的用法。
以下内容参考[官网](http://greendao-orm.com/)
关系型数据库,当然少不了多表关联,SQLite也不例外。那么我们就下来看下greenDao如何建立表关联
- 一对一 1:1 entity.addToOne(entity,property)
我们以人和身份证为例,
~~~
/**
* 人实体
*/
Entity people = schema.addEntity("People");
people.addStringProperty("name").primaryKey(); //名字
people.addIntProperty("age"); //年龄
/**
* 身份证
*/
Entity idcard = schema.addEntity("IdCard");
idcard.addStringProperty("idcardnum").primaryKey(); //身份证号
/**
* 人和身份证 是一对一的关系
*/
/** 一个人对应一个idcard **/
Property propertyidcardnum = people.addStringProperty("idcardnum").getProperty();
people.addToOne(idcard, propertyidcardnum);
/** 一个idcrad 对应一个name ***/
Property propertyname = idcard.addStringProperty("name").getProperty();
idcard.addToOne(people, propertyname);
~~~
注意:当我们要建立多表关联的时候,就不在添加id主键了,以为我们这里的主键要当成其他表的外键使用。
上面我们通过entity.addToOne(otherEntity,peoperty)来建立一对一关联,关系为,entity和otherentity通过peroperty来建立关联,其中peoperty在otherentity中式主键,在entity中是外键。这么干说,谁也记不住。看代码
~~~
Property propertyidcardnum = people.addStringProperty("idcardnum").getProperty();
people.addToOne(idcard, propertyidcardnum);
~~~
我们将身份证实体中的主键idcardnum,当成外键以一对一的关系添加到people实体中了,就是这么简单。
- 一对多 1:n addToMany(entity,property)
~~~
Entity order = schema.addEntity("Order");
order.addIntProperty("orderid").primaryKey();
order.addDoubleProperty("money").notNull();
/**
* 建立人与订单的一对多关系
*/
// Property propertypeoplenum=people.addStringProperty("idcardnum").getProperty();
Property property = order.addStringProperty("name").getProperty();
order.addToOne(people, property);
people.addToMany(order,propertyname).setName("orders");
~~~
我在这里建立了一个购物的实体。形成一对多关系 。并将订单中的主键id,以多对一的形式给people当外键。
- 多对多 m:n
~~~
Entity course = schema.addEntity("Course");
course.addStringProperty("courseid").primaryKey();
course.addStringProperty("coursename").notNull();
Property propertyPeopleId = course.addStringProperty("name").getProperty();
course.addToMany(people,propertyPeopleId);
Property propertyCourseID = people.addStringProperty("courseid").getProperty();
people.addToMany(course,propertyCourseID);
~~~
,这个就和上面的一样了,我就不再多少了。
需要注意的是,这里的关系特别绕,一不小心就会弄错。
现在,我们去看看生成的实体类有什么区别。
先看People,我们还记得,和身份证是一对一,和订单是一对多,和课程是多对多。
~~~
private String name;
private Integer age;
private String idcardnum;
private String courseid;
/** Used to resolve relations */
private transient DaoSession daoSession;
/** Used for active entity operations. */
private transient PeopleDao myDao;
private IdCard idCard;
private String idCard__resolvedKey;
private List<Order> orders;
private List<Course> courseList;
~~~
看到没,成员变量这里有了IdCard(单一),List< Order> ,List< Course >,确实是形成了上面我们写的关系。
接下来我们看下构造函数
~~~
public People(String name, Integer age, String idcardnum, String courseid) {
this.name = name;
this.age = age;
this.idcardnum = idcardnum;
this.courseid = courseid;
}
~~~
构造函数里面只是几个相关表的主键。。没什么奇怪的。那么我们看下,这个类下面都有什么方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680675d40.jpg "")
有发现,我们会发现这里面有getCourseList 和 getOrderList,我们挑一个来看看
~~~
public List<Order> getOrders() {
if (orders == null) {
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
OrderDao targetDao = daoSession.getOrderDao();
List<Order> ordersNew = targetDao._queryPeople_Orders(name);
synchronized (this) {
if(orders == null) {
orders = ordersNew;
}
}
}
return orders;
}
~~~
看到,这里通过name来查询,为什么呢?因为我们这个name是Order表的外键。哈哈,这样就爽了,都直接给提供方法了,都不同我们自己搞。恩,确实爽。
我们再看看_queryPeople_Orders()方法
~~~
public List<Order> _queryPeople_Orders(String name) {
synchronized (this) {
if (people_OrdersQuery == null) {
QueryBuilder<Order> queryBuilder = queryBuilder();
queryBuilder.where(Properties.Name.eq(null));
people_OrdersQuery = queryBuilder.build();
}
}
Query<Order> query = people_OrdersQuery.forCurrentThread();
query.setParameter(0, name);
return query.list();
}
~~~
不粗,果然是给我们封装好了的。啥,这种查询方法,看不懂?没事,我们后面会介绍到。
多表关联我们看完了,这里你要注意一个坑,那就是 关联的2张表的主键类型一定要一样,别问我为什么。
- 其他用法
添加约束 无非就是添加 主键 非空 自增等等。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076806900f2.jpg "")
如:
~~~
order.addIntProperty("orderid").primaryKey();
~~~
多线程下
~~~
Query<Order> query = people_OrdersQuery.forCurrentThread();
~~~
多条件查询
~~~
名字叫“乔”和(出生年份大于1970或(出生年份是1970年,出生月等于或大于10(10月)。
* 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();
~~~
或者
~~~
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 query = userDao.queryBuilder().where(
new StringCondition("_ID IN " +
"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)").build();
~~~
链接查询
~~~
使用连接查询 查询用户名为admin
Query query = userDao.queryRawCreate(
", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");
~~~
我们再来看看query的几个list…方法
> list() 所有的实体都被加载到内存中,结果是一个ArrayList,比较容易使用
listLazy() 实体按照需求加载,并不会查询完立即加载进内存,只会在需要的时候加载,并且会缓存在一个list之中,并需调用close关闭
listLazyUnCached() 一个虚拟的实体集,任何访问都必须从数据库中加载,必须被关闭
listIterator() 让你便利加载,数据没有被缓存,必须关闭
代码混淆
~~~
-keepclassmembers class * extends de.greenrobot.dao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
~~~
或者去github上看他们demo如何混淆的。
- 数据库升级
本以为这么好个东西数据库升级的一些问题肯定也弄好了,哎,结果。多说无益,我们来看看他封装的代码把。
~~~
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this,"persons-db",null);
~~~
~~~
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
~~~
~~~
public static void dropAllTables(SQLiteDatabase db, boolean ifExists) {
PersonDao.dropTable(db, ifExists);
PeopleDao.dropTable(db, ifExists);
IdCardDao.dropTable(db, ifExists);
OrderDao.dropTable(db, ifExists);
CourseDao.dropTable(db, ifExists);
}
~~~
~~~
public static void dropTable(SQLiteDatabase db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"PERSON\"";
db.execSQL(sql);
}
~~~
看见没,根本没有所谓的数据库升级,还把原来的数据库给删除了。擦。那怎么办呢。别着急,虽然咱垃圾,但是思路还是有的,
怎么办呢?
第一步,我们在生成生成实体类的文件中。
~~~
schema.enableKeepSectionsByDefault();//通过次Schema对象添加的所有实体都不会覆盖自定义的代码 或者根据需要添加其他()
~~~
这样我们再生成的时候就不会覆盖了。
第二步,咱给出一个连接,你们看吧。(本屌太渣)
[greenDao数据库升级](http://blog.csdn.net/fancylovejava/article/details/46713445)
到此为止:
参考资料:[greenDao官网](http://greendao-orm.com/)
Android ORM数据库框架之-greenDao(一)
最后更新于:2022-04-01 15:49:04
在了解greenDao之前,我们得了解什么是ORM,ORM Object-Relation-Mapping,对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上来说,它其实是创建了一个可在编程语言里使用的”虚拟对象数据库”。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系型数据库则是从数学理论发展而来的。两者之间是不匹配的,而ORM作为项目中间件形式实现数据在不同场景下数据关系映射,对象关系映射是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
那么,现在比较流行的有哪些ORM框架呢?
- [OrmLite](http://ormlite.com/)
- [greenDao](http://greendao-orm.com/)
- [Activiteandroid](http://www.activeandroid.com/)
- 其他的
我们今天在这里学习下greenDao的使用。关于greenDao和OrmLite的简单比较,请点击[传送门](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1127/2066.html)
greenDao设计的主要目标
- 一个精简的库
- 性能最大化
- 内存开销最小化
- 易于使用的API
- 对Android进行高度优化
greenDao设计的主要特点
- greenDao性能远远高于同类的OrmLite,
- greenDao支持[protocol buffer(protobuf)](https://github.com/google/protobuf) 协议数据的直接存储,
- 与OrmLite等使用注解方式的ORM框架不同,greenDao使用Code generation 的方式,这也是其性能提升的原因
接下就看看如何用。
- 在gradle中添加依赖,并修改文件
~~~
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "gl.com.greendaodemo"
minSdkVersion 17
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets{
main{
java.srcDirs = ['src/main/java','src/main/java-gen']
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'de.greenrobot:greendao:2.0.0'
}
~~~
注意:
~~~
sourceSets{
main{
java.srcDirs = ['src/main/java','src/main/java-gen']
}
}
~~~
这个为新添加的,src/main/文件夹名
- 在与java同级的目录下新建一个文件夹,理论上名字随你
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076805d8916.jpg "")
- 在项目中建一个Java Library
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076805efaf9.jpg "")
- 在java library的gradle脚本中添加
~~~
compile 'de.greenrobot:greendao-generator:2.0.0'
~~~
- 在java library中编写代码
~~~
public class ExampleDaoGenerator {
public static void main(String[] args) throws Exception{
//自动生成的数据库版本号,和生成代码的包路径
Schema schema = new Schema(2,"gl.com.greendaodemo");
// 当然,如果你愿意,你也可以分别指定生成的 Bean 与 DAO 类所在的目录,只要如下所示:
// Schema schema = new Schema(1, "me.itangqi.bean");
// schema.setDefaultJavaPackageDao("me.itangqi.dao");
// 模式(Schema)同时也拥有两个默认的 flags,分别用来标示 entity 是否是 activie 以及是否使用 keep sections。
// schema2.enableActiveEntitiesByDefault();
// schema2.enableKeepSectionsByDefault();
// 一旦你拥有了一个 Schema 对象后,你便可以使用它添加实体(Entities)了。
schema.enableKeepSectionsByDefault();//通过次Schema对象添加的所有实体都不会覆盖自定义的代码
addPerson(schema);
// 最后我们将使用 DAOGenerator 类的 generateAll() 方法自动生成代码,此处你需要根据自己的情况更改输出目录(既之前创建的 java-gen)。
// 其实,输出目录的路径可以在 build.gradle 中设置,有兴趣的朋友可以自行搜索,这里就不再详解。
new DaoGenerator().generateAll(schema, "/Users/mac/Desktop/GLandroidstudy/AS/greendaodemo/src/main/java-gen");
}
public static void addPerson(Schema schema){
Entity person = schema.addEntity("Person");
//也可以重新给表命名
// person.setTableName("Person_1");
// greenDAO 会自动根据实体类的属性值来创建表字段,并赋予默认值
// 接下来你便可以设置表中的字段:
// 与在 Java 中使用驼峰命名法不同,默认数据库中的命名是使用大写和下划线来分割单词的。
// For example, a property called “creationDate” will become a database column “CREATION_DATE”.
//id自增 name->非空字符串 age-> int 非空
person.addIdProperty();
person.addStringProperty("name").notNull();
person.addIntProperty("age").notNull();
person.addStringProperty("card");
}
}
~~~
小提示:右键文件,然后 copy path就可以将文件夹路径copy出来了。
代码中有详细的注释,就不在多说了。接下来我们回到我们的android工程中,看看我们的工程有什么变化。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768061fb60.jpg "")
给我们生成了这些玩意。我们先暂时不管这些东西。
接下来我们就要实现增删改查了。想想是不是有点小激动呢。
- 获得操作数据的dao对象
~~~
// 通过 DaoMaster 的内部类 DevOpenHelper,你可以得到一个便利的 SQLiteOpenHelper 对象。
// 可能你已经注意到了,你并不需要去编写「CREATE TABLE」这样的 SQL 语句,因为 greenDAO 已经帮你做了。
// 注意:默认的 DaoMaster.DevOpenHelper 会在数据库升级时,删除所有的表,意味着这将导致数据的丢失。
// 所以,在正式的项目中,你还应该做一层封装,来实现数据库的安全升级。
//实体名小写+s+"-db"
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this,"persons-db",null);
db = helper.getWritableDatabase();
Log.e("tag","this is db version ->"+db.getVersion());
// 该数据库连接属于DaoMaster,所以多个Session指的是想用的数据库连接
daoMaster = new DaoMaster(db);
daoSession =daoMaster.newSession();
return daoSession.getPersonDao();
~~~
具体就不解释了,代码里有注释。。
- CURL
增:
~~~
String name_string = name.getText().toString().trim();
int age_int = Integer.parseInt(age.getText().toString().trim());
Person person = new Person(null,name_string,age_int,age_int+"");
Log.e("tag","name-->"+person.getName()+"---"+"age---->"+person.getAge());
dao.insert(person);
NotifyDataSetChanged();
~~~
查and删
~~~
String name_delate = name.getText().toString().trim();
Query<Person> query = dao.queryBuilder().where(PersonDao.Properties.Name.eq(name_delate))
.build();
List<Person> persons = query.list();
dao.delete(persons.get(0));
NotifyDataSetChanged();
~~~
看看NotifyDataSetChanged()
~~~
private void NotifyDataSetChanged() {
String ageCoulmn =PersonDao.Properties.Age.columnName;
String orderby = ageCoulmn + " COLLATE LOCALIZED ASC";
cursor = db.query(dao.getTablename(),dao.getAllColumns(),
null,null,null,null,orderby);
adapter.swapCursor(cursor);
}
~~~
恩,这次就说这么多把。
代码地址:[找到greendaoDemo和daoexamplegenerator]即可([https://github.com/Guolei1130/MySelfStudy](https://github.com/Guolei1130/MySelfStudy))
如果大家还有疑问,请移步[泡在网上的日子](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1127/2069.html)
那么,最后,我们来放张效果图看看。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076806371d1.jpg "")
这一篇只带来简单的用法,下一篇将带给大家更多的用法,如
- 表关联、以及一些复杂的查询
- 数据库升级
- 代码混淆
Android四大图片缓存框架之-Picasso和Glide
最后更新于:2022-04-01 15:49:02
我看了看这2个,有很大的相似之处。这里我就不再介绍用法了,只给出几个比较好的博客,只是因为我不会转发。。。
Gilde:[github地址](https://github.com/bumptech/glide)[用法](http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0327/2650.html)
Picasso [Github地址](http://square.github.io/picasso/#features)[用法](http://blog.csdn.net/xu_fu/article/details/17043231)
请小伙伴原谅我偷懒。
Android 四大缓存框架之-Universal-Image-Loader
最后更新于:2022-04-01 15:48:59
以下为个人总结回顾,不喜勿喷。
github地址[链接](https://github.com/nostra13/Android-Universal-Image-Loader)
作为很早就出名的一个图片缓存框架,用起来还是很方便的。
- **在gradle构建脚本中添加**
~~~
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
~~~
- **在配置文件中添加相应的权限**
~~~
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
~~~
- **在Application中初始化**
在这里我们需要初始化一个ImageLoader,并且配置我们项目中可会会用到的DisplayImageOptions。
ImageLoader的初始化,ImageLoader是一个单例对象,我们在需要使用他的时候直接getInstance()即可。但是,在此之前,我们需要初始化。
~~~
public void getNormalImageloader(){
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(getApplicationContext());
/**
* 初始化
*/
ImageLoader.getInstance().init(configuration);
}
~~~
上面用的是默认的初始化参数,绝大多数情况下我们会这样用。但是,有时候我们也需要配置一些参数。这时,我们就需要
~~~
public void getMyImageloader(){
File cacheDir = StorageUtils.getCacheDirectory(getApplicationContext());
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.memoryCacheExtraOptions(480, 800) // default = device screen dimensions
.diskCacheExtraOptions(480, 800, null)
// .taskExecutor(...)
// .taskExecutorForCachedImages(...)
// .threadPoolSize(3) // default
.threadPriority(Thread.NORM_PRIORITY - 1) // default
.tasksProcessingOrder(QueueProcessingType.FIFO) // default
.denyCacheImageMultipleSizesInMemory()
.memoryCache(new LruMemoryCache(2 * 1024 * 1024))
.memoryCacheSize(2 * 1024 * 1024)
.memoryCacheSizePercentage(13) // default
.diskCache(new UnlimitedDiskCache(cacheDir)) // default
.defaultDisplayImageOptions(normal_options)
.diskCacheSize(50 * 1024 * 1024)
.diskCacheFileCount(100)
.diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
.imageDownloader(new BaseImageDownloader(getApplicationContext())) // default
.imageDecoder(new BaseImageDecoder(true)) // default
.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
.writeDebugLogs()
.build();
ImageLoader.getInstance().init(config);
}
~~~
上面不做多介绍。很简单看字面意思就知道了。我们看看,他支持那些参数设置呢。
com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder类中
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768056c694.jpg "")
小伙伴们根据需求配置就好。
接下来我们看DisplayOption参数的配置
~~~
default_options = new DisplayImageOptions.Builder()
/**
* 这只Uri为空、加载失败、加载时候的图片资源,可以接受Drawable 和 资源ID
*/
.showImageForEmptyUri(R.mipmap.ic_launcher)
.showImageOnFail(R.mipmap.ic_launcher)
.showImageOnLoading(R.mipmap.ic_launcher)
//是否设置在加载之前重置view
.resetViewBeforeLoading(false)
.delayBeforeLoading(1000)
//是否缓存在内存中
.cacheInMemory(false)
//是否缓存在文件中
.cacheOnDisk(false)
// .preProcessor(...)
// .postProcessor(...)
// .extraForDownloader(...)
.considerExifParams(false)
//Image的缩放类型
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
//bitmap 的config
.bitmapConfig(Bitmap.Config.ARGB_8888)
//decode参数
// .decodingOptions()
//设置显示,可以设置为圆角
.displayer(new SimpleBitmapDisplayer())
.handler(new Handler())
.build();
~~~
同样,我们看下可以配置哪些参数。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768058ca48.jpg "")
同样 根据自己的需求设置就好,比如说我这里想设置圆角怎么办
~~~
head_options = new DisplayImageOptions.Builder()
.showStubImage(R.mipmap.ic_launcher)
.showImageOnFail(R.mipmap.ic_launcher)
.showImageForEmptyUri(R.mipmap.ic_launcher)
//设置圆角显示
.displayer(new RoundedBitmapDisplayer(50))
.cacheOnDisk(true)
.cacheInMemory(true)
.build();
~~~
只需要这样就好,那么我们在来看看displayer()可以设置什么吧。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076805a4110.jpg "")
> FadeinBitmapDisplayer 会在显示的时候有个动画效果
RoundBitmapDisplayer 可以设置圆角大小
RoundedVignetteBitmapDisplayer 出了设置圆角外,还可以设置装饰圆角。其实就是加个margin而已
SimpleBitmapDisplay 不带圆角
- **在代码中设置并显示**
使用imageloader的方法显示,他提供了以下方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076805b89bd.jpg "")
这里我们简单看下就好。
一般情况下我们这样就好
~~~
imageLoader.displayImage("http://pic.33.la/20141114bztp/1764.jpg",
normal_image,MyApplication.normal_options);
~~~
如果在options没有设置失败等情况下显示的图片,我们还可以这样
~~~
imageLoader.displayImage("http://www.juzi2.com/uploads/allimg/130524/1_130524115230_1.jpg",
head_image, MyApplication.head_options,
new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
}
}, new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
Log.e("tag","还是可以进来的");
Log.e("tag","imageUri--->"+imageUri+"|||urrent--->"+current+"|||total---->"+total);
tv_progress.setText(current+"/"+total);
}
});
~~~
这里的2个监听器,一个是监听下载状况,另一个是监听下载进度。
关于loadimage()和loadimagesync(),就不说了,也很简单。
这里在多说一个叫做PauseOnScrollListener的类,这个类是让我们判断在listview等,滑动时候取消加载的。
好了,关于Universal-image-loader的用法就介绍这么多了。不喜勿喷
Android四大图片缓存框架之-Fresco之initialize(二)
最后更新于:2022-04-01 15:48:57
我们今天来了解下Fresco的初始化过程。
以系统默认的初始化参数为例。
~~~
Fresco.initialize(getApplicationContext());
~~~
进入到 com.facebook.drawee.backends.pipeline.Fresco中
~~~
public static void initialize(Context context) {
ImagePipelineFactory.initialize(context);
initializeDrawee(context);
}
~~~
我们先看ImagePipelineFactory.initialize(context);方法
~~~
public static void initialize(Context context) {
initialize(ImagePipelineConfig.newBuilder(context).build());
}
~~~
我们继续看ImagePipelineConfig.newBuilder(context)方法,
~~~
public static ImagePipelineConfig.Builder newBuilder(Context context) {
return new ImagePipelineConfig.Builder(context, null);
}
~~~
这里调用了ImagePipelineCongif的一个内部类Builder。
~~~
private Builder(Context context) {
this.mDownsampleEnabled = false;
this.mResizeAndRotateEnabledForNetwork = true;
this.mContext = (Context)Preconditions.checkNotNull(context);
}
~~~
这里只初始化了3个参数。其他的一些相关的参数并没有在这里进行初始化,那么,其他的一些设置是怎么实现的呢。别着急,我们接下来看
ImagePipelineConfig.newBuilder(context).build()的build()方法。
~~~
public ImagePipelineConfig build() {
return new ImagePipelineConfig(this, null);
}
~~~
这里返回了ImagePipelineConfig的一个实例。我们看下他的构造函数吧。
~~~
private ImagePipelineConfig(ImagePipelineConfig.Builder builder) {
this.mAnimatedImageFactory = builder.mAnimatedImageFactory;
this.mBitmapMemoryCacheParamsSupplier = (Supplier)(builder.mBitmapMemoryCacheParamsSupplier == null?new DefaultBitmapMemoryCacheParamsSupplier((ActivityManager)builder.mContext.getSystemService("activity")):builder.mBitmapMemoryCacheParamsSupplier);
this.mCacheKeyFactory = (CacheKeyFactory)(builder.mCacheKeyFactory == null?DefaultCacheKeyFactory.getInstance():builder.mCacheKeyFactory);
this.mContext = (Context)Preconditions.checkNotNull(builder.mContext);
this.mDownsampleEnabled = builder.mDownsampleEnabled;
this.mEncodedMemoryCacheParamsSupplier = (Supplier)(builder.mEncodedMemoryCacheParamsSupplier == null?new DefaultEncodedMemoryCacheParamsSupplier():builder.mEncodedMemoryCacheParamsSupplier);
this.mImageCacheStatsTracker = (ImageCacheStatsTracker)(builder.mImageCacheStatsTracker == null?NoOpImageCacheStatsTracker.getInstance():builder.mImageCacheStatsTracker);
this.mImageDecoder = builder.mImageDecoder;
this.mIsPrefetchEnabledSupplier = builder.mIsPrefetchEnabledSupplier == null?new Supplier() {
public Boolean get() {
return Boolean.valueOf(true);
}
}:builder.mIsPrefetchEnabledSupplier;
this.mMainDiskCacheConfig = builder.mMainDiskCacheConfig == null?getDefaultMainDiskCacheConfig(builder.mContext):builder.mMainDiskCacheConfig;
this.mMemoryTrimmableRegistry = (MemoryTrimmableRegistry)(builder.mMemoryTrimmableRegistry == null?NoOpMemoryTrimmableRegistry.getInstance():builder.mMemoryTrimmableRegistry);
this.mNetworkFetcher = (NetworkFetcher)(builder.mNetworkFetcher == null?new HttpUrlConnectionNetworkFetcher():builder.mNetworkFetcher);
this.mPlatformBitmapFactory = builder.mPlatformBitmapFactory;
this.mPoolFactory = builder.mPoolFactory == null?new PoolFactory(PoolConfig.newBuilder().build()):builder.mPoolFactory;
this.mProgressiveJpegConfig = (ProgressiveJpegConfig)(builder.mProgressiveJpegConfig == null?new SimpleProgressiveJpegConfig():builder.mProgressiveJpegConfig);
this.mRequestListeners = (Set)(builder.mRequestListeners == null?new HashSet():builder.mRequestListeners);
this.mResizeAndRotateEnabledForNetwork = builder.mResizeAndRotateEnabledForNetwork;
this.mSmallImageDiskCacheConfig = builder.mSmallImageDiskCacheConfig == null?this.mMainDiskCacheConfig:builder.mSmallImageDiskCacheConfig;
int decodeThreads = this.mPoolFactory.getFlexByteArrayPoolMaxNumThreads();
this.mExecutorSupplier = (ExecutorSupplier)(builder.mExecutorSupplier == null?new DefaultExecutorSupplier():builder.mExecutorSupplier);
}
~~~
看到这里,就明白了,构造函数中根据判断ImagePipelineConfig.Builder对象的成员变量是否为空来初始化,不为空,则用我们设置好的,为空,那么就用系统的。我们以mBitmapMemoryCacheParamsSupplier为例。
~~~
public class DefaultBitmapMemoryCacheParamsSupplier implements Supplier<MemoryCacheParams> {
private static final int MAX_CACHE_ENTRIES = 256;
private static final int MAX_EVICTION_QUEUE_SIZE = 2147483647;
private static final int MAX_EVICTION_QUEUE_ENTRIES = 2147483647;
private static final int MAX_CACHE_ENTRY_SIZE = 2147483647;
private final ActivityManager mActivityManager;
public DefaultBitmapMemoryCacheParamsSupplier(ActivityManager activityManager) {
this.mActivityManager = activityManager;
}
public MemoryCacheParams get() {
return new MemoryCacheParams(this.getMaxCacheSize(), 256, 2147483647, 2147483647, 2147483647);
}
private int getMaxCacheSize() {
int maxMemory = Math.min(this.mActivityManager.getMemoryClass() * 1048576, 2147483647);
return maxMemory < 33554432?4194304:(maxMemory < 67108864?6291456:(VERSION.SDK_INT <= 9?8388608:maxMemory / 4));
}
}
~~~
看到这些敏感的数字就知道,这里就是配置LruCache大小的地方了。(猜测)。我想,这里会在某个地方调用get方法来获取系统设置的参数。接着看下这些参数的意思。
~~~
public MemoryCacheParams(int maxCacheSize, int maxCacheEntries, int maxEvictionQueueSize, int maxEvictionQueueEntries, int maxCacheEntrySize) {
this.maxCacheSize = maxCacheSize;
this.maxCacheEntries = maxCacheEntries;
this.maxEvictionQueueSize = maxEvictionQueueSize;
this.maxEvictionQueueEntries = maxEvictionQueueEntries;
this.maxCacheEntrySize = maxCacheEntrySize;
}
~~~
- 内存缓存的最大Size
- 缓存的最大条目,应该就是缓存图片的最大数目
- 驱逐队列的Size,(以下是我猜测的内容,有待验证),驱逐队列指的是重Lrucache中淘汰下来的图片,但是近期可能会用到的,暂时存放在这里。
- 驱逐队列的数目
- 单个缓存条目的最大大小
以上纯属个人意见,如有错误,请及时更正。
Android四大图片缓存框架之-Fresco(一)
最后更新于:2022-04-01 15:48:55
本文来自于[Fresco中文文档](http://www.fresco-cn.org/),这仅仅是自己的学习笔记!!!大牛绕路,放我我。
关于Fresco的介绍,请查看[链接](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0402/2683.html)
关于android图片缓存,这是一个android程序员必须了解的。关于四大图片缓存框架的特性与对比,请移步MDCC[传送门](http://www.csdn.net/article/2015-10-21/2825984)
首先说明,本文的大多数内容来自于官方文档,请勿喷!!!
那么今天我们就来了解下Fresco,作为FB出版的开源项目,据说是目前最好的缓存框架。
那么我们就先来了解下Fresco是个什么。
- Fresco是一个强大的图片加载组件
- Fresco中设计有一个叫做image pipeline 的模块。他负责从网络,从本地文件系统,本地资源加载图片。为了最大限度上节省空间和CPU时间,它含有3级缓存的设计(额,没三级能拿出手?)
- Fresco中设计有一个叫做Drawees模块,方便地显示loading图,当图片不再显示在屏幕上时,及时地释放内存和空间占用。
- Fresco支持Android2.3及以上系统
简单的看下使用SimpleDraweeView显示一张占位图。在XML文件中加入
~~~
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
>
</com.facebook.drawee.view.SimpleDraweeView>
~~~
在代码中设置Uri,
~~~
draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");
// draweeView.setController(draweeController);
draweeView.setImageURI(uri);
~~~
最后添加网络权限,就可以了。我们在来看下这里的Uri支持什么格式:
- 远程图片 http:// 或者 https://
- 本地文件 file://
- Content Provider content://
- asset目录下的资源 asset://
- res目录下的资源 res://包名/+R……
接下来便详细的介绍Drawee
举个栗子:
~~~
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="20dp"
fresco:fadeDuration="300"
fresco:actualImageScaleType="focusCrop"
fresco:placeholderImage="@color/wait_color"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImage="@drawable/error"
fresco:failureImageScaleType="centerInside"
fresco:retryImage="@drawable/retrying"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImage="@drawable/progress_bar"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:backgroundImage="@color/blue"
fresco:overlayImage="@drawable/watermark"
fresco:pressedStateOverlayImage="@color/red"
fresco:roundAsCircle="false"
fresco:roundedCornerRadius="1dp"
fresco:roundTopLeft="true"
fresco:roundTopRight="false"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="true"
fresco:roundWithOverlayColor="@color/corner_color"
fresco:roundingBorderWidth="2dp"
fresco:roundingBorderColor="@color/border_color"
/>
~~~
上面这个是官方的栗子,上面有所有的属性,我们就来看看,这些属性代表什么意思,
- layout_width和layout_height 不支持warp_content,但是可以通过setAspectRetio();来设置宽高比
- fadeDuration() 淡..时间
- actualImageScaleType 设置图片缩放,通常使用foucsCrop,该属性值会通过算法把人头像放在中间,关于缩放,请移步[黑洞](http://www.fresco-cn.org/docs/scaling.html#_)
- placeholderImage 下载成功之前显示的图片
- placeholderImageScaleType
- failureImage 加载失败时显示的图片
- failureImageScaleType
- retryImage 加载失败,提示用户点击重新加载的图片
- retryImageScaleType
- progressBarImage 提示 用户正在加载,和进度无关
- progressBarImageScaleType
- progressBarAutoRotateInterval 图片自动旋转的时间间隔
- backgroundImage 背景
- overlayImage 叠加图
- pressedStateOverlayImage 按下时候的叠加图
- roundAsCircle 是否涉及圆圈
- roundedCornerRadius 圆角
- roundTopLeft、roundTopRight….. 分别设置4个角不同半径,设置为true以后可以再代码中设置角度。通过RoundingParams的setConnersRadii()方法
~~~
public RoundingParams setCornersRadii(float topLeft, float topRight, float bottomRight, float bottomLeft) {
float[] radii = this.getOrCreateRoundedCornersRadii();
radii[0] = radii[1] = topLeft;
radii[2] = radii[3] = topRight;
radii[4] = radii[5] = bottomRight;
radii[6] = radii[7] = bottomLeft;
return this;
}
~~~
- roundWithOverlayColor,边框的叠加颜色
- roundingBorderWidth 边框宽度
-
roundingBorderColor 边框颜色
我们看到,仅仅靠xml文件的属性,功能就已经很强大了。我们通过什么来在代码中设置各种效果呢?看代码
~~~
GenericDraweeHierarchyBuilder builder =
new GenericDraweeHierarchyBuilder(getResources());
GenericDraweeHierarchy hierarchy = builder
.setFadeDuration(300)
.setPlaceholderImage(new MyCustomDrawable())
.setBackgrounds(backgroundList)
.setOverlays(overlaysList)
.build();
mSimpleDraweeView.setHierarchy(hierarchy);
~~~
DraweeHierarchy的一些属性可以在运行时改变。
~~~
GenericDraweeHierarchy hierarchy =mSimpleDraweeView.getHierarchy();
hierarchy.setPlaceholderImage(R.drawable.placeholderId);
~~~
注意:对于同一个View,不要多次调用setHierarchy。
我们看看GenericDraweeHierarchy提供了哪些set方法
![看这里看这里](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_57076805010fa.jpg "")
看到这里有相当多的drawable啊。我们在来看看,Fresco源码中有哪些Drawable,
![drawable](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_570768052009d.jpg "")
我们在set…(new ..Drawable),就可以轻松使用了。我们以ProgressBarDrawable为例,看看他提供了哪些方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680538312.jpg "")
,看起来还是很强大的。当然,我们可以自定义Drawable,关于自定义Drawable,我这里就不再说了,网上还是很多的。
我们在来看看ControllerBuilder,从字面上就可以看出,这个可以对图片做出一些控制。
~~~
ControllerListener listener = new BaseControllerListener() {...}
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setTapToRetryEnabled(true)
.setOldController(mSimpleDraweeView.getController())
.setControllerListener(listener)
.build();
mSimpleDraweeView.setController(controller);
~~~
在指定一个新的controller的时候,使用setOldController,可以节省不必要的内存分配。
我们来看看Fresco.newDraweeControllerBuilder()有哪些set方法。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-08_5707680553c8f.jpg "")
自定义图片加载请求
~~~
Uri uri;
Postprocessor myPostprocessor = new Postprocessor() { ... }
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(myPostprocessor)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
// 其他设置
.build();
~~~
渐进式JPEG图
~~~
ProgressiveJpegConfig pjpegConfig = new ProgressiveJpegConfig() {
@Override
public int getNextScanNumberToDecode(int scanNumber) {
return scanNumber + 2;
}
public QualityInfo getQualityInfo(int scanNumber) {
boolean isGoodEnough = (scanNumber >= 5);
return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);
}
}
ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
.setProgressiveJpegConfig(pjpeg)
.build();
~~~
~~~
Uri uri;
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
Gif动态图
~~~
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
. // other setters
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setAutoPlayAnimations(true)
. // other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
手动控制动态图播放
~~~
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (anim != null) {
// app-specific logic to enable animation starting
anim.start();
}
};
Uri uri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setControllerListener(controllerListener)
// other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
使用动画
~~~
Animatable animation = mSimpleDraweeView.getController().getAnimatable();
if (animation != null) {
// 开始播放
animation.start();
// 一段时间之后,根据业务逻辑,停止播放
animation.stop();
}
~~~
多图请求以及图片复用
~~~
Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
缩略图预览
~~~
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
本地图片复用
~~~
Uri uri1, uri2;
ImageRequest request = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = { request1, request2 };
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
监听下载事件
~~~
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (imageInfo == null) {
return;
}
QualityInfo qualityInfo = imageInfo.getQualityInfo();
FLog.d("Final image received! " +
"Size %d x %d",
"Quality level %d, good enough: %s, full quality: %s",
imageInfo.getWidth(),
imageInfo.getHeight(),
qualityInfo.getQuality(),
qualityInfo.isOfGoodEnoughQuality(),
qualityInfo.isOfFullQuality());
}
@Override
public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
FLog.d("Intermediate image received");
}
@Override
public void onFailure(String id, Throwable throwable) {
FLog.e(getClass(), throwable, "Error loading %s", id)
}
};
Uri uri;
DraweeController controller = Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
// other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
修改图片尺寸
~~~
Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";
int width = 50, height = 50;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(mDraweeView.getController())
.setImageRequest(request)
.build();
mSimpleDraweeView.setController(controller);
~~~
自动旋转
~~~
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.build();
~~~
图片的修改。后处理器
~~~
Uri uri;
Postprocessor redMeshPostprocessor = new BasePostprocessor() {
@Override
public String getName() {
return "redMeshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, Color.RED);
}
}
}
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(redMeshPostprocessor)
.build();
PipelineDraweeController controller = (PipelineDraweeController)
Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
// other setters as you need
.build();
mSimpleDraweeView.setController(controller);
~~~
图片请求 Image Requests
~~~
Uri uri;
ImageDecodeOptions decodeOptions = ImageDecodeOptions.newBuilder()
.setBackgroundColor(Color.GREEN)
.build();
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(new ResizeOptions(width, height))
.build();
~~~
上面我们说了Deawee,下面我们说下Image Pipeline
配置代码
~~~
ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
.setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
.setCacheKeyFactory(cacheKeyFactory)
.setEncodedMemoryCacheParamsSupplier(encodedCacheParamsSupplier)
.setExecutorSupplier(executorSupplier)
.setImageCacheStatsTracker(imageCacheStatsTracker)
.setMainDiskCacheConfig(mainDiskCacheConfig)
.setMemoryTrimmableRegistry(memoryTrimmableRegistry)
.setNetworkFetchProducer(networkFetchProducer)
.setPoolFactory(poolFactory)
.setProgressiveJpegConfig(progressiveJpegConfig)
.setRequestListeners(requestListeners)
.setSmallImageDiskCacheConfig(smallImageDiskCacheConfig)
.build();
Fresco.initialize(context, config);
~~~
是在是太多了,我也懒的写了。
参考资料:[中文文档](http://www.fresco-cn.org/)
前言
最后更新于:2022-04-01 15:48:53
> 原文出处:[Android开源框架学习系列](http://blog.csdn.net/column/details/android222.html)
作者:[qq_21430549](http://blog.csdn.net/qq_21430549)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# Android开源框架学习系列
> 总结自己在学习android开源框架的笔记