JNI-Tips-提要

引:
项目需要用到JNI,实际上还需要在native跟Java之间写一些代码,来连接二位,需要做进一步了解。本文主要对官方的JNI Tips做一些简单的关键提取
参考:
JNI Tips


Evernote连接

XMind 文档下载

JNI Tips.png

文本:

JNI Tips

definition
Java Native Interface
how it works
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
JavaVM and JNIEnv
JavaVM
one process can only create one JavaVM in Android
JNIEnv
provides most of JNI functions. Your native functions all receive a JNIEnv as first argument
used for thread-local, so you cannot share a JNIEnv between threads (GetEnv can be used to discover the thread’s JNIEnv when no other way)
Threads
a thread will have no JNIEnv unless it’s attached, before this it cannot make JNI calls (all threads are Linux threads, scheduled by the kernel)
Threads attached through JNI must call DetachCurrentThread before they exit
jclass, jmethodID, and jfieldID
steps to access an object’s field from native code
FindClass:—> get the class object reference for the class
GetFieldID:—>Get the field ID for the field with GetFieldID
GetIntField:—>Get the contents of the field with something appropriate
call a method—> get a class object reference and then a method ID
it’s useful to look the values up once and cache the results in your native code. Because there is a limit of one JAVAVM per process
if want to cache IDs when a class is loaded, add a piece of code that allow the native code to cache field offsets
Local and Global Reference
almost every object returned by a JNI function is a “local reference”—>it’s valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid
the only way to get non-local references is via the functions NewGlobalRef and NewWeakGlobalRef (the global reference is guaranteed to be valid until you call DeleteGlobalRef)
to see if two reference refer to the same object, you must use the IsSameObject function, never use == in native code
jfieldIDs, jmethod are opaque types, not object references, should not be passed to NewGlobalRef
if you attach a native thread with AttachCurrentThread, the code you are running will never automatically free local references until the thread detaches. Any local references you create will have to be deleted manually.
UTF-8 and UTF-16 Strings
java use UTF-16, JNI provides methods that work with Modified UTF-8 as well.
you cannot pass arbitrary UTF-8 data to JNI and expect it to work correctly
Android currently does not require a copy in GetStringChars,however GetStringUTFChars requires an allocation and a conversion to UTF-8. UTF-16 strings are not zero-terminated, \u0000 is allowed, so you need to hang on to the string length like jchar pointer
Don’t forget to Release the strings you Get. the string functions return jchar or jbyte
Data passed to NewStringUTF must be in Modified UTF-8 format
Primitive Arrays
You must Release every array you Get
release mode arguments alternative values
0
buffer freed
JNI_COMMIT
do nothing/ buffer not freed
JNI_ABORT
buffer freed, Earlier wirtes are not aborted
Region calls
advantages
reducing overhead by reducing JNI call requirements
do not require pinning or extra data copies
reduces the risk of programmer error–> no risk of forgetting to call Release
Exception
your must not call JNI functions while an exception is pending, except several special kinds of API: delete/ exception process/stack process/ release process …
Extended Checking
JNI does very little error checking ~~!
Android offers a mode called CheckJNI
array, bad pointers, Class names, Critical call,Direct ByteBuffers, Exceptions, JNIEnvs, jmethodIDs, References,Release modes, Type safety, UTF-8
Native Libraries
steps
call System.loadLibrary
provide a native function : jint JNI_OnLoad(JavaVM
vm void* reserved)
in JNI_OnLoad, register all your native methods
64-bit Considerations
you need to stash your native pointers in a long field rather than an int
Unsupported Features/ Backwards Compatibility
Define class
dynamic lookup of native functions
detaching threads
weak global references
Local references
determining reference type with GetObjectRefType


关注公众号“夕识”,雕刻时光,不负流年

declare-styleable-枚举format简介

旨:介绍styleable enum format的使用方法

技术含量:低
(项目中对字体的自定义类型较多,考虑将字体设置属性定义成styleable,方便直接在xml中设置)

首先定义attr.xml中的styleable:

attr_define.png

Code Glance (类型设置为enum)

1
2
3
4
5
6
7
8
9
<declare-styleable name="Font">
<attr name="font_type" format="enum">
<enum name="msyh" value="0"/>
<enum name="unidream_led" value="1"/>
<enum name="helvetica" value="2"/>
<enum name="bank" value="3"/>
<enum name="ping_fang" value="4"/>
</attr>
</declare-styleable>

之后就可以在xml中直接利用font_type属性了:

attr_enum.png

在具体的类中解析:

attr_parse.png

Code Glance

1
2
3
4
5
6
7
8
9
if (attrs != null) {
TypedArray ar = context.getTheme().obtainStyledAttributes(attrs, R.styleable.Font, defStyleAttr, 0);
@FontMaster.Type
int fontType = ar.getInteger(R.styleable.Font_type, FontMaster.Type.Default);
FontMaster.font(fontType).set(this);
ar.recycle();
} else {
FontMaster.font(FontMaster.Type.MSYH).set(this);
}


关注公众号“夕识”,雕刻时光,不负流年

VectorDrawable介绍


今天视频处理中碰到一些简单图片的vector绘制方案
调研结果:

效果:

vector paint eg.png

优点:Vector与PNG文件相比,能极大地缩小文件占用空间;
弱势

  1. 兼容性处理起来需要花费一些心思;
  2. 复杂图片不适合运用vector绘制,一个是初始化效率,第二个是重复使用时bitmap在GPU中会有缓存,而vector没有;
  3. 需要额外的一些辅助工具协作生成path;

例注:

  1. 使用:
    需要在Gradle 中添加:android.defaultConfig域
    1
    vectorDrawables.useSupportLibrary = true
  1. XML attributes:
    1
    viewportHeight \ viewportWidth

划分的比例大小(图片中的例子:36dp大小的图像划分成24份,Path中的坐标建立在划分后的坐标系统之上)
意义-->将大小与path分离


Path指令:
M = moveto(M X,Y)
L = lineto(L X,Y)
H = horizontal lineto(H X)
V = vertical lineto(V Y)
C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线
S = smooth curveto(S X2,Y2,ENDX,ENDY)
Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线
T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射
A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
Z = closepath()


工具:
to vector
SVG editor


参考:(涵盖由头、Vector语法、转换工具、原理、兼容处理、性能考量等方面知识,良心之作)
Android Vector曲折的兼容之路


关注公众号“夕识”,雕刻时光,雕刻思维

Clean-Architecture--初析

本篇承接 _上文_
参考:

  1. Architecting Android…The clean way?
  2. Clean Architeture Sample -By android10

色彩简化后的结构图:
ease_clean_architecture.png

想表达的是依赖层级,从这幅图可以看出,模块之间的依赖关系在理想情况下是清晰的,也不存在多维度相互依赖的问题,最外层UI依赖Presenter层,Presenter层又依赖Use Cases层,以此类推… (每层的依赖只能向内收缩

当然,这里的层级内涵只是举例示意,在实际业务中需要根据需要设定各层核心,层级数也可适量调整,整个架构强调的只是一种依赖收缩的思想


一个例子:
clean_architecture_android_schema.png
架构分为三层:

1. Presentation
2. Domain
3. Data

Presentation
clean_architecture_mvp.png
要点即是大家熟知的MVP一类;
Android的代码应该全部都出现在这一层

Domain
clean_architecture_domain.png
业务逻辑层,代码应该是纯粹的JAVA API,与Android无关

Data
clean_architecture_data.png
这一层主要是处理数据业务

几张图其实能很清楚地说明Clean Architecture的立意、用法


关注公众号“夕识”,雕刻时光,雕刻思维

Android-Clean-Architecture推介

A detailed guide on developing Android apps using the Clean Architecture pattern

本文为Clean Architecture在Android App开发中的guideline


Bob大叔所作Clean Architecture结构图如下:
clean architecture.jpg

使用Clean Architecture具有如下优点:

1. 独立于Frameworks
2. 易测试.
3. 独立于UI.
4. 独立于Database.
5. 独立于外部 agency.

Android应用常用的3层架构:

Outer: Implementation layer  实现层
Middle: Interface adapter layer 接口适配层
Inner: Business logic layer 业务逻辑层(——应可用独立于Android的测试用例进行测试)

分别为:

  1. Outer layer
    UI、Storage、Network等
  2. Middle layer
    Presenters、Converters
  3. Inner layer
    POJOs、Interactors、Model、Repositories、Executors

识别二维码,关注公众号“夕识”

AS添加JNI支持

JNI入坑

索引:

  1. 旧 NDK 使用姿势(AS 2.2 以下 参考链接:Create hello-JNI with android studio)
  2. 新 NDK 使用方法(AS 2.2及以上 参考链接:Add C and C++ Code to Your Project)

参考:
JNI Tips
(链接均需VPN)


旧 NDK-build:

简单来说分这么几步:

  1. 添加ndk库支持
  2. 在 gradle.properties中添加:

    1
    android.useDeprecatedNdk=true
  3. 在 app级build.gradle中添加:

    1
    2
    3
    4
    5
      defaultConfig {
    ndk {
    moduleName "test-jni" // name the jni lib you want to add
    }
    }
  4. 创建load 静态块,添加待实现native api

    1
    2
    3
    4
    5
    6
    7
    public class JniTest {
    static {
    System.loadLibrary("test-jni");
    }

    public native String getMsgFromJni();
    }

在 目标api所在行停留一会,会出现红色灯泡提示按钮,点击图标,会有Create Function Java_%full class name%_getMsgFromJni选项出现;点击之后会自动创建一个与java目录并列的cpp目录,并在目录下生成test-jni.c文件
具体代码如下:

1
2
3
4
5
6
7
8
9
10
#include <jni.h>

JNIEXPORT jstringJNICALL
Java_com_zealens_face_jnitest_JniTest_getMsgFromJni(JNIEnv
*env, jobject instance)
{

// TODO
return (*env)->NewStringUTF(env, returnValue);
}

说明:

  1. 对于第3行代码:
    AS生成的代码有错误,需将JNICALL与前面的jstring隔开;
  2. 将returnValue修改为常量值(eg:”hello”)

至此,build之后调用API就能得到刚才填入的hello值。


####新CMake

AS现在默认的C++工具已经修改为CMake(需要在SDK-Tools中安装CMake、LLDB、NDK),旧的ndk-build库也可以手动导入

准备工作:

  1. 如果有则移除:
    1
    2
    // Remove this line
    android.useDeprecatedNdk = true
  1. install build tools

(略:创建新的C++support Application)

  1. 向现有项目添加C/C++ 代码:
    a. 新建cpp目录;
    b. 添加cpp文件,代码如下:
1
2
3
4
5
6
7
8
9
10
11
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_%your test class full name%_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

在欲链接cpp的根目录中(与build.gradle同级)创建文件:CMakeLists.txt,键入如下代码:(各API含义见参考链接末尾部分,或 CMake command

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
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
native-lib

# Links the target library to the log library
# included in the NDK.
${log-lib} )

之后,在同一模块目录上单击右键,选择link c++...,选中欲链接的cpp文件,build.gradle就会生成如下代码:(android模块中)

1
2
3
4
5
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

此时再调用相应API就可以看到效果了:

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

public class CppTest {
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();

// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
}

pay attention to the lib name

高阶设置请参考手册,这里也只是介绍一个最小的CMake环境


关注公众号“夕识”,雕刻时光,雕刻思维

retrofit2


作者: Jake Wharton

原文引自:link

Retrofit 作为简化 HTTP 请求的库,已经运行多年,2.0版本依然不辱使命的在做这些事情。不过 2.0 版本修复了一些长期影响开发者的设计,还加入了前所未有的强大特性。在 NYC 2015 的这一个分享中,Jake Wharton 的演讲涵盖了所有 Retrofit 2.0 的新特性,全面介绍了 Retrofit 2.0 工作原理。



Save the date for Droidcon SF in March — a conference with best-in-class presentations from leaders in all parts of the Android ecosystem.


简介 (0:00);)

我叫 Jake Wharton,现在在 Square 工作。一个天真的人曾经说过:”Retrofit 2 将会在今年年底前放出。”,那个人,就是去年在纽约 DroidCon 上表态的我。然而,事实是 Retrofit 2 将会在今年年底放出,这次我保证!

Retrofit 5年前就开源了,是 Square 最早的开源项目之一。一开始的时候,Retrofit 只是我们用在各个开源项目里的福袋:比如说最早里面有晃动检测功能,HTTP Client,还有现在的 tap 库。多数功能都是 Bob Lee 完成的,我大概 3 年前开始接管这些工作。最终历经 3 年,完成了 1.0 版本,然后彻底开源。从那会儿到现在,已经 release 了 18 个版本了。

Retrofit 1 不错的地方 (2:23);)

Retrofit 里已经有很多不错的特性了。Retrofit 可以利用接口,方法和注解参数(parameter annotations)来声明式定义一个请求应该如何被创建。比如说,下面是一个如何请求 GitHub API 的例子:

1
2
3
4
5
6
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
List<Contributor> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

Retrofit 背后的 HTTP client,以及序列化机制(JSON/XML 协议)都是可替换(pluggable)的,因此你可以选择合适自己的方案。Retrofit 最早出来的时候,只支持 Apache 的 HTTP client。在 1.0 放出前,我们增加了 URL connection,以及 OkHttp 的支持。如果你想要加入的其他的 HTTP client,都可以简单的加入。这个特性非常赞,让我们有能力去支持不同的自定义 client。

1
2
3
4
5
builder.setClient(new UrlConnectionClient());
builder.setClient(new ApacheClient());
builder.setClient(new OkClient());

builder.setClient(new CustomClient());

序列化功能也是可替换的。默认是用的 GSON,你当然也可以用 Jackson 来替换掉。

1
2
3
4
5
6
7
8
9
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
List<Contributor> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

builder.setConverter(new GsonConverter());
builder.setConverter(new JacksonConverter());

如果你在用某些数据交换协议,比如 protocol buffer,Retrofit 也支持 Google 的 protobuf,也包括 XML 协议的转换(如果你自己不怕折腾)。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
ContributorResponse repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

builder.setConverter(new ProtoConverter());
builder.setConverter(new WireConverter());

builder.setConverter(new SimpleXMLConverter());

builder.setConverter(new CustomConverter());

序列化部分跟 client 部分一样,都是可替换的。你如果想要引入或者实现自己的序列化组件,完全没有问题。

在发请求的实现上,你可以用的方法有很多,比如:同步发送请求——

1
2
3
4
5
6
7
8
9
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
List<Contributor> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

List<Contributor> contributors =
gitHubService.repoContributors("square", "retrofit");

——和异步发送,他们之间的区别就是异步发送要在最后一个参数上声明一个 callback 回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
void repoContributors(
@Path("owner") String owner,
@Path("repo") String repo,
Callback<List<Contributor>> cb);
}

service.repoContributors("square", "retrofit", new Callback<List<Contributor>>() {
@Override void success(List<Contributor> contributors, Response response) {
// ...
}

@Override void failure(RetrofitError error) {
// ...
}
});

——再到后来 1.0 后,我们还支持了 RxJava ,被证明真的是个非常受欢迎的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

gitHubService.repoContributors("square", "retrofit")
.subscribe(new Action1<List<Contributor>>() {
@Override public void call(List<Contributor> contributors) {
// ...
}
});

Retrofit 1: 不够好的地方 (4:58);)

不幸的是,没有一个库是完美的,Retrofit 也不例外。为了支持可替换的功能模块,我们必须嵌套大量的组件,类的数量极多以至于成为了一个痛处,一方面是因为整个库非常的脆弱,还有就是因为我们无法修改公开的 API 接口。

Receive news and updates from Realm straight to your inbox

订阅Comments

如果你想要操作某次请求返回的数据,比如说返回的 Header 部分或者 URL,你又同时想要操作序列化后的数据部分,这是 Retrofit 1.0 上是不可能实现的。

1
2
3
4
5
6
7
8
9
10
11
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
List<Contributor> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);

@GET("/repos/{owner}/{repo}/contributors")
Response repoContributors2(
@Path("owner") String owner,
@Path("repo") String repo);
}

在上面的这个 GitHub 的例子里,我们返回了一个 contributor 的列表,你可以用不同的 converter 去做反序列化。然而,如果说你要读取一个 reponse 的 header 部分。除非你设置一个 endpoint 来接管这个 reponse,不然你没有办法去读取这个 response。 由于 response header 数据里并没有反序列化后的对象,如果不做反序列化操作的话,那你也就无法拿到 contributor 对象了。

我刚才说过同步和异步,以及用起来非常棒的 RxJava,但是这些用起来却有些刻板。比如:我们在某些场景下既需要异步的调用,又需要同步的调用。在 Retrofit 1.0 里,你必须得声明两次这个方法,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
List<Contributor> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);

@GET("/repos/{owner}/{repo}/contributors")
void repoContributors(
@Path("owner") String owner,
@Path("repo") String repo,
Callback<List<Contributor>> cb);
}

RxJava 也有类似问题。但值得庆幸的是你在用 RxJava 的时候只用声明一次就行,为了实现这个,我们还在核心代码里增加了对 RxJava 的支持,以辅助返回 Observable 的对象。

1
2
3
4
5
6
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

我们可能已经熟悉如何在 Retrofit 里创建 Observable 对象。但是如果你需要一些其他对象呢? 比如,我们没有计划支持用了 Guava 的 ListenableFuture,以及那些用了 Java 8 的 CompleteableFuture。毕竟,Retrofit 1 是基于还在用着 Java 6 的 Android 开发的。

Retrofit 1 里 Converter 工作的效率并不算是很高。下面是在 Retrofit 1 里创建自定义 Converter 的代码,非常简单:

1
2
3
4
interface Converter {
Object fromBody(TypedInput body, Type type);
TypedOutput toBody(Object object);
}

自定义 Converter 接收一个对象,然后返回一个格式化后的 HTTP 对象。问题是在我们传入了 Response 和一个我们想要转换的格式 Type 参数后,Converter 必须得搞清楚到底应该如何去反序列化,这部分的实现很复杂,而且耗时。尽管一些库做了对象的缓存,但依然效率很低。

1
2
3
4
5
6
7
8
9
interface GitHubService {
@GET("/search/repositories")
RepositoriesResponse searchRepos(
@Query("q") String query,
@Query("since") Date since);
}

/search/repositories?q=retrofit&since=2015-08-27
/search/repositories?q=retrofit&since=20150827

有时候,声明式 API 会遇到一些小问题。比如就像上面的例子一样,你有个接口需要传入一个 Date,但是一个 Date 会有多种不同的格式表示。有的接口可能需要一个字符串,有的可能需要一个分隔开的日期表示(尤其是那些比日期要复杂很多的对象,可能会有更多的表示方法)。

以上,基本上就是 Retrofit 1 无力解决的需求了,我们要如何修复呢?

Retrofit 2 (10:18);)

开发 Retrofit2 的时候,我们希望我们定位和解决所有大家多年以来在 Retrofit 1 里遇到的那些问题。

Call (10:30);)

首先得提到的是:Retrofit2 有了新的类型。如果你熟悉用 OkHttp 做 API 请求,你可能比较熟悉其中的一个类:Call。现在, Retrofit 2 里也多了一个 call 方法。语法和 OkHttp 基本一模一样,唯一不同是这个函数知道如何做数据的反序列化。它知道如何将 HTTP 响应转换成对象。

另外,每一个 call 对象实例只能被用一次,所以说 request 和 response 都是一一对应的。你其实可以通过 Clone 方法来创建一个一模一样的实例,这个开销是很小的。比如说:你可以在每次决定发请求前 clone 一个之前的实例。

另一个大的进步是 2.0 同时支持了在一个类型中的同步和异步。同时,一个请求也可以被真正地终止。终止操作会对底层的 http client 执行 cancel 操作。即便是正在执行的请求,也能立即切断。

1
2
3
4
5
6
7
8
9
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

Call<List<Contributor>> call =
gitHubService.repoContributors("square", "retrofit");

这个 Call 对象是从你的 API 接口返回的参数化后的对象。调用跟接口名相同的函数名,你就会得到一个实例出来。我们可以直接调用它的 execute 方法,但是得留意一下,这个方法只能调用一次。

1
2
3
4
5
6
7
8
9
10
11
Call<List<Contributor>> call =
gitHubService.repoContributors("square", "retrofit");

response = call.execute();

// This will throw IllegalStateException:
response = call.execute();

Call<List<Contributor>> call2 = call.clone();
// This will not throw:
response = call2.execute();

当你尝试调用第二次的时候,就会出现失败的错误。实际上,可以直接克隆一个实例,代价非常低。当你想要多次请求一个接口的时候,直接用 clone 的方法来生产一个新的,相同的可用对象吧。

想要实现异步,需要调用 enqueue 方法。现在,我们就能通过一次声明实现同步和异步了!

1
2
3
4
5
6
7
8
9
10
11
12
Call<List<Contributor>> call =
gitHubService.repoContributors("square", "retrofit");

call.enqueue(new Callback<List<Contributor>>() {
@Override void onResponse(/* ... */) {
// ...
}

@Override void onFailure(Throwable t) {
// ...
}
});

当你将一些异步请求压入队列后,甚至你在执行同步请求的时候,你可以随时调用 cancel 方法来取消请求:

1
2
3
4
5
6
7
8
9
Call<List<Contributor>> call =
gitHubService.repoContributors("square", "retrofit");

call.enqueue( );
// or...
call.execute();

// later...
call.cancel();

Parameterized Response Object (13:48);)

另一个新的特性是参数化的 Response 类型。 Response 对象增加了曾经一直被我们忽略掉的重要元数据:响应码(the reponse code),响应消息(the response message),以及读取相应头(headers)。

1
2
3
4
5
6
7
8
9
10
class Response<T> {
int code();
String message();
Headers headers();

boolean isSuccess();
T body();
ResponseBody errorBody();
com.squareup.okhttp.Response raw();
}

同时还提供了一个很方便的函数来帮助你判断请求是否成功完成,其实就是检查了下响应码是不是 200。然后就拿到了响应的 body 部分,另外有一个单独的方法获取 error body。基本上就是出现一个返回码,然后调用相对应的函数的。只有当响应成功以后,我们会去做反序列化操作,然后将反序列化的结果放到 body 回调中去。如果出现了返回了网络成功响应(返回码:200)却最终返回 false 的情况,我们实际上是无法判断返回到底是什么的,只能将 ResponseBody(简单封装的了 content-type,length,以及 raw body部分) 类型交给你去处理。

以上是在声明接口时候的两个重大的改变。

动态 URL Parameter (16:33);)

动态 URL 参数是让我头疼多年的一个问题,现在我们终于解决了!如果你向 GitHub 发出多个请求,收到一个响应,通常这个响应大概像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

Call<List<Contributor>> call =
gitHubService.repoContributors("square", "retrofit");
Response<List<Contributor>> response = call.execute();

// HTTP/1.1 200 OK
// Link: <https://api.github.com/repositories/892275/contributors?
page=2>; rel="next", <https://api.github.com/repositories/892275/
contributors?page=3>; rel="last"
// ...

要是你想做分页,你就得自己去分析这些 URL 了。GitHub 可能将 header link 地址列表里的数据已经缓存在服务器内存里了,当你去按他们指引的地址去请求的话,他们就不必费劲去从数据库里给你拿数据了,速度上也更快。但是,在 Retrofit 1.0 的时候,我们没有办法去直接执行 GitHub Server 返回在 header 里的请求地址。

用上我们新的 response 类型后,不止是我刚才提到的那些元数据,我们还可以写一些方法来读出自定义的字段,比如上面例子里的下一页的地址:

1
2
3
4
5
6
7
8
9
10
11
12
Response<List<Contributor>> response = call.execute();

// HTTP/1.1 200 OK
// Link: <https://api.github.com/repositories/892275/contributors?
page=2>; rel="next", <https://api.github.com/repositories/892275/
contributors?page=3>; rel="last"
// ...

String links = response.headers().get("Link");
String nextLink = nextFromGitHubLinks(links);

// https://api.github.com/repositories/892275/contributors?page=2

这个可能和上面的接口生成地址略有不同。

动态 URL 地址就是用在连续请求里的。在第一个请求之后,如果返回的结果里有指明下个请求的地址的话,在之前,你可能得单独写个 interface 来处理这种情况,现在就无需那么费事了。

1
2
3
4
5
6
7
8
9
10
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);

@GET
Call<List<Contributor>> repoContributorsPaginate(
@Url String url);
}

Retrofit 2.0 有了新的 标注:@Url ,允许你直接传入一个请求的 URL。

有了这个方法后,我们就可以直接把刚才取出来的下一页的地址传入,是不是一切都流畅了很多:

1
2
3
4
5
6
String nextLink = nextFromGitHubLinks(links);

// https://api.github.com/repositories/892275/contributors?page=2

Call<List<Contributor>> nextCall =
gitHubService.repoContributorsPaginate(nextLink);

这样的话,我们就能通过调用 repoContributorsPaginate 来获取第二页内容,然后通过第二页的 header 来请求第三页。你可能很多的 API 都见到过类似的设计,这在 Retrofit 1 里确实是个困扰很多人的大麻烦。

更多更有效的 Converters (19:31);)

Retrofit 1 里有一个 converter 的问题。多数人可能没遇到过,是库内部的一个问题。在 Retrofit 2 里,我们已经解决了这个问题,同时开始支持多种 Converter 并存。

在之前,如果你遇到这种情况:一个 API 请求返回的结果需要通过 JSON 反序列化,另一个 API 请求需要通过 proto 反序列化,唯一的解决方案就是将两个接口分离开声明。

1
2
3
4
5
6
7
8
interface SomeProtoService {
@GET("/some/proto/endpoint")
Call<SomeProtoResponse> someProtoEndpoint();
}

interface SomeJsonService {
@GET("/some/json/endpoint")
Call<SomeJsonResponse> someJsonEndpoint();

之所以搞得这么麻烦是因为一个 REST adapter 只能绑定一个 Converter 对象。我们费工夫去解决这个是因为:接口的声明是要语意化的。API 接口应该通过功能实现分组,比如: account 的接口,user 的接口,或者 Twitter 相关的接口。返回格式的差异不应该成为你分组时候的阻碍。

现在,你可以把他们都放在一起了:

1
2
3
4
5
6
7
interface SomeService {
@GET("/some/proto/endpoint")
Call<SomeProtoResponse> someProtoEndpoint();

@GET("/some/json/endpoint")
Call<SomeJsonResponse> someJsonEndpoint();
}

我大概提一下这个是怎么工作起来的,因为了解 Converter 的调用原理在写代码的时候很重要。

看我们上面的代码:第一个方法返回了一个 proto 对象。

SomeProtoResponse —> Proto? Yes!

原理很简单,其实就是对着每一个 converter 询问他们是否能够处理某种类型。我们问 proto 的 converter: “Hi, 你能处理 SomeProtoResponse 吗?”,然后它尽可能的去判断它是否可以处理这种类型。我们都知道:Protobuff 都是从一个名叫 message 或者 message lite 的类继承而来。所以,判断方法通常就是检查这个类是否继承自 message。

在面对 JSON 类型的时候,首先问 proto converter,proto converter 会发现这个不是继承子 Message 的,然后回复 no。紧接着移到下一个 JSON converter 上。JSON Converter 会回复说我可以!

SomeJsonResponse —> Proto? No! —> JSON? Yes!

因为 JSON 并没有什么继承上的约束。所以我们无法通过什么确切的条件来判断一个对象是否是 JSON 对象。以至于 JSON 的 converters 会对任何数据都回复说:我可以处理!这个一定要记住, JSON converter 一定要放在最后,不然会和你的预期不符。

另一个要注意的是,现在已经不提供默认的 converter 了。如果不显性的声明一个可用的 Converter 的话,Retrofit 是会报错的:提醒你没有可用的 Converter。因为核心代码已经不依赖序列化相关的第三方库了,我们依然提供对 Converter 的支持,不过你需要自己引入这些依赖,同时显性的声明 Retrofit 需要用的 Converter 有哪些。

更多可替换的执行机制 (22:38);)

在此之前,Retrofit 有一个死板的 execution 流程。在 Retrofit 2 里,我们调整了整个流程,让它变得可替换(pluggable),同时允许多个。跟 converter 的工作原理很像。

比如说,你有一个方法返回了一个 Call 对象,Call 是内置的 Converter 类型。比如:Retrofit 2 的执行机制:

1
2
3
4
5
6
7
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);

...

现在,你可以自定义这些了。或者用我们提供的一个:

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

@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> repoContributors2(
@Path("owner") String owner,
@Path("repo") String repo);

@GET("/repos/{owner}/{repo}/contributors")
Future<List<Contributor>> repoContributors3(
@Path("owner") String owner,
@Path("repo") String repo);
}

Retrofit 2.0 依然支持 RxJava,但现在是分离的。(你如果想要一些别的特性,你也可以自己写一个)同时支持不同的 execution 是怎么实现的呢?

1
2
3
4
5
6
7
8
9
10
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(..);

@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> repoContributors2(..);

@GET("/repos/{owner}/{repo}/contributors")
Future<List<Contributor>> repoContributors3(..);
}

通过返回类型来判断需要调用哪个 exection。比如说:返回为 Call 的类型, 我们的整个执行机制会问:“Hey,你知不知道如何处理 Call ?” 如果是 RxJava,它就会说:“我不知道,我只知道 Observable 的处理方法。”。 随后,我们又问内部的 converter,他刚好回答说:“是的!我会!”。

call —> RxJava? No! —> Call? Yes!

Observable 也是同样的工作原理。我们同样问 RxJava,它就说:“我能处理这个”:

1
2
3
4
5
6
7
8
9
10
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(..);

@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> repoContributors2(..);

@GET("/repos/{owner}/{repo}/contributors")
Future<List<Contributor>> repoContributors3(..);
}

Observable —> RxJava? Yes!

如果你没装相对应的 Converter,这就意味着我们无法验证响应的类型。比如:如果询问是否有办法能处理 Future,他们两个都会说:“不行”。

1
2
3
4
5
6
7
8
9
10
interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(..);

@GET("/repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> repoContributors2(..);

@GET("/repos/{owner}/{repo}/contributors")
Future<List<Contributor>> repoContributors3(..);
}

Future —> RxJava? No! —> Call? No! —> Throw!

这将会返回一个 Exception,意味着这两个以及内置的机制都无法处理这种类型。关于工作原理我们随后会深入讨论。

OkHttp 提供支持 (24:17);).

Retrofit 2 现在开始依赖了 OkHttp 了,而且这部分不再支持替换。这是一件比较有争议的事情。但是希望我能证明为什么这是一个对的决定。

OkHttp 现在很小而且很聚焦,有很多好用的 API 接口。我们在 Retrofit 2 里都有对 OkHttp 的接口映射,也基本具备了我们需要的所有的特性,包括提到的所有的抽象类们。这些都超赞!这是压缩 Retrofit 库大小的一个法宝。我们最终减小了Retrofit 60% 的体积,同时又具有了更多的特性。

OkHttp 提供支持 (以及 Okio!) (26:20);)

另一个用 OkHttp 的好处就是我们能够在 Retrofit 2 把 OkHttp 以公开接口的方式直接导出。你可能在 error body 方法或者响应里见到过 response body 的内容。显然,我们在 Retrofit 的 Reponse 对象里直接返回了 OkHttp 的 Response body。我们正在导出这些类型,OkHttp 的类型基本上已经以更好更简洁的 API 替代 Retrofit 1.0 的一些接口。

OkHttp 的背后是一个叫做 Okio 的库,提供的 IO 支持。我之前在 Droidcon Montreal 做过关于这个库的演讲。讨论过为什么它是众多 IO 库中更好的选择,还讨论了它为何极度高效,以及为什么你应该使用它们。演讲中我还提到 Retrofit 2 ,当时它还是脑海里的一个概念。现在 Retrofit 2 已经实现了。

Retrofit 2 的效率 (27:31);)

我做了这个图表来展示 Retrofit 相比 Retrofit 1 以及其他可能的方案要高效的多,这归功于刚刚提到的硬性依赖和那些抽象。我带大家来看一下上面视频中的这个表。所以一定要看我演讲的这部分;)噢!

初始化 - Retrofit 类型 (31:24);)

现在,让我们来看一下 Retrofit 的类型是如何替代 REST adapter 类型的,以及如何初始化。原来的方法叫做 endpoint, 不过现在我们称之为 baseUrl, baseUrl 就是你所请求的 Server 的 URL,下面是一个请求 GitHub Api 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.build();

interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

GitHubService gitHubService = retrofit.create(GitHubService.class);

我们声明了自己的接口,我们称作创建方法,跟 Retrofit 1 里的是一致的。接下来,我们来生成一个接口的实现,以使这些接口方法可以直接被调用。

当我们调用 repoContributors 这个方法的时候,Retrofit 会创建这个 URL。如果我们传入 SquareRetrofit 字符串,分别作为 ownerrepo 参数。我们就会得到这个 URL:https://api.github.com/repos/square/retrofit/contributors。在 Retrofit 内部,Retrofit 会用 OkHttp 的 HTTP URL 类型作为 基础的 URL ,然后 resolve 方法就会取出相对地址和 baseUrl 拼接起来,接着发起请求。接下来给你展示下改变 API 前缀,比如 V3。

1
2
3
4
5
6
7
8
9
10
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/v3/")
.build();

interface GitHubService {
@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

虽然说这不是 GitHub 真实的 API,但是真实世界里就是有很多 API 是由这样的前缀和路径组成的。调用相同的方法,被解析出来的 URL 将会是是这样的: https://api.github.com/repos/square/retrofit/contributors。可以看到在主机地址之后并没有v3 ,这是因为地址的 URL 是以一个斜线开始的,而在 HTTP 里,斜线开始的地址往往是绝对地址后缀路径。Retrofit 1 会因为语义化的约束,强制你加这个前缀斜线, 然后把 baseUrl 和相对地址拼接起来。现在,考虑到规范问题,我们已经对这两种地址加以区分。

1
2
3
4
5
6
interface GitHubService {
@GET("repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

如果有前缀 / 就代表着是一个绝对路径。删除了那个前缀的 /, 你将会得到正确的、包含了 v3 路径的全 URL。

1
2
3
4
5
6
7
8
9
10
11
12
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/v3/")
.build();

interface GitHubService {
@GET("repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
}

// https://api.github.com/v3/repos/square/retrofit/contributors

由于现在我们开始依赖 OkHttp, 并没有 Http Client 层的抽象。现在是可以传递一个配置好的 OkHttp 实例的。比如:配置 interceptors, 或者一个 SSL socket 工厂类, 或者 timeouts 的具体数值。 (OkHttp 有默认的超时机制,如果你不需要自定义,实际上不必进行任何设置,但是如果你想要去设置它们,下面是一个例子告诉你来怎么操作。)

1
2
3
4
5
6
7
OkHttpClient client = new OkHttpClient();
client.interceptors().add(..);

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.client(client)
.build();

如果你要指明特定的 converter 或者 execute 机制,也是在这个时候加的。比如这会儿:我们可以给 GSON 设置一个或者多个 converter。也可以给 protocol buffer 设置一个 converter。

1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ProtoConverterFactory.create())
.build();

我想要强调的是:添加 converter 的顺序很重要。按照这个顺序,我们将依次询问每一个 converter 能否处理一个类型。我上面写的其实是错的。如果我们试图反序列化一个 proto 格式,它其实会被当做 JSON 来对待。这显然不是我们想要的。我们需要调整下顺序,因为我们先要检查 proto buffer 格式,然后才是 JSON。

1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();

Retrofit 的文档里可能还没这些,如果你想要使用 RxJava 来代替 call, 你需要一个 Call Adapter Factory:

1
2
3
4
5
6
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

Call Adapter Factory 是一个知道如何将 call 实例转换成其他类型的工厂类。目前,我们只有 RxJava 的类型,也就是将 Call 类型转换成 Observable 类型。如果你了解 RxJava, 其实还有一种新的 Observable 类型(一次只发射一个 item 的类型)。你可以用这个 call adapter factory 来转换到其中任意一种 Observable。

扩展性 (36:50);)

刚才提到的 Factory,也是可扩展的。这意味着你可以写属于自己的 Call Adapter Facotry。实现起来其实就是一个方法。传递给它一个类型,返回 null 代表拒绝,或者返回一个 converter 的实例。

1
2
3
4
5
6
7
8
9
SomeJsonResponse

class ProtoConverterFactory {
Converter<?> create(Type type); null
}

class GsonConverterFactory {
Converter<?> create(Type type); Converter<?>
}

看上面的例子,如果你给它传递一个 JSON 的 Respnose 类型,这个类型不是从 proto 继承而来,那么它就会说:”我不知道如何传递这个,所以我返回空值 null.』,然而,对于 GSON converter 而言,它通过返回一个实例来表明它可以处理这个类型的。这就是为什么这个类是一个工厂类,因为我们让它生产 converter 实例。

如果你想要做一些自定义,实现起来是非常容易的。Converter 的实现与之前的实现是非常相似的,尽管代替类型化的输入和输出,我们现在使用的是 OkHttp 的请求body 和相应 body。

1
2
3
4
5
6
7
8
interface Converter<T> {
interface Factory {
Converter <?> create(Type type);
}

T fromBody(ResponseBody body);
RequestBody toBody(T value);
}

现在这已经高效的多,因为我们实际上可以查询那些 adapter。举个例子,GSON 有一个type adapter, 当我们请求 GSON Converter Factory,询问它是否可以处理某种请求的时候, converter factory 就开始查询这个 adapter, 它将以缓存的形式存在,当我们再次查询的时候,这个 adapter 就可以直接被使用了。这是一个非常小的成功,极力避免了不断地查询带来的损耗。

call adapter 有相同的模式。我们询问一个 call adapter factory 它是否可以处理某个类型,它将会以相同的方式回应。(例如:它会返回 null 来表达否)。它的API 是非常简单的。

1
2
3
4
5
6
7
8
interface CallAdapter<T> {
interface Factory {
CallAdapter<?> create(Type type);
}

Type responseType();
Object adapt(Call<T> value);
}

我们有一个方法来来实现适配。传入一个 call 实例,返回了一个 observable,single 或者 future 等。 还有一种方法来获得这种 response 类型:当我们声明一系列 contributor 调用时, 我们没法自动把那些参数化的类型提取出来,因此我们基本上只是请求这个 call adapter 也返回这个 response type。如果你为这个变量创造了一个实例,我们会请求 call adapter, 它会将 contributor type 列表返回。

还在建设中 (40:05);)

Retrofit 2 正在完善中!现在还不够完整,但是已经可以用了。我上面提到的点都是已经完成了的,那还有哪些未完成呢?

关于所谓的“参数 handler”我们现在还没有一个成熟的想法。我们未来想要让它有从 Guava 传递多个 map,或者数据类型及枚举类型。

日志功能还没有完成,在 Retrofit 1 里是有日志的,但是在 Retrofit 2 里面没有。依赖 OkHttp 的一个优点是你实际上可以使用一个 interceptor 来实现实际的底层的请求和响应日志。因此,对于原始请求和响应,我们并不需要它,但是我们很可能需要日志来记录 Java 类型。

如果你曾经使用过 mock 模块,你会发现它也还没被完成,但很快会完成的。

现在文档依然比较缺。

最后,在我有空的时候,我想在 Retrofit 2 里支持 WebSocket。在 2.0 里很可能无法实现,但是我想在后续的2.1 版本里会加入支持。

Release? (41:31);)

我保证过 Retrofit 2 今年会和大家见面,今年确实可以。至于具体哪天问世,我们不会做任何承诺。我不想再在 2016 的 DroidCons 上开相同的玩笑。因此今年一定会问世。我保证。至于2015年8月27日,我已经开放了一个2.0的测试版。

1
2
3
4
5
dependencies {
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta1'
}

你可以依赖它。它已经可以正常工作了,API 接口也相对稳定。Converter 和 converter 工厂方法未来可能会改变,但是总体来说是有用的。要是你有什么不喜欢或者有问题的地方,请联系我!谢谢!

AndroidAS-依赖lib建议、微信QRCode登陆

只有一条:
能download lib就download之后放到libs dir吧,不然伙伴们sync起来太费时间了;

这样的话,注意定期检查各lib版本就好

今日入坑:微信登陆(扫描二维码登陆)
参考链接:官方API doc

总结一下步骤:

  1. 获取二维码(QRCode)
  2. 用户授权后,重定向接口会接受到带code的请求-->code [get]
  3. 拿着上一步的code,连带在微信注册时的appid、secret一起去请求access_token -->access_token、openid[get]

  4. 大功告成:拿着上一步得到的token跟openid,就可以请求你想要的数据啦

时序图:

wechat.png


关注公众号“夕识”,雕刻时光,雕刻思维

GL exampleModule memory consume test report

“GL” test部分模块 内存占用测试

trunk 3.0 #90770

测试机型:N5 6.0

dump场景:

  1. 正常运行——静置3min
  2. 进入负一屏
  3. 进入3D时钟
  4. 进入AllApps
  5. 进入天气
  6. 进入搜索
  7. 应用3D主题之后
  8. 3D主题关屏之后
  9. 从3D切回2D

测试结论

以下结论基于N5 6.0机型,结果未与其它机器、安卓版本做横向比较

  • test进程内,各模块切换时未出现太大波动
  • 但test起始值(Gfx dev)在 40000KB(~40M)左右,考虑这一点是否有改进必要、空间?
  • 3D主题相较2D,内存占用升高~17M左右(具体值未严格测试)
  • 关屏之后(静置>2min),未对内存占用做优化处理——考虑是否可行?是否必要???
  • 从3D切回2D,内存反增11M左右(对此dump文件感兴趣的同学联系我)——此处需要分析hprof,找到问题点

实验数据

正常运行——静置3min

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
** MEMINFO in pid 16257 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 25108 25080 0 0 43264 36859 6404
Dalvik Heap 42114 42040 0 0 65438 50717 14721
Dalvik Other 3860 3860 0 0
Stack 1516 1516 0 0
Ashmem 176 140 0 0
Gfx dev 41004 41004 0 0
Other dev 136 0 136 0
.so mmap 8610 320 812 0
.apk mmap 2555 0 2100 0
.ttf mmap 173 0 128 0
.dex mmap 29908 48 23812 0
.oat mmap 2767 0 900 0
.art mmap 1705 1528 4 0
Other mmap 1078 8 608 0
EGL mtrack 44880 44880 0 0
Unknown 4924 4924 0 0
TOTAL 210514 165348 28500 0 108702 87576 21125

App Summary
Pss(KB)
------
Java Heap: 43572
Native Heap: 25080
Code: 28120
Stack: 1516
Graphics: 85884
Private Other: 9676
System: 16666

TOTAL: 210514 TOTAL SWAP (KB): 0

进入负一屏

未点击触发任何动画

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
** MEMINFO in pid 16257 [com..test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 25583 25548 0 0 43264 37267 5996
Dalvik Heap 45619 45532 0 0 65017 57890 7127
Dalvik Other 3900 3900 0 0
Stack 1520 1520 0 0
Ashmem 176 140 0 0
Gfx dev 42456 42456 0 0
Other dev 136 0 136 0
.so mmap 9323 320 960 0
.apk mmap 2583 0 2104 0
.ttf mmap 173 0 128 0
.dex mmap 30308 48 24112 0
.oat mmap 3297 0 1284 0
.art mmap 2015 1540 272 0
Other mmap 1223 8 696 0
EGL mtrack 53520 53520 0 0
Unknown 4924 4924 0 0
TOTAL 226756 179456 29692 0 108281 95157 13123

App Summary
Pss(KB)
------
Java Heap: 47344
Native Heap: 25548
Code: 28956
Stack: 1520
Graphics: 95976
Private Other: 9804
System: 17608

TOTAL: 226756 TOTAL SWAP (KB): 0

进入3D时钟

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
** MEMINFO in pid 16257 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 25243 25216 0 0 43776 36699 7076
Dalvik Heap 42553 42480 0 0 66333 50306 16027
Dalvik Other 3692 3692 0 0
Stack 1532 1532 0 0
Ashmem 176 140 0 0
Gfx dev 42884 42884 0 0
Other dev 136 0 136 0
.so mmap 8622 320 836 0
.apk mmap 2549 0 2108 0
.ttf mmap 173 0 128 0
.dex mmap 29687 48 23884 0
.oat mmap 3646 0 1660 0
.art mmap 2539 1564 792 0
Other mmap 1077 8 608 0
EGL mtrack 36240 36240 0 0
Unknown 4924 4924 0 0
TOTAL 205673 159048 30152 0 110109 87005 23103

App Summary
Pss(KB)
------
Java Heap: 44836
Native Heap: 25216
Code: 28984
Stack: 1532
Graphics: 79124
Private Other: 9508
System: 16473

TOTAL: 205673 TOTAL SWAP (KB): 0

进入AllApps

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
** MEMINFO in pid 16257 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 25539 25512 0 0 43776 37873 5902
Dalvik Heap 45413 45340 0 0 66146 57815 8331
Dalvik Other 3960 3960 0 0
Stack 1532 1532 0 0
Ashmem 176 140 0 0
Gfx dev 39380 39380 0 0
Other dev 136 0 136 0
.so mmap 8620 320 836 0
.apk mmap 2549 0 2108 0
.ttf mmap 173 0 128 0
.dex mmap 29887 48 24052 0
.oat mmap 3646 0 1648 0
.art mmap 2558 1584 792 0
Other mmap 1127 8 636 0
EGL mtrack 44880 44880 0 0
Unknown 4924 4924 0 0
TOTAL 214500 167628 30336 0 109922 95688 14233

App Summary
Pss(KB)
------
Java Heap: 47716
Native Heap: 25512
Code: 29140
Stack: 1532
Graphics: 84260
Private Other: 9804
System: 16536

TOTAL: 214500 TOTAL SWAP (KB): 0

进入天气

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
** MEMINFO in pid 16257 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 30251 30224 0 0 50176 46472 3703
Dalvik Heap 56889 56816 0 0 82047 68387 13660
Dalvik Other 4124 4124 0 0
Stack 1872 1872 0 0
Ashmem 228 144 0 0
Gfx dev 48684 48684 0 0
Other dev 136 0 136 0
.so mmap 9119 320 1216 0
.apk mmap 2537 0 2096 0
.ttf mmap 261 0 216 0
.dex mmap 31424 48 25576 0
.oat mmap 3819 0 1784 0
.art mmap 2593 1620 792 0
Other mmap 1397 8 852 0
EGL mtrack 39552 39552 0 0
Unknown 9044 9044 0 0
TOTAL 241930 192456 32668 0 132223 114859 17363

App Summary
Pss(KB)
------
Java Heap: 59228
Native Heap: 30224
Code: 31256
Stack: 1872
Graphics: 88236
Private Other: 14308
System: 16806

TOTAL: 241930 TOTAL SWAP (KB): 0

进入搜索

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
** MEMINFO in pid 16257 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 30979 30952 0 0 51712 46697 5014
Dalvik Heap 53559 53488 0 0 78029 62140 15889
Dalvik Other 4084 4084 0 0
Stack 1864 1864 0 0
Ashmem 224 152 0 0
Gfx dev 44948 44948 0 0
Other dev 136 0 136 0
.so mmap 9092 320 1184 0
.apk mmap 1214 0 812 0
.ttf mmap 224 0 204 0
.dex mmap 31530 48 25696 0
.oat mmap 4048 0 2056 0
.art mmap 2619 1704 732 0
Other mmap 1449 8 812 0
EGL mtrack 53520 53520 0 0
Unknown 8700 8700 0 0
TOTAL 248190 199788 31632 0 129741 108837 20903

App Summary
Pss(KB)
------
Java Heap: 55924
Native Heap: 30952
Code: 30320
Stack: 1864
Graphics: 98468
Private Other: 13892
System: 16770

TOTAL: 248190 TOTAL SWAP (KB): 0

应用3D主题之后

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
** MEMINFO in pid 23770 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 16450 16424 0 0 24832 22375 2456
Dalvik Heap 32283 32212 0 0 57652 46603 11049
Dalvik Other 3352 3352 0 0
Stack 864 864 0 0
Ashmem 130 128 0 0
Gfx dev 58872 58872 0 0
Other dev 4 0 4 0
.so mmap 3563 268 1692 0
.apk mmap 1359 0 896 0
.ttf mmap 114 0 112 0
.dex mmap 24990 32 20756 0
.oat mmap 3203 0 1672 0
.art mmap 1352 1152 16 0
Other mmap 913 8 716 0
EGL mtrack 44880 44880 0 0
Unknown 300 300 0 0
TOTAL 192629 158492 25864 0 82484 68978 13505

App Summary
Pss(KB)
------
Java Heap: 33380
Native Heap: 16424
Code: 25428
Stack: 864
Graphics: 103752
Private Other: 4508
System: 8273

TOTAL: 192629 TOTAL SWAP (KB): 0

3D桌面——关屏

静置1min

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
** MEMINFO in pid 23770 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 20416 20388 0 0 33280 27504 5775
Dalvik Heap 34972 34900 0 0 60660 44452 16208
Dalvik Other 3228 3228 0 0
Stack 1088 1088 0 0
Ashmem 150 136 0 0
Gfx dev 59148 59148 0 0
Other dev 8 0 8 0
.so mmap 12463 284 9176 0
.apk mmap 2967 0 2524 0
.ttf mmap 110 0 108 0
.dex mmap 31520 52 26864 0
.oat mmap 3170 0 1560 0
.art mmap 1701 1348 20 0
Other mmap 1359 8 1120 0
EGL mtrack 44880 44880 0 0
Unknown 3652 3652 0 0
TOTAL 220832 169112 41380 0 93940 71956 21983

App Summary
Pss(KB)
------
Java Heap: 36268
Native Heap: 20388
Code: 40568
Stack: 1088
Graphics: 104028
Private Other: 8152
System: 10340

TOTAL: 220832 TOTAL SWAP (KB): 0

从3D切回2D

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
** MEMINFO in pid 23770 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 23624 23596 0 0 37376 31068 6307
Dalvik Heap 48127 48056 0 0 69274 53190 16084
Dalvik Other 3368 3368 0 0
Stack 1256 1256 0 0
Ashmem 150 136 0 0
Gfx dev 70896 70896 0 0
Other dev 8 0 8 0
.so mmap 4425 320 968 0
.apk mmap 654 0 64 0
.ttf mmap 118 0 108 0
.dex mmap 13328 52 8816 0
.oat mmap 1996 0 380 0
.art mmap 2020 1440 332 0
Other mmap 88 8 0 0
EGL mtrack 53520 53520 0 0
Unknown 3668 3668 0 0
TOTAL 227246 206316 10676 0 106650 84258 22391

App Summary
Pss(KB)
------
Java Heap: 49828
Native Heap: 23596
Code: 10708
Stack: 1256
Graphics: 124416
Private Other: 7188
System: 10254

TOTAL: 227246 TOTAL SWAP (KB): 0

------------------------
After 5Min------------------
------------------------
** MEMINFO in pid 23770 [com.org.test] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 23368 23340 0 0 37120 30598 6521
Dalvik Heap 43928 43856 0 0 69379 65551 3828
Dalvik Other 3560 3560 0 0
Stack 1244 1244 0 0
Ashmem 150 136 0 0
Gfx dev 72440 72440 0 0
Other dev 8 0 8 0
.so mmap 3636 320 788 0
.apk mmap 395 0 0 0
.ttf mmap 26 0 8 0
.dex mmap 12470 52 8176 0
.oat mmap 2610 0 260 0
.art mmap 2159 1456 28 0
Other mmap 58 8 20 0
EGL mtrack 44880 44880 0 0
Unknown 3668 3668 0 0
TOTAL 214600 194960 9288 0 106499 96149 10349

App Summary
Pss(KB)
------
Java Heap: 45340
Native Heap: 23340
Code: 9604
Stack: 1244
Graphics: 117320
Private Other: 7400
System: 10352

TOTAL: 214600 TOTAL SWAP (KB): 0

【Android Tool】内存快照分析,自动抓取dump文件并转换脚本

最新得到的Google内部消息,未来谷歌应用商店的排名算法将会把App的性能纳入考量范围(使用的机器学习算法),我们开发者也应该将性能这一指标纳入自己的检查范围

解决问题

解决抓取内存文件,拉取到电脑,新建文件夹,转换为Eclipse Memory Analyzer 文件操作繁琐的问题

优势

  • clone项目之后,上述流程只需要👇的指令即可完成
  • 自动将转换的文件存档到新建的同名目录下,避免MAT分析产生的临时文件污染文档目录
  • 更改目录或进程时,无需修改配置文件,输入即可自动记录上次操作习惯
  • 重复输入名称时,会自动append 时间到原来名称,避免覆盖历史文件
1
python GrabDump.py

读完本文,你将收获到一个自动化上述流程的简单project

需要了解 Apk包的内存占用各部分组成

GitHub 地址

https://github.com/KyleCe/PhonePerformanceMeasure

关键点

最终的使用时样式:

说明

  • 首先你需要安装Python ^_^
  • 可以输入的参数包括: dump文件名、存放目录、需要抓取的进程名
  • 下次输入时,如果没有完全输入,会自动使用上一次的参数

第一次输入的时候还是需要完全的配置

已知问题

抓取dump次数过多时会出现adb缓存读写错误的问题,需要重启adb(kill-server | start-server),后续找到解决方案会及时更新到Github repository


扫描二维码,关注“夕识”,一起学习Android知识、CS知识,努力成为一位 different thinker

点击“链接”,跳转至repo地址


Powered by KyleCe

Copyright © 2015 - 2019 KyleCe All Rights Reserved.

访客数 : | 访问量 :