0%

在使用MediaRecorder录制视频流的时候,突然发现有个LocalSocket类,兴趣使然就研究了下,为了避免遗忘,整理成博文记录下来。

阅读全文 »

最近在研究Hybrid的相关东西,其中重要一项就是JS与Java的相互调用以及传递参数,传统的方法是通过Webview的addJavascriptInterface方法来注入一个JS全局对象,但这样容易产生很大的安全隐患。比较好的方法就是在加载网页的过程中动态注入一段JS脚本,JSBridge这个开源库就是利用此种方法,此库支持异步回调,方法参数支持js所有已知的类型,包括number、string、boolean、object、function;Java层方法可以返回void或能转为字符串的类型(如int、long、String、double、float等)或可序列化的自定义类型。下面将仔细分析它的工作原理。

阅读全文 »

最近在研究CoordinatorLayout和Behavior的相关特性,在泡网看到Behavior实现的简书快速返回效果,觉得实现相对简单,突然灵机一动,想到用Behavior来做RecyclerView的粘性头部,通过不断尝试发现Behavior中获得的滑动距离不大准确,并不是我想要的。于是果断放弃此路,通过监听RecyclerView的滑动来实现。

阅读全文 »

Retrofit2是square公司出品的一个网络请求库,目前非常流行,特别适合于rest请求。网上也有不少介绍该库的文章,但别人的终究是别人的,还需要转化为自己的才行。正所谓“纸上得来终觉浅,绝知此事要躬行”,本着学习的态度笔者对retroift2的用法进行了下列研究,主要包括以下几个方面

  • get请求
  • post请求(包括key/value,以及body)
  • 文件上传(进度监听)
  • 文件下载
  • 与RxJava整合

单独使用Retrofit2

引入类库

1
2
3
4
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

构造http接口类

Retrofit2可以根据一个服务接口类,利用jdk动态代理生成它的相应实现。

1
2
3
4
5
6
retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
myService=retrofit.create(MyService.class);

生成了代理类之后,就可以进行相应请求了

Get请求

1
2
3
@GET("rest/findUserForGet")
Call<User> findUserForGet(@Query("id") int id, @Query("username") String username,@Query("address") String address);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Call<User> userCall=myService.findUserForGet(12,"张明明","北京海淀区");
userCall.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
//主线程
Log.e(TAG,Thread.currentThread().getName());
Log.e(TAG,response.body().toString());

}

@Override
public void onFailure(Call<User> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

需要注意Query注解不能丢,即使形参和请求的key相同也要加上,否则报错;另外回调函数发生在主线程,可以进行UI相关的操作

Post请求(key/value)

1
2
3
4
@FormUrlEncoded
@POST("rest/findUserForPost")
Call<ResponseBody> findUserForPost(@Field("id") int id, @Field("username") String username,@Field("address") String address);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Call<ResponseBody> userCall=myService.findUserForPost(9,"陈玄功","恶人谷");
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e(TAG,getResponsString(response.body()));

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

Post请求(body体)

1
2
3
 
@POST("rest/postBodyJson")
Call<User> postBodyJson(@Body User user);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
User user=new User();
user.setId(2);
user.setUsername("李明");
user.setBirthday("1995-09-06 09-09-08");
user.setSex("1");
Call<User> userCall=myService.postBodyJson(user);
userCall.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
Log.e(TAG,response.body().toString());
}

@Override
public void onFailure(Call<User> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

此种方式会默认加上Content-Type: application/json; charset=UTF-8的请求头,即以JSON格式请求,再以JSON格式响应。

单个文件上传

1
2
3
4
5

@Multipart
@POST("rest/upload")
Call<ResponseBody> upload(@Part("username") RequestBody username,@Part("address") RequestBody address,
@Part MultipartBody.Part file);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

File file=new File(Environment.getExternalStorageDirectory(),"测试01.jpg");

//普通key/value
RequestBody username =
RequestBody.create(
MediaType.parse("multipart/form-data"), "jim");

RequestBody address =
RequestBody.create(
MediaType.parse("multipart/form-data"), "天津市");

//file
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);


//包装RequestBody,在其内部实现上传进度监听
CountingRequestBody countingRequestBody=new CountingRequestBody(requestFile, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,contentLength+":"+bytesWritten);
}
});


MultipartBody.Part body =
MultipartBody.Part.createFormData("file", file.getName(), countingRequestBody);


Call<ResponseBody> userCall=myService.upload(username, address, body);
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e(TAG, getResponsString(response.body()));

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});

多文件上传

1
2
3
4

@Multipart
@POST("rest/upload")
Call<ResponseBody> uploads(@PartMap Map<String, RequestBody> params);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//必须使用LinkedHashMap,保证文件按顺序上传
Map<String,RequestBody> params=new LinkedHashMap<>();
File file1=new File(Environment.getExternalStorageDirectory(),"测试01.jpg");
RequestBody filebody1 =RequestBody.create(MediaType.parse("multipart/form-data"), file1);
//记录文件上传进度
CountingRequestBody countingRequestBody1=new CountingRequestBody(filebody1, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,"file1:"+contentLength+":"+bytesWritten);
}
});
//file代表服务器接收到的key,file1.getName()代表文件名
params.put("file\";filename=\""+file1.getName(),countingRequestBody1);


File file2=new File(Environment.getExternalStorageDirectory(),"girl.jpg");
RequestBody filebody2 =RequestBody.create(MediaType.parse("multipart/form-data"), file2);
CountingRequestBody countingRequestBody2=new CountingRequestBody(filebody2, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,"file2:"+contentLength+":"+bytesWritten);
}
});
params.put("file\";filename=\""+file2.getName(),countingRequestBody2);


File file3=new File(Environment.getExternalStorageDirectory(),"测试02.jpg");
RequestBody filebody3 =RequestBody.create(MediaType.parse("multipart/form-data"), file3);
CountingRequestBody countingRequestBody3=new CountingRequestBody(filebody3, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,"file3:"+contentLength+":"+bytesWritten);
}
});
params.put("file\";filename=\""+file3.getName(),countingRequestBody3);

//普通key/value
params.put("username", RequestBody.create(
MediaType.parse("multipart/form-data"), "jim"));
params.put("address", RequestBody.create(
MediaType.parse("multipart/form-data"), "天津市"));

Call<ResponseBody> userCall=myService.uploads(params);
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e(TAG, getResponsString(response.body()));

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});

文件下载

1
2
3
@Streaming
@GET("image/{filename}")
Call<ResponseBody> downFile(@Path("filename") String fileName);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Call<ResponseBody> userCall=myService.downFile(fname);
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {

String fileName=Environment.getExternalStorageDirectory()+"/"+fname;
FileOutputStream fos=new FileOutputStream(fileName);
InputStream is=response.body().byteStream();

byte[] buf=new byte[1024];
int len;
while ((len=is.read(buf))!=-1){
fos.write(buf,0,len);
}
is.close();
fos.close();

}catch (Exception ex){
Log.e(TAG,ex.getMessage());
}
Log.e(TAG,"success");

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

开启OKHttp的日志拦截

Retrofit2底层还是使用的OKHttp,可以使用其相关的一些特性,比如开启日志拦截,此时就不能使用Retrofit2默认的OKHttp实例,需要自己单独构造,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

httpClient.addInterceptor(logging);

retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();

开启日志后,会记录request和response的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: --> POST http://192.168.1.104:8080/mobile/rest/postBodyJson http/1.1
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: Content-Type: application/json; charset=UTF-8
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: Content-Length: 71
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: {"birthday":"1995-09-06 09-09-08","id":2,"sex":"1","username":"李明"}
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: --> END POST (71-byte body)
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: <-- 200 OK http://192.168.1.104:8080/mobile/rest/postBodyJson (79ms)
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Server: Apache-Coyote/1.1
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Access-Control-Allow-Origin: *
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Content-Type: application/json;charset=UTF-8
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Transfer-Encoding: chunked
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Date: Sat, 14 May 2016 07:00:42 GMT
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: OkHttp-Sent-Millis: 1463209242134
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: OkHttp-Received-Millis: 1463209242206
05-14 03:00:42.208 5212-25519/com.bryan D/OkHttp: {"id":2,"username":"李明","sex":"1","birthday":"1995-09-06","address":null}
05-14 03:00:42.208 5212-25519/com.bryan D/OkHttp: <-- END HTTP (77-byte body)

Retrofit2与RxJava整合

引入类库

1
2
3
4
5
6
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

构造http接口类

Retrofit2可以根据一个服务接口类,利用jdk动态代理生成它的相应实现。

1
2
3
4
5
6
7
8
retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(httpClient.build())
.build();
myService=retrofit.create(MyRxService.class);

Get请求

1
2
3
4

@GET("rest/findUserForGet")
Observable<User> findUserForGet(@Query("id") int id, @Query("username") String username,@Query("address") String address);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
myService.findUserForGet(12,"张明明","北京海淀区")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<User>() {

@Override
public void onStart() {
super.onStart();
Log.e(TAG,"onStart");
}

@Override
public void onCompleted() {
Log.e(TAG,"onCompleted");
}

@Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage());
}

@Override
public void onNext(User user) {
Log.e(TAG,user.toString());
}
});

Post请求(key/value)

1
2
3
@FormUrlEncoded
@POST("rest/findUserForPost")
Observable<ResponseBody> findUserForPost(@Field("id") int id, @Field("username") String username, @Field("address") String address);

实现和Get类似

多文件上传

1
2
3
4
@Multipart
@POST("rest/upload")
Observable<ResponseBody> uploads(@PartMap Map<String, RequestBody> params);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//params构造部分和单独使用Retrofit2的构造相同
myService.uploads(params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ResponseBody>() {

@Override
public void onStart() {
super.onStart();
Log.e(TAG, "onStart");
}


@Override
public void onCompleted() {
Log.e(TAG, "onCompleted");
}

@Override
public void onError(Throwable e) {
Log.e(TAG, e.getMessage());
}

@Override
public void onNext(ResponseBody body) {
Log.e(TAG, getResponsString(body));
}
});

文件下载

1
2
3
4
@Streaming
@GET("image/{filename}")
Observable<ResponseBody> downFile(@Path("filename") String fileName);

总结

无论是单独使用Retrofit2还是整合RxJava一起使用,请求体的构造部分并没有多大变化,主要区别是RxJava支持链式写法,可以对response作更为复杂的处理。现实业务中大多数也是两者结合起来使用。

Github Demo

Calladv

此App支持在来电和去电时显示广告。

  1. 来电:响铃即触发屏显广告,挂断就消失
  2. 去电:打去时触发屏显广告,挂断就消失
  3. 广告图可任意移动
  4. 按下返回键,home键,recent键,屏显广告自动消失

效果图

原理

原理比较简单,监听来去电广播,再启动service显示悬浮窗,
此悬浮窗在API>=19 不需要额外权限,API<19需要SYSTEM_ALERT_WINDOW权限

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//声明service和broadcastreceiver即可
<service
android:name=".core.PhoneService"
android:process=":ui"
/>
<receiver android:name=".core.PhoneBroadcast">
<intent-filter android:priority="1000">
<!-- 系统启动完成后会调用 -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
<!-- 解锁完成后会调用 -->
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>

GitHub地址

Calladv

致谢

liaohuqiu (https://github.com/liaohuqiu/android-UCToast)

SimpleHttp

  1. 使用原生UrlConnection(无第三方依赖)
  2. 支持HTTPS
  3. 支持同步和异步
  4. 自动解析实体对象(默认使用fastjson)

用法

1
compile 'com.bryan:simplehttp:1.1.0'

同步Get请求

1
2
3
String result=new SimpleHttpRequest.Builder()
.url("http://www.baidu.com")
.getSync(String.class);

异步Get请求

1
2
3
4
5
6
7
8
9
final List<FormParam> formParams=new ArrayList<>();
formParams.add(new FormParam("id","12"));
formParams.add(new FormParam("username","张明明"));
formParams.add(new FormParam("address","北京海淀区"));

new SimpleHttpRequest.Builder()
.url(BASE_URL+"/rest/findUserForGet")
.params(formParams)
.get(userCallBack);

异步Post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
RequestCallback<User> userCallBack=new RequestCallback<User>() {
@Override
public void onSuccess(User user) {
webContent.setText(Html.fromHtml(user.toString()));
Log.e(TAG,user.toString());
}

@Override
public void onError(Exception e) {
webContent.setText(Html.fromHtml(e.getMessage()));
Log.e(TAG, e.getMessage());
}

};

new SimpleHttpRequest.Builder()
.url(BASE_URL+"/rest/findUserForPost")
.addParam("id","9")
.addParam("username","陈玄功")
.addParam("address","恶人谷")
.post(userCallBack);

Post请求json

1
2
3
4
5
6
7
String json="{\"id\":2,\"username\":\"李明\",\"birthday\":\"1995-09-06 09-09-08\",\"sex\":\"1\"}";
new SimpleHttpRequest.Builder()
.url(BASE_URL+"/rest/postBodyJson")
.contentType("application/json;charset=utf-8")
.content(json)
.post(userCallBack);

下载(支持下载进度)

1
2
3
4
5
6
7
SimpleHttpRequest request=new SimpleHttpRequest.Builder()
//.url(BASE_URL+"/image/测试01.jpg")
// .url(BASE_URL+"/image/测试02.jpg")
.url(BASE_URL+"/image/girl.jpg")
.destFileDir(Environment.getExternalStorageDirectory().getAbsolutePath())
.destFileName(null)
.download(stringCallBack);

文件上传(支持上传进度)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<FileParam> fileParams=new ArrayList<>();
fileParams.add(new FileParam("file",null,new File(
Environment.getExternalStorageDirectory(),"测试01.jpg")));
fileParams.add(new FileParam("file",null,new File(
Environment.getExternalStorageDirectory(),"测试02.jpg")));
fileParams.add(new FileParam("file",null,new File(
Environment.getExternalStorageDirectory(),"girl.jpg")));
new SimpleHttpRequest.Builder()
.url(BASE_URL+"/rest/upload")
.addParam("id","5")
.addParam("username","刘冰")
.addParam("address","天津市")
.files(fileParams)
.upload(stringCallBack);

添加header

1
2
3
4
SimpleHttpRequest request=new SimpleHttpRequest.Builder()
.addHeader("content-Type","text/plain")
.addHeader("Connection","keep-alive")
......

自定义CallBack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RequestCallback<String> stringCallBack = new RequestCallback<String>() {
@Override
public void onSuccess(String response) {
webContent.setText(Html.fromHtml(response));
Log.e(TAG, response);
}

@Override
public void onError(Exception e) {
webContent.setText(Html.fromHtml(e.getMessage()));
Log.e(TAG,e.getMessage());
}

@Override
public void onProgress(long total, long current, boolean isUploading) {
Log.e(TAG,"total:"+total+",current:"+current+",isUploading:"+isUploading);
}

@Override
public void onCancel() {
webContent.setText("onCancel");
Log.e(TAG, "onCancel");
}
};

取消单个请求

1
2
SimpleHttpRequest request= new SimpleHttpRequest.Builder()...
request.cancel();

GitHub地址

SimpleHttp