RednaxelaFX对《深入理解Android》的笔记(13)

RednaxelaFX
RednaxelaFX (Script Ahead, Code Behind)

在读 深入理解Android

深入理解Android
  • 书名: 深入理解Android
  • 作者: 邓凡平
  • 副标题: 卷I
  • 页数: 488
  • 出版社: 机械工业出版社
  • 出版年: 2011-9-5
  • 第3页
    图1-2 Java世界和Native世界交互
    然后下面:
    一般而言,Java世界经由JNI层通过IPC方式与Native世界交互,而Android平台上最为神秘的IPC方法就是Binder了,第6章将详细分析Binder。
    这图里在“JNI层”与“Native世界”之间用“进程间通信”的箭头联系了起来。实际上Java代码通过JNI与native代码的交互很多都是在同进程内的,并不涉及进程间通信。相信作者原本就很清楚这点,但写书的时候一下给想漏了。
    2013-08-04 15:58:32 1人推荐 回应
  • 第2页
    所以这本书是以Android 2.2 "Froyo"为原料来讲解的。现在(2013年8月)看起来是老了点,不过考虑到作者写这部分的时间点,这个版本还算合理。回顾下Android的大版本的发布时间:
    http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
    2010-06: Android 2.2 Froyo
    2010-11: Android 2.3 Gingerbread
    2011-02: Android 2.3.3 Gingerbread MR1
    2011-02: Android 3.0 Honeycomb
    2011-05: Android 3.1 Honeycomb MR1
    2011-06: Android 3.2 Honeycomb MR2
    2011-09: 《深入理解Android:卷I》首发 <--
    2011-10: Android 4.0 Ice Cream Sandwich
    2011-12: Android 4.0.3 Ice Cream Sandwich MR1
    2012-06: Android 4.1 Jelly Bean
    2012-08: 《深入理解Android:卷II》首发
    除非作者能一直跟着最新开发版代码来写书,不然确实也无法跟进得那么快⋯
    可见卷II已经跟进到Ice Cream Sandwich了。
    希望这本卷I能尽快来个更新版,不然里面讲的一些东西就要显得过时了。
    说来这本书既然是以Android源码分析为切入点的,开篇部分为啥不讲解一下Android源码的repo结构和每个repo里代码的目录结构的?能讲一下这个的话读者应该能对后面作者选取的例子由更好的理解吧。
    记一下本书的勘误表链接:http://blog.csdn.net/innost/article/details/6864129
    P.S. 说到Android的源码分析,有些资源链接顺带记在这里:
    Dalvikのソースコードを読んで分かったこと: http://www.slideshare.net/kishima7/dalvik-source-code-reading
    访问不了kernel.org的话,可以在Github上找到Android源码仓库的镜像。例如说Dalvik VM的源码在 https://github.com/android/platform_dalvik
    2013-08-04 16:05:01 回应
  • 第6页
    Froyo的编译依赖JDK 1.5,所以首先要做的就是下载JDK 1.5。下载网址是 http://www.oracle.com/technetwork/java/javase/index-jdk5-jsp-142662.html 。
    原谅我刚看到这里的时候楞了一下⋯“JDK 1.5”?
    然后想起来Froyo真的是依赖JDK 5的。要到Gingerbread开始才支持用JDK6来build。
    参考官方文档:http://source.android.com/source/initializing.html
    Froyo从现在的角度看真是太老了啊⋯
    P.S. 从Oracle官网下载JDK5的链接也得更新一下了。书上给的链接是Sun刚被Oracle收购之后没多久的时候用的链接。现在应该用: http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase5-419410.html
    2013-08-04 16:59:59 回应
  • 第14页
    既然要讲JNI的东西了,开场部分如果能先给出JNI的“定义”性资料会更好些。
    Java SE 5的JNI规范:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/jniTOC.html Java SE 6的JNI规范:http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html Java SE 7的JNI规范:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
    Android Froyo所支持的JNI应该是基于Java SE 5版的规范吧。
    本书是在第2章末尾,2.5 本章小结 的地方才提到了一下JNI规范。
    也应该普及一下JNI是一种“FFI”(Foreign Function Interface)这个基本概念。
    这里可以参考Wikipedia:http://en.wikipedia.org/wiki/Foreign_function_interface
    JNI是Java Native Interface的缩写,中文译为“Java本地调用”。
    其实应该是“Java本地接口”?“本地调用”只是JNI的一方面。
    通俗地说,JNI是一种技术,通过这种技术可以做到以下两点: * Java程序中的寒暑可以调用Native语言系的函数,Native一般指的是C/C++编写的函数。 * Native程序中的函数可以调用Java层的函数,也就是说在C/C++程序中可以调用Java的函数。
    书上说的这两点是JNI的两个侧面。“通俗说”,可以把这两点分别称为“正向JNI”与“反向JNI”(reverse JNI)。
    其中“反向JNI”,也就是从native代码调用回到Java的一侧,在JNI规范里的正式名字是“The Invocation API”:
    Java SE 5: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#wp9502 Java SE 6: http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html#wp9502 Java SE 7: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#wp9502
    话说,讲到JNI我会希望看到先讲解标准Java里JNI的概念和特点,然后讲解JNI在Android中的使用场景和特别之处,特别是它与标准Java的JNI的异同点。可惜本书一上来就扎进Android的细节里了⋯
    2013-08-04 17:27:14 1人喜欢 回应
  • 第16页
    通行的做法是在类的static语句中加载,调用System.loadLibrary方法就可以了。
    “类的static语句”这个的正式名称是“static initializer”: http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 中文可以叫做“静态初始化器”。
    如果一个native库通过JNI暴露给Java的所有native方法都由同一个Java类暴露出来,那么那个类的静态初始化器显然是调用System.loadLibrary()最合理的位置。
    但假如有这样的情况:某个Java包里有多个Java类,它们各自有若干native方法,而这些native方法其实是由同一个native库来提供,那就没必要在每个类的静态初始化器里都调用System.loadLibrary()了,只要调用一次就好。那这“一次”到底放哪儿就得想想了。
    (对同一个native库重复调用System.loadLibrary()不会重复加载与初始化该native库,所以就算重复调用了倒也不用担心就是。)
    有些库为了代码组织上的方便,会选择把同一个Java包下的所有native方法都集中由一个Java类暴露出来,例如说叫做XXXNatives的类,并把这些native方法都声明为包级别可见。这样就可以跟书上给的例子一样就在这个XXXNatives类的静态初始化器调用System.loadLibrary(),完事~
    2013-08-04 17:40:34 3回应
  • 第23页
    表2-2把jfloatArray误写为floatArray了。
    看作者的勘误表似乎没提到这个地方: http://blog.csdn.net/innost/article/details/6864129
    2.4.2的标题与其叫做“数据类型转换”,还是叫“数据类型对应关系”准确些。JNI规范写得很清楚:
    Java SE 5: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html Java SE 6: http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html Java SE 7: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
    ===============================================================
    如果说对象类型都用jobject表示,就好比是Native层的void*类型一样,对“码农”来说,它们是完全透明的。既然是透明的,那该如何使用和操作它们呢?
    “透明”一词在这里显示出了技术文章里词汇的“博大精深”⋯很多时候大家说“透明”和“不透明”其实想说的是同一个意思:
    透明:你看不到它,或者你不需要关心它
    不透明:它不让你看到它里面
    本来jobject系的这些JNI类型都应该叫做“opaque type”,也就是“不透明的类型”,因为使用者无法透过jobject看到背后的实现细节。
    可以留意JNI规范里的用词:
    Java SE 5: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1253 Java SE 6: http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp1253 Java SE 7: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp1253 "Accessing Java Objects
    The JNI provides a rich set of accessor functions on global and local references. This means that the same native method implementation works no matter how the VM represents Java objects internally. This is a crucial reason why the JNI can be supported by a wide variety of VM implementations.
    The overhead of using accessor functions through opaque references is higher than that of direct access to C data structures. We believe that, in most cases, Java programmers use native methods to perform nontrivial tasks that overshadow the overhead of this interface."
    留意“opaque reference”。
    文中用了正好相反的“透明”一词,却似乎也能表达出正确的意思⋯“似乎”,但差了那么一点。
    “使用者看不到实现细节” => “实现细节对使用者而言是‘透明’的”
    我想说的是,“透明”的是实现细节,而jobject则是隔绝实现细节用的一个“不透明”的表现形式。这里用词得非常非常小心才行。
    2013-08-04 19:15:50 回应
  • 第24页
    图2-3 JNIEnv内部结构简图
    书中的图2-3在JNI规范有相似的图,在第2章Design Overview的Figure 2-1, Interface Pointer
    Java SE 5: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp16696 Java SE 6: http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp16696 Java SE 7: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp16696
    图的细节不完全一样。我更喜欢JNI规范里的原图,凸显出JNIEnv有per-thread data structure。
    2013-08-05 09:50:39 回应
  • 第19页
    需要解释一下静态方法中native函数是如何找到对应的JNI函数的。其实,过程非常简单: 当Java层调用native_init()函数时,它会从对应的JNI库中寻找Java_android_media_MediaScanner_native_init函数,如果没有,就会报错。如果找到,则会为这个native_init和Java_android_media_MediaScanner_native_init建立一个关联关系,其实就是保存JNI层函数的函数指针。以后再调用native_init函数时,直接使用这个函数指针久可以了,当然这项工作是由虚拟机完成的。
    JVM之所以会知道对Java层的android.media.MediaScanner.native_init()要去找名为Java_android_media_MediaScanner_native_1init()的native函数,是因为JNI规范里定义了JNI的native函数的命名规则,让JVM里的dynamic linker可以根据名字来查找:
    Java SE 5: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp615 Java SE 6: http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp615 Java SE 7: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp615
    可以对比一下标准Java SE的JVM的实现。以Oracle JDK/OpenJDK里的HotSpot VM为例,它处理JNI函数的注册/查找/调用是这样做的:http://hllvm.group.iteye.com/group/topic/37604#post-243981 可以留意里面使用RegisterNatives()主动注册与交由JVM来完成查找的两种处理。
    那么Android里的Dalvik VM又是怎么做的呢?
    构造JNI函数名的逻辑在vm/Native.cpp的createJniNameString()与mangleString()。它俩都被findMethodInLib()调用。这个findMethodInLib()包含了动态查找的逻辑。
    2013-08-05 10:27:07 1人喜欢 回应
  • 第25页
    可能会有人担心每次要获取JNIEnv*都去调用JNI提供的AttachCurrentThread()会不会很慢。
    可以对比一下标准Java SE的JVM的实现。以Oracle JDK/OpenJDK里的HotSpot VM为例,它内部以thread local的方式保存着每个线程对应在JVM里的数据结构(Thread/JavaThread),这个Thread结构持有该线程对应的JNIEnv*。某个线程如果已经在JVM里注册为Java线程,那么要执行的代码其实很简单,开销不大:
    static jint attach_current_thread(JavaVM *vm, void **penv, void *_args, bool daemon) {
      JavaVMAttachArgs *args = (JavaVMAttachArgs *) _args;
    
      Thread* t = ThreadLocalStorage::get_thread_slow();
      if (t != NULL) {
        // If the thread has been attached this operation is a no-op
        *(JNIEnv**)penv = ((JavaThread*) t)->jni_environment();
        return JNI_OK;
      }
      // ...
    }
    
    这个ThreadLocalStorage底下使用的是操作系统提供的thread local存储(TLS)功能。在Windows上用Tls*系的Win32 API,在Linux上用pthread的pthread_getspecific/pthread_setspecific等函数。
    Dalvik VM是怎么实现的呢?
    vm/Jni.cpp
    static jint attachThread(JavaVM* vm, JNIEnv** p_env, void* thr_args, bool isDaemon) {
        JavaVMAttachArgs* args = (JavaVMAttachArgs*) thr_args;
    
        /*
         * Return immediately if we're already one with the VM.
         */
        Thread* self = dvmThreadSelf();
        if (self != NULL) {
            *p_env = self->jniEnv;
            return JNI_OK;
        }
    
      // ...
    }
    
    这里的dvmThreadSelf()底下也是依赖pthread的thread local存储。跟HotSpot VM几乎是一样的逻辑。
    如果我们不想每次获取线程对应的JNIEnv*都调用AttachCurrentThread(),那要么得自己发明一种thread local的数据结构或者是用个map记住线程与JNIEnv*的映射关系,要么也去依赖操作系统的thread local存储功能。此处既然JVM已经帮我们实现了thread local的包装,还是每次都调用AttachCurrentThread()简单方便,而且也不慢。
    2013-08-05 10:46:25 回应
  • 第88页
    在分析zygote创建虚拟机的时候,我们说过系统默认设置的Java虚拟机堆栈最大为16MB
    “堆栈”在大陆计算机科学术语指的是stack,是“栈”,但这里作者想说的是heap,是“堆”。我果然还是很不喜欢看到“堆栈”这个词,太容易弄混了。这不,作者都把自己弄混了。
    本书分析的Android Froyo还没有对Large Heap的支持。从Android 3.0 Honeycomb开始可以通过在AndroidManifest配置里加上android:largeHeap="true"来使用更大的Java堆。不过这当然是有代价的咯,要牺牲一些原本CoW省下的内存。 http://developer.android.com/reference/android/R.styleable.html#AndroidManifestApplication_largeHeap
    Froyo里有另外一个功能,VMRuntime.setMinimumHeapSize(),可以指定让Dalvik VM分配比默认-Xmx大的Java堆。但从Android 2.3 Gingerbread开始整个VMRuntime类都没了。
    本书既然说的是Froyo,在讨论能否“动态配置heapsize”的时候涉及一下VMRuntime.setMinimumHeapSize()是如何实现的会更好。
    2013-08-05 11:45:15 2人喜欢 回应
2人推荐
<前页 1 2 后页>