框架基础JNI
2.1 概述
public boolean setLastModified(long time) {
if (time < 0) {
throw new IllegalArgumentException("time < 0");
}
return setLastModifiedImpl( path, time);//调用本地方法setLastModifiedImpl方法
}
private static native boolean setLastModifiedImpl(String path, long time);
不同平台的虚拟机有自己的实现方式。而java层不须要关心平台的差异。
Log.d("tag","Msg");
public final class Log{
public static int d(String tag, String msg) {
return println_native( LOG_ID_MAIN, DEBUG , tag, msg);//调用本地方法println_native
}
public static native int println_native(int bufID, int priority, String tag, String msg);
}
看它相应的jni代码就可以。
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
//比java层的println_native 函数多了两个參数JNIEnv* env, jobject clazz,其它的參数和java层一一相应。
const char* tag = NULL;
const char* msg = NULL; if (msgObj == NULL) {
//异常处理,后面会涉及到
jniThrowNullPointerException(env, "println needs a message");
return -1;
} if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
} if (tagObj != NULL)
// 将java 中String对象转换成本地UTF-8字符串
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL); //继续调用本地方法 __android_log_buf_write。
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL)
//使用完之后要释放资源。否则导致JVM内存泄露
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg); return res;
}
int register_android_util_Log(JNIEnv* env)
{
jclass clazz = env->FindClass("android/util/Log"); if (clazz == NULL) {
LOGE("Can't find android/util/Log");
return -1;
}
//通过jni操作java相应。后面会介绍。
levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
//调用了registerNativeMethod方法
return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};
typedef struct {
const char* name; //java层的native函数的名称
const char* signature; //该native函数的签名(返回值+參数列表)。由于java中支持函数重载,所以函数名称+函数签名才干明白确定一个函数
void* fnPtr; //函数指针,指向jni层该相应的函数实现
} JNINativeMethod;
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }
"println_native", //相应java层的函数名称为println_native的函数
"(IILjava/lang/String;Ljava/lang/String;)I",//该函数签名为(IILjava/lang/String;Ljava/lang/String;)I,关于函数签名后面会介绍含义
(void*) android_util_Log_println_native//该函数在jni层实现的方法指针为 (void*) android_util_Log_println_native
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
LOGV("Registering %s natives", className);
scoped_local_ref<jclass> c(env, findClass(env, className));
if (c.get() == NULL) {
LOGE("Native registration unable to find class '%s', aborting", className);
abort();
}
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s', aborting", className);
abort();
}
return 0;
}
看完Log,我们再看一个样例:MediaScanner
public class MediaScanner{
static {
System.loadLibrary("media_jni");
native_init();
}
...
private static native final void native_init();
private native void processFile(String path, String mimeType, MediaScannerClient client);
}
// 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.
//java层中native_init 在jni层的实现
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
LOGV("native_init");
// kClassMediaScannerClient = "android/media/MediaScannerClient";
jclass clazz = env->FindClass(kClassMediaScanner);
if (clazz == NULL) {
return;
}
//在fields.context 中保存int类型的mNativeContext成员变量的fieldId
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
if (fields.context == NULL) {
return;
}
}
static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,
jstring mimeType, jobject client)
{
LOGV("processFile");
// Lock already hold by processDirectory MediaScanner *mp = getNativeScanner_l(env, thiz); if (mp == NULL) {
//异常处理。后面会介绍异常处理
jniThrowException(env, kRunTimeException, "No scanner available");
return;
} if (path == NULL) {
//异常处理
jniThrowException(env, kIllegalArgumentException, NULL);
return;
} const char *pathStr = env->GetStringUTFChars(path, NULL);
if (pathStr == NULL) { // Out of memory
return;
} 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;
} MyMediaScannerClient myClient(env, client);
//调用MediaScanner的本地方法processFile
MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient); if (result == MEDIA_SCAN_RESULT_ERROR) {
LOGE("An error occurred while scanning file '%s'.", pathStr);
}
env->ReleaseStringUTFChars(path, pathStr);
if (mimeType) {
env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
}
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaScanner(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));
}
static JNINativeMethod gMethods[] = {
//..
{
"processFile", //java层名称为processFile
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", //java层函数签名为(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)
(void *)android_media_MediaScanner_processFile //该函数在jni层中的函数指针为(void *)android_media_MediaScanner_processFile
},
//..
{
"native_init", //java层名称为native_init
"()V", //java层函数签名为()V
(void *)android_media_MediaScanner_native_init 该函数在jni层中的函数指针为(void *)android_media_MediaScanner_native_init
},
};
下次调用时直接使用这个函数指针就可以。
这就涉及到2.1中的一个结构体类型JNINativeMethod
typedef struct {
const char* name; //java层的native函数的名称
const char* signature; //该native函数的签名(返回值+參数列表)。由于java中支持函数重载,所以函数名称+函数签名才干明白确定一个函数
void* fnPtr; //函数指针,指向jni层该相应的函数实现
} JNINativeMethod;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL); if (register_android_media_MediaPlayer(env) < 0) {
LOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
} if (register_android_media_MediaRecorder(env) < 0) {
LOGE("ERROR: MediaRecorder native registration failed\n");
goto bail;
} //在这里调用register_android_media_MediaScanner 注冊函数
if (register_android_media_MediaScanner(env) < 0) {
LOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
} if (register_android_media_MediaMetadataRetriever(env) < 0) {
LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
} if (register_android_media_AmrInputStream(env) < 0) {
LOGE("ERROR: AmrInputStream native registration failed\n");
goto bail;
} if (register_android_media_ResampleInputStream(env) < 0) {
LOGE("ERROR: ResampleInputStream native registration failed\n");
goto bail;
} if (register_android_media_MediaProfiles(env) < 0) {
LOGE("ERROR: MediaProfiles native registration failed");
goto bail;
} if (register_android_mtp_MtpDatabase(env) < 0) {
LOGE("ERROR: MtpDatabase native registration failed");
goto bail;
} if (register_android_mtp_MtpDevice(env) < 0) {
LOGE("ERROR: MtpDevice native registration failed");
goto bail;
} if (register_android_mtp_MtpServer(env) < 0) {
LOGE("ERROR: MtpServer native registration failed");
goto bail;
} /* success -- return valid version number */
result = JNI_VERSION_1_4;
}
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVqdW40MTE1MjI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
struct _JNIEnv;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //c++中使用_JNIEnv
..
#else
typedef const struct JNINativeInterface* JNIEnv; //c语言中使用JNINativeInterface
..
#endif
/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions; #if defined(__cplusplus)
..非常多方法
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
jthrowable ExceptionOccurred()
{ return functions->ExceptionOccurred(this); } void ExceptionDescribe()
{ functions->ExceptionDescribe(this); } void ExceptionClear()
{ functions->ExceptionClear(this); } jobject NewGlobalRef(jobject obj)
{ return functions->NewGlobalRef(this, obj); } void DeleteGlobalRef(jobject globalRef)
{ functions->DeleteGlobalRef(this, globalRef); } jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); } jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetStaticMethodID(this, clazz, name, sig); } jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
{ return functions->GetFieldID(this, clazz, name, sig); } jobject GetObjectField(jobject obj, jfieldID fieldID)
{ return functions->GetObjectField(this, obj, fieldID); }
//各种GetXXXField 方法 void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
{ functions->SetObjectField(this, obj, fieldID, value); }
//各种SetXXXField 方法 #define CALL_TYPE_METHOD(_jtype, _jname)
_jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...)
{
_jtype result;
va_list args;
va_start(args, methodID);
result = functions->Call##_jname##MethodV(this, obj, methodID,args);
va_end(args);
return result;
} #define CALL_STATIC_TYPE_METHOD(_jtype, _jname)
_jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, ..)
{
_jtype result;
va_list args;
va_start(args, methodID);
result = functions->CallStatic##_jname##MethodV(this, clazz,methodID, args);
va_end(args);
return result;
}
//..其它方法
}
struct JNINativeInterface { jclass (*FindClass)(JNIEnv*, const char*);
jint (*Throw)(JNIEnv*, jthrowable);
jint (*ThrowNew)(JNIEnv *, jclass, const char *);
jthrowable (*ExceptionOccurred)(JNIEnv*);
void (*ExceptionDescribe)(JNIEnv*);
void (*ExceptionClear)(JNIEnv*);
jobject (*NewGlobalRef)(JNIEnv*, jobject);
void (*DeleteGlobalRef)(JNIEnv*, jobject);
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//..其它方法
}
同一个线程对本地方法的多次调用,使用都是同一个JNIEnv。
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
/*
* Primitive types that match up with Java equivalents.
*/
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVqdW40MTE1MjI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVqdW40MTE1MjI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj)
*env, jobject thiz, jstring path,jstring mimeType, jobject client)
就是通过方法签名。
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native }
{
"processFile","(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V"
(void *)android_media_MediaScanner_processFile
}
jclass Findclass(const char* name)//查找全路径名称为name的类信息
jclass GetObjectClass(jobject ojb)//返回该对象所在类的信息
jclass clazz = env->FindClass("android/util/Log");
jfieldID GetFieldID(jclass clazz,const char* name,const char*sig);
fields.context =env->GetFieldID(clazz, "mNativeContext", "I");
jmethodID GetMethodID(jclass clazz,const char* name,const char*sig);
MyMediaScannerClient(JNIEnv *env, jobject client){
..
jclass mediaScannerClientInterface =
env->FindClass(kClassMediaScannerClient); //相应MediaScannerClie中void scanFile(String,long,long,boolean,boolean)方法
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface,
"scanFile",
"(Ljava/lang/String;JJZZ)V"); //相应MediaScannerClie中void handleStringTag (String,String)方法
mHandleStringTagMethodID = env->GetMethodID(
mediaScannerClientInterface,
"handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V"); //相应MediaScannerClie中 void setMimeType(String)方法
mSetMimeTypeMethodID = env->GetMethodID(
mediaScannerClientInterface,
"setMimeType",
"(Ljava/lang/String;)V");
...
}
static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
{
//fields.context =env->GetFieldID(clazz, "mNativeContext", "I");
return (MediaScanner *) env->GetIntField(thiz, fields.context);
}
GetTypeField(jobject,jfieldID)//返回jobject类中变量为jfieldID的变量
//fields.context =env->GetFieldID(clazz, "mNativeContext", "I");
env->SetIntField(thiz, fields.context, 0);//将mNativeContext设置为0
SetTypeField(jobject obj,jfieldID fieldID,nativeType value)//fieldID的值设置为value
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
fileSize, isDirectory, noMedia);
CallTypeMethod(jobject obj,jmethodID methodID,參数1,參数2...)来调用jmethodID函数,并将參数传递进去。
GetStaticFieldID(jclass clazz,const char* name,const char*sig);
GetStaticTypeField(jobject obj,jfieldID fieldID)
SetStaticTypeField(jobject obj,jfieldID fieldID,nativeType value) GetStaticMethodID(jclass clazz,const char* name,const char*sig);
CallStaticTypeMethod(jobject obj,jmethodID methodID,參数1,參数2...)
static jobject save_class = NULL//定义了一个全局的jobject
static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path,
jstring mimeType, jobject client)
{
....
//保存thiz对象,也就是MediaScanner对象
save_class =thiz ;
...
return ;
}
//调用call方法
void call()
{
//使用save_class 能够吗?
//不能够,由于有可能MediaScanner对象已经被回收了。
}
2 global reference:全局应用,能够添加引用计数,作用范围多线程。须要显示释放。假设不释放。永远不会被回收。
须要显示释放。可是及时没有释放,也可能被虚拟机回收。
boolean test(const char* name)
{ for(int i=0;i<10000;i++)
{
jstring nameStr = env->NewStringUTF(name);
//假设这里我们不马上释放nameStr。那么会在函数结束之后才释放。看起来没有太大差别,若像这样创建10000个jstring
就占用了许多的内存了。所以要这里在不须要使用的时候还是及时释放
//env->DeleteLocalRef(nameStr);
}
}
public:
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
mClient(env->NewGlobalRef(client)),//创建一个全局引用mClient
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
{
LOGV("MyMediaScannerClient constructor");
jclass mediaScannerClientInterface =
env->FindClass(kClassMediaScannerClient); if (mediaScannerClientInterface == NULL) {
LOGE("Class %s not found", kClassMediaScannerClient);
} else {
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface,
"scanFile",
"(Ljava/lang/String;JJZZ)V"); mHandleStringTagMethodID = env->GetMethodID(
mediaScannerClientInterface,
"handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V"); mSetMimeTypeMethodID = env->GetMethodID(
mediaScannerClientInterface,
"setMimeType",
"(Ljava/lang/String;)V");
}
} virtual ~MyMediaScannerClient()
{
LOGV("MyMediaScannerClient destructor");
//在析构函数中主动释放mClient
mEnv->DeleteGlobalRef(mClient);
}
static void
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;
}
..
}
extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {
JNIEnv* e = reinterpret_cast<JNIEnv*>(env); if ((*env)->ExceptionCheck(e)) {
/* TODO: consider creating the new exception with this as "cause" */
scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
(*env)->ExceptionClear(e); if (exception.get() != NULL) {
char* text = getExceptionSummary(env, exception.get());
LOGW("Discarding pending exception (%s) to throw %s", text, className);
free(text);
}
} scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));
if (exceptionClass.get() == NULL) {
LOGE("Unable to find exception class %s", className);
/* ClassNotFoundException now pending */
return -1;
} if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {
LOGE("Failed throwing '%s' '%s'", className, msg);
/* an exception, most likely OOM, will now be pending */
return -1;
} return 0;
}
框架基础JNI的更多相关文章
- Hibernatel框架基础使用
Hibernatel框架基础使用 1.简介 1.1.Hibernate框架由来 Struts:基于MVC模式的应用层框架技术 Hibernate:基于持久层的框架(数据访问层使用)! Spring:创 ...
- Struts2框架基础
Struts2框架基础 1.Java的框架 1.1.框架简介 在大型项目开发过程中,经常会使用到一些框架,这样做好的好处是能够提高工作效率,在java中最常用的的框架就是SSH,这其实是三个框架的简称 ...
- .NET面试题系列[1] - .NET框架基础知识(1)
很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...
- 框架基础之Hibernate简介
框架基础之Hibernate简介 1.什么是Hibernate? Hibernate是一个开发源代码的对象关系映射框架,它对JDBC进行非常轻量级的对象封装,使得程序员可以随心所欲地使用对象编程思维来 ...
- ThinkPHP框架基础
ThinkPHP 一.php框架基础介绍 真实项目开发步骤: 多人同时开发项目,协作开发项目.分工合理.效率有提高(代码风格不一样.分工不好) 测试阶段 上线运行 对项目进行维护.修改.升级(单个人维 ...
- Python云端系统开发入门——框架基础
Django框架基础 这是我学习北京理工大学嵩天老师的<Python云端系统开发入门>课程的笔记,在此我特别感谢老师的精彩讲解和对我的引导. 1.Django简介与安装 Django是一个 ...
- 如何基于Winform开发框架或混合框架基础上进行项目的快速开发
在开发项目的时候,我们为了提高速度和质量,往往不是白手起家,需要基于一定的基础上进行项目的快速开发,这样可以利用整个框架的生态基础模块,以及成熟统一的开发方式,可以极大提高我们开发的效率.本篇随笔就是 ...
- PHP面试(二):程序设计、框架基础知识、算法与数据结构、高并发解决方案类
一.程序设计 1.设计功能系统——数据表设计.数据表创建语句.连接数据库的方式.编码能力 二.框架基础知识 1.MVC框架基本原理——原理.常见框架.单一入口的工作原理.模板引擎的理解 2.常见框架的 ...
- 6月13 ThinkPHP框架基础
ThinkPHP 一.php框架基础介绍 真实项目开发步骤: 多人同时开发项目,协作开发项目.分工合理.效率有提高(代码风格不一样.分工不好) 测试阶段 上线运行 对项目进行维护.修改.升级(单个人维 ...
随机推荐
- TortoiseSVN (一) - 疑难操作
引用: http://blog.sina.com.cn/s/blog_74c22b210101cy3s.html 一.由于Unity打包过程需要耗费很长的时间,在测试过程中如果只需测某几种功能,则 ...
- C++的运算符
C++的运算符十分丰富,使得C++的运算十分灵活方便.例如把赋值号(=)也作为运算符处理,这样,a=b=c=4就是合法的表达式,这是与其他语言不同的.C++提供了以下运算符: 算术运算符+(加) - ...
- Qt 智能指针学习(7种QT智能指针和4种std智能指针)
从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include <QApplication> #include <QLabel> int main(int arg ...
- perl oracle utf-8 结果匹配中文字符
[oracle@oadb sbin]$ cat s1.pl #!/usr/bin/perl use DBI; use Encode; use HTTP::Date qw(time2iso str2ti ...
- hibernate笔记(一)
一.主配置文件hibernate.cfg.xml 位置:放在src目录下. 主要包含信息: 一.数据库信息 数据库方言(类的全名) 及 数据库链接信息 1. 数据库方言 2. ...
- UVA 1524 - Hot or Cold?(数学)
UVA 1524 - Hot or Cold? 题目链接 题意:给一个一元n次方程,带入x表示时间,f(x)表示温度,如今要求[s, e]的平均温度 思路:平均温度就是 总温度/ (e - s),画出 ...
- Swift - 使用闭包筛选过滤数据元素
通常筛选一个数组,通常会在代码的其它地方创建一个函数,然后为数组的每个元素调用它.但这样做会使代码分散在许多地方,不便于阅读.使用闭包就可以将相关代码片断放在一起,使结构逻辑更加清晰. 比如,筛选一个 ...
- Windows下sass的安装
sass依赖Ruby,所以,首先得先安装个Ruby 安装步骤: 1.安装Ruby的时候,勾上Add Ruby executables to your PATH(添加环境变量) 2.安装好Ruby之后, ...
- opencv之haar特征+AdaBoos分类器算法流程(二)
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/ ...
- 服务确定(服务类收货ML81N)
FUNCTION zrfc_mm005. *"---------------------------------------------------------------------- * ...