在aosp的gerrit中,我们知道有好几百个git repo,这如果在某一阶段需要集中精力 攻克某一个问题(局限在几个repo 中)但是不想影响其他git repo,前几次公司的做法是 直接拉一个分支,注意,这个分支是一个误导词,表面说是分支,其实是一个完整的gerrit 仓库。大 回归主线不容易。
大神想了一招,就在这几个分支上拉一个真正的分支,后台CMS专家怎么拉的分支我不知道,但是 当服务器上的分支建立完成后,可以依次执行:
你就可以看见 origin/ 下的新的分支:
* vimer_dev
zhimo-aosp-d_art
remotes/m/zhimo-aosp -> origin/zhimo-aosp
remotes/origin/android-9-dev-v50
remotes/origin/aosp/android-10.0.0_r20
remotes/origin/aosp/android-10.0.0_r25
remotes/origin/aosp/android-10.0.0_r29
remotes/origin/aosp/android-9.0.0_r20
remotes/origin/aosp/android-9.0.0_r3
remotes/origin/master
remotes/origin/zhimo-aosp
remotes/origin/zhimo-aosp-d_art
remotes/origin/zhimo-aosp-d_emu
接着:
vimer@host:~/src/aosp/art$ git branch -t art_dev origin/zhimo-aosp-d_art
分支 'art_dev' 设置为跟踪来自 'origin' 的远程分支 'zhimo-aosp-d_art'。
在方法中的修饰关键词override,表明该方法应该重写(override)基于该基类的虚方法。 什么? 虚函数不就是用来重写的吗?是的,但是重写的时候会出现很多非意料之中的疏忽,至少在 我目前看来就是这样的。我们先来看下简单的例子。
#include <iostream>
using namespace std;
class Base {
public:
virtual void func() { // user want to override this in the derived class
cout << "I am in base" << endl;
}
};
class Derived : public Base {
void func(int a){ // By mistake to put int a int argument
cout << "I am in derived class" << endl;
}
};
int main(){
Base b;
Derived d;
cout << "Compiled successfully" << endl;
// cout << "Compiled successfully"
}
我们看到在Base类中,已经使用virtual
进行声明了,那么在派生类中进行重写确实是理所当然的
但是,如果我们在派生类重新实现时改变了函数签名(所谓的函数签名就是指函数的名称 参数
返回值。。。)。此类错误我们应该极力避免,但是,很遗憾的是上面的程序是可以编译通过的。
不一而足,此类暗含的 编译器难以发现的错误严重影响我们的期望值,导致一些难以发觉的错误。
幸运的是,在c++11中,我们有一个关键词”override”可以阻止这种情况发生。
...
void func(int a) override
{
cout << "I am in derived class" << endl;
}
...
出错信息为:
g++ -g over2.cpp -o over2
over2.cpp:11:8: error: ‘void Derived::func(int)’ marked ‘override’, but does not override
void func(int a) override { // By mistake to put int a int argument
^~~~
override
可以帮助检查以下几个方面的问题:
virtual
修饰的,这个本意就是让用户在后面重写从这篇文章的题目我们就可以得知,本篇文章并不能在全局的角度介绍art test相关的东西。 我自己在对这个熟悉起来之前,真的总结不出来因为所以然。之所以还记录下来就是,让自己熟悉的 更快一些。
这个源文件定义很多与编译执行的test.最主要的就是CommonCompilerTest
:
class CommonCompilerTest : public CommonRuntimeTest {
public:
CommonCompilerTest();
~CommonCompilerTest(); // deconstructor
void MakeExecutable(ArtMethod* method, const CompiledMethod* compiled_method) // git show 815d5e5304a
REQUIRES_SHARED(Locks::mutator_lock_); // CommonCompilerDriverTest 相关
static void MakeExecutable(const void* code_start, size_t code_length);
protected:
void SetUp() override; //
void SetUpRuntimeOptions(RuntimeOptions* options) override;
Compiler::Kind GetCompilerKind() const;
void SetCompilerKind(Compiler::Kind compiler_kind);
virtual CompilerFilter::Filter GetCompilerFilter() const {
return CompilerFilter::kDefaultCompilerFilter;
}
void TearDown() override;
void CompileMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
void CompileDirectMethod(Handle<mirror::ClassLoader> class_loader, const char* class_name,
const char* method_name, const char* signature)
REQUIRES_SHARED(Locks::mutator_lock_);
void CompileVirtualMethod(Handle<mirror::ClassLoader> class_loader, const char* class_name,
const char* method_name, const char* signature)
REQUIRES_SHARED(Locks::mutator_lock_);
void ApplyInstructionSet();
void OverrideInstructionSetFeatures(InstructionSet instruction_set, const std::string& variant);
void ClearBootImageOption();
Compiler::Kind compiler_kind_ = Compiler::kOptimizing;
InstructionSet instruction_set_ =
(kRuntimeISA == InstructionSet::kArm) ? InstructionSet::kThumb2 : kRuntimeISA;
// Take the default set of instruction features from the build.
std::unique_ptr<const InstructionSetFeatures> instruction_set_features_
= InstructionSetFeatures::FromCppDefines();
std::unique_ptr<CompilerOptions> compiler_options_;
std::unique_ptr<VerificationResults> verification_results_;
private:
// Chunks must not move their storage after being created - use the node-based std::list.
std::list<std::vector<uint8_t>> header_code_and_maps_chunks_;
};
最近没有什么事情,要说对我生活影响最大的就是趁着端午节的间隙回到山东枣庄去见了 女友一家人,当然我是那里的常客:)
还有一件事情促使我在今日留下这篇念念碎,主要是刚才对象给我打电话说自己家的 一个哥去世了。主要是那个哥和对象家来往密切,虽然这次我回去没有和他碰面, 但是前几次我们经常还是有说有笑的,所以这样的事,对我的影响还是挺震撼的。
一句话,珍惜眼前人。
最近在和脚本干上了,没办法,概念太深的理解不上去,只能先这样了。但是,我想 是自己赶快跳过这一段时间,真的很痛苦。
aosp 中使用了大量的jni,现在我们进行一个简单的总结,对aosp中的jni有一个大概的了解。
frameworks中存在大量的上层服务,也就是aosp中上层的服务。我也不理解为什么这块如此过量的使用java,今天这个例子的目录在:
frameworks/base/media/java/android/media/MediaScanner.java. 稍微统计了一下,就这个media
目录中,存在了168个
文件,可见aosp中存在大量的java代码。
从java代码入手。
public class MediaScanner implements AutoCloseable {
static {
System.loadLibrary("media_jni");
native_init();
}
private final static String TAG = "MediaScanner";
...
private native void processDirectory(String path, MediaScannerClient client);
private native boolean processFile(String path, String mimeType, MediaScannerClient client);
private static native final void native_init();
private native final void native_setup();
private native final void native_finalize();
...
file: frameworks/base/media/java/android/media/MediaScanner.java.
按照邓凡平老师的书上说,media_jni
会自动加载为libmedia_jni.so(显而易见,这是由C++代码写的,也就是native代码).
native关键字表示调用native代码。
这上面的代码有两点值得注意,一个是动态加载JNI动态库,一个就是调用native函数。由下面的loadLibrary
我们可知,aosp中
可能是在运行时加载JNI库,后面native下面再总结。
loadLibrary:
...
@CallerSensitive
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
...
[file: ??]奇怪,没找到这个文件定义在哪块。
native_init()
是后面要解决的问题。
所谓的JNI层函数就是说,不使用JNI动态库,java调用c++代码(或者反过来说也是一样的),由java的关键词native
修饰的函数
就是调用JNI层的函数。
对应上面java代码的JNI层的文件在:frameworks/base/media/jni/android_media_MediaScanner.cpp,我们把两个文件的位置对比下:
frameworks/base/media/jni/android_media_MediaScanner.cpp
frameworks/base/media/java/android/media/MediaScanner.java
看到没有,这样就很容易识别,总结一句话就是,frameworks/base下的服务,绝大多说存在java和jni目录,也就是说,java相应的JNI文件 就离自己不远,这就是为什么frameworks中大量使用java做业务开发的原因吧,需要效率或者系统操作的动作时,则使用c/c++去完成。 现在来看一下android_media_MediaScanner.cpp文件中的主要内容.
...
// This function gets a field ID, which in turn causes class initialization.
// It is called from a static block in MediaScanner, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
ALOGV("native_init");
jclass clazz = env->FindClass(kClassMediaScanner);
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
}
...
static jboolean
android_media_MediaScanner_processFile(
JNIEnv *env, jobject thiz, jstring path,
jstring mimeType, jobject client)
{
// Lock already hold by processDirectory
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "No scanner available");
return false;
}
const char *mimeTypeStr =
(mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
if (mimeType && mimeTypeStr == NULL) { // Out of memory
// ReleaseStringUTFChars can be called with an exception pending.
env->ReleaseStringUTFChars(path, pathStr);
return false;
}
MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
if (result == MEDIA_SCAN_RESULT_ERROR) {
ALOGE("An error occurred while scanning file '%s'.", pathStr);
}
env->ReleaseStringUTFChars(path, pathStr);
if (mimeType) {
env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
}
return result != MEDIA_SCAN_RESULT_ERROR;
}
如果,这里弄不明白二者之间的联系,很好,说明这个问题很关键,提醒一下,我们要注意下cpp文件中的头文件,那里有我们 想要的答案。
来看一下JNI层函数的命名:
android_media_MediaScanner_native_init
这里面有什么玄机吗?是的,目前看出来的就是,这个命名规格与java的相应方法的
全路径是一模一样的。我们注意,在frameworks/base/media/java/android/media/MediaScanner.java
文件中,开头就是
package android.media; # MediaScanner.java的打包
...
public class MediaScanner implements AutoCloseable {
static {
System.loadLibrary("media_jni");
native_init();
}
...
private native boolean processFile(String path, String mimeType, MediaScannerClient client);
private static native final void native_init();
...
MediaScanner作为一个大类,其方法由native_init,如果我们把这个方法的全路径补全,不就是:
android.media.MediaScanner.native_init
吗,除了最后一个下划线,其他的把下划线换成点就可以将二者联系起来,这
里面一定有相关的方法是解决这个问题的。
我们上面说的就是java世界中的JNI注册的问题。
这个简单说下就是利用java的编译工具生成一个带有签名的头文件。后面会写一篇文章做一些记录的。
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaScannerJNI"
#include <utils/Log.h>
#include <utils/threads.h>
#include <media/mediascanner.h>
#include <media/stagefright/StagefrightMediaScanner.h>
#include <private/media/VideoFrame.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
#include <android-base/macros.h>
这个库函数应该包含在java的库的路径中(这里有一个专门的java系统变量是处理这个的
java.library.path),对于我们这种自定义的library,在编译java程序时可以传递过去。
-Djava.library.path=/path/to/lib
,如果java找不到的话,则会throw一个
UnsatisfiedLinkError的错误。
public class myJni { // save as JNI.java
static {
System.loadLibrary("hello"); // load library libhello.so on UBIX
// at runtime, the library contains
} // a native method called sayHello()
private native void sayHello();
public static void main(String[] args){
new Jni().sayHello(); // Create an instance and invoke the native method
}
}
javac -h . myJni.java
:我们来看一下javac -h.
-h <directory> 指定放置生成的本机标头文件的位置
ls:
vimer@host:~/src/test/java$ ls
myJni.class myJni.h myJni.java
hit: 我这是OpenJdk9.其他版本的请自己寻找一下相关的用法。
我们来看一下会生成的相关的头文件。
vimer@host:~/src/test/java$ cat myJni.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJni */
#ifndef _Included_myJni
#define _Included_myJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJni
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_myJni_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
其中最主要的就是下面这个:
JNIEXPORT void JNICALL Java_myJni_sayHello
(JNIEnv *, jobject);
这个命名规则是Java_{package-and-classname}_{function-name}(JNI-argument)
这里就有一个大名鼎鼎的dot代替下划线的替换。
这两个参数是:
: JNI环境,请注意这个JNI是广泛的特指,而不是 本例的JNI.java中的JNI。
: “this” object in java.
“extern c”是针对c++编译器而言的。它通知c++的编译器这些函数使用c的命名规则而不是c++的 命名规则。
我们首先对应的c程序,不然就算写出来了h文件也没有什么用途。
#include <stdlib.h>
#include <unistd.h>
#include <jni.h> // JNI header provided by JDK
#include "myJni.h" // Generated file from javac
JNIEXPORT void JNICALL Java_myJni_sayHello(JNIEnv *env, jobject thisObj){
printf("Hello, Jni world\n");
return;
}
使用命令去编译这个c程序生成动态加载库:
vimer@host:~/src/test/java$ gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJni.c
vimer@host:~/src/test/java$ ls
libhello.so myJni.c myJni.class myJni.h myJni.java
vimer@host:~/src/test/java$ nm libhello.so | grep say
000000000000061a T Java_myJni_sayHello
vimer@host:~/src/test/java$ java -Djava.library.path=. myJni
Hello, Jni world
可以使用tool nm查看一下动态库有没有定义这个符号,我们可以清楚地看到,为T(定义)。 然后使用java命令就可以将动态库的方法加载进去了。
将上面的java代码保持不变,还是使用这个。唯一的不同是使用:
#include <iostream>
#include <jni.h>
#include "myJni.h"
using namespace std;
// Implementions of native method sayHello()
JNIEXPORT void JNICALL Java_myJni_sayHello(JNIEnv *env, jobject thisObj){
cout << "Hello jni world from c++" << endl;
return;
}
接下来不变:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJni.cpp
vimer@host:~/src/test/java$ java -Djava.library.path=. myJni
Hello jni world from c++
这一个例子是揭示如何利用一个java代码同时使用c或者c++的程序。这次的java代码就有几处 不同。
public class myJniCpp { // save as JNI.java
static {
System.loadLibrary("hello"); // load library libhello.so on UBIX
// at runtime, the library contains
} // a native method called sayHello()
private native void sayHello();
public static void main(String[] args){
new myJniCpp().sayHello(); // Create an instance and invoke the native method
}
}
接着产生myJniCpp.h文件。
vimer@host:~/src/test/java$ javac -h . myJniCpp.java
详细看一下这个文件的内容。
cat myJniCpp.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJniCpp */
#ifndef _Included_myJniCpp
#define _Included_myJniCpp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJniCpp
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_myJniCpp_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
当然,我们最关心的应该还是sayHello()方法的实现。
然后我们利用这个头文件分别实现对应的c和c++的头文件。什么?需要吗?按照我的理解是这样的 一个java服务程序,当然支持c或者c++的程序最好了,那既然上面的方法我们分别实现了对应的 native代码,说明肯定有冲突的地方。
这三个文件各自有自己的用途,我们先看一下myJniCppImpl.h, 这是一个C++的头文件。
#ifndef _MY_JNI_CPP_IMPL_H
#define _MY_JNI_CPP_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
void sayHello();
#ifdef __cplusplus
}
#endif
#endif
cat myJniCppImpl.cpp:
#include <iostream>
#include "myJniCppImpl.h"
using namespace std;
void sayHello(){
cout << "hello wrold from c++!" << endl;
return;
}
很明显这里就是在c++的程序中实现了c库的函数,其实这里很复杂的,涉及到了编译器 对c++ c函数的编译声明方式。 cat myJniCpp.c:
#include <jni.h>
#include "myJniCpp.h"
#include "myJniCppImpl.h"
JNIEXPORT void JNICALL Java_myJniCpp_sayHello(JNIEnv *env, jobject thisObj){
sayHello();// invoke c++ function
return;
}
这个c程序负责与java程序的交互。下面是编译参数:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJniCpp.c myJniCppImpl.cpp
如果不注意的话则会出错,比如下面这个。
In file included from myJniCpp.c:3:0:
myJniCppImpl.h:5:10: error: language string ‘"c"’ not recognized
extern "c" {
^~~
In file included from myJniCppImpl.cpp:2:0:
myJniCppImpl.h:5:10: error: language string ‘"c"’ not recognized
extern "c" {
^~~
记住,调用c库的函数时一定注意这个错误,这个错误很牛逼,就是: 你把该大写的”C”写成了小写”c”.
java -Djava.library.path=. myJniCpp
hello wrold from c++!
我们总结一下这个过程,首先在java程序中使用native关键词说明本java方法使用底层库 的一个函数。然后我们使用c++实现了具体的方法,后面使用c程序invoke c++的实现。