JNI线程

2023-11-09


作者:左少华
博客:http://blog.csdn.net/shaohuazuo/article/details/43149193
转载请注明出处:http://blog.csdn.net/shaohuazuo


1.Android线程介绍.

  1.线程的概念:

            这个是百度百科里的一段话.

      线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。

      一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

      另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,

      只拥有一点儿在运行中必不可少的资源, 但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

      一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

      由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。

      就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;

      阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。

      每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

    2.Android进程.

         在默认情况下,一个App内的各类别(Activity,BroadcastReceiver,Serverice等)都会在同一个进程中执行,

      而且这个进程由主线程负责执行,在Android里.一个app也可以同时运行多个进程,可以在manifest.xml文件中进行声明.

      再组件后面添加android:process="name" name是进程的名字.可以随意取名.

  3.Android线程.

        3.1 在android中为了保持良好的用户体验,Android主线程主用来和用户交互.并产生子线程完成一些幕后工作.

      3.2 有些后台用户的处理,比如播放音乐,下载图片,等一些耗时的工作就不能由主线程来完成了.

      3.3 解决方法由两个.一个是线程.一个是使用进程.在Android中.我们通常使用线程来解决这样的问题.

          也可以使用进程来解决.但是创建一个进程需要的资源和线程比,就和前面说的一样,一个是重量级的,一个是LWP的.

      3.4 选定Android多线程解决的问题的话.那么不可避免的就是线程安全.资源的竞争问题,Java层的解决方法

          和C层的解决原理是一样的.尽量不要产生竞争问题.如果由,则需要加锁保护.比如,java中的lock,同步,

          c中的信号量,互斥锁mutex和读写锁,spinlock等等.

      3.5 JNI是如何处理线程资源冲突问题的.请看第二节.


2.JavaVM对象和JNIEvn对象.

    1.JavaVM结构体.

            在Android环境中.定义两个主要的结构体.JavaVM和JavaEvn,在Java环境中.每个进程里可以诞生许多VM实例.

      每个实例会有一个JavaVM结构体实例和他对应.但是在Android环境中.每个进程只能诞生一个VM实例,

      所以只有一个JavaVM结构体对实例. 通常在VM加载*.so程序库时,

      会先调用JNI_OnLoad()函数,在JNI_OnLoad()函数中会将JavaVM指针对象保存到c层JNI的全局变量中.

    JavaVM对象是所有线程共享的.

    2.JNIEnv结构体

           2.1  JNIEnv对象,当Java线程调用到C层的JNI函数的时候.一定会进入VM,VM会产生一个相应的JNIEnv对象.

            这JNIEnv对象和线程是一一对应的关系.

    2.2   在调用JNIEnv中的函数时.多个线程调用的JNIEnv中的本地函数都是独立的.

            因为VM会为每个线程产生一个JNIEnv对象实体.

    2.3   在调用函数的时候如果不方便传递JNIEnv对象,可以先获取JavaVM对象,再使用GetEnv()函数获取JNIEnv对象.

    2.4   如果c/c++层自己创建的线程,就必须向VM替它诞生相应的JNIEnv对象.并且回传该对象的指针的值.


3.jclass, jmethodID和jfieldID

    3.1  功能

              jclass,jmethodID和jfieldID三者都指针.通过这个三个指针,能获取到java中的属性和方法.

    3.2  生命周期

              这个类被载入的时候,这些指针都是有效的.一直到这个类被卸载为止.

    3.3  作用域.

                jmethodID和jfieldID可以直接存储在c获取C++的全局变量中.它不需要NewGlobalRef()函数进行转换 .

        它们是线程和函数之间共享的对象.               

        但是jobject的所有子类,必须使用NewGlobalRef()方法转化才能生成一个全局对象的引用.

        它们默认情况下时局部的.不能函数共享,和多线程共享.

        另外还有两个对象, GetStringUTFChars()和GetBytesArrayElements()函数所回传的数据是不需要

        使用NewGlobalRef()转换的.


4.Java线程进入JNI本地函数.

  4.1 介绍.

              在Android中不管是主线程还是子线程都能通过JNI标准调用c++或者C函数.

       同样C函数同样也可以调用到Android主线程和其他线程中的函数.

    4.2  Demo流程介绍.

              1. ResultValue 存储计算的结果信息的类.

       2. ActNative是用来执行计算动作的类.

       3. CounterNative它讲ResultValue对象传递给C层,并获取返回的一个数.

       4. JNI Module获取ConterNative传递的ResultValue对象之后.获取setValue()方法.

       5. JNI Module获取CounterNative对象的number对象.

       6. 并把这个值赋值给ResultValue对象.

  4.3 类图

 

  4.4 代码

               1. Java层代码 

MainActivity代码:

package com.zuoshaohua.threadtest;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {
	
	private Button runbtn;
	private Button exitbtn;
	private TextView counterView;
	private CounterNative cn;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		exitbtn = (Button) this.findViewById(R.id.exit);
		runbtn = (Button) this.findViewById(R.id.run);
		
		counterView = (TextView) this.findViewById(R.id.counterView);
		
		exitbtn.setOnClickListener(this);
		runbtn.setOnClickListener(this);
		
		cn = new CounterNativesub1(); //这new的是子类对象.子类会先调用父类的构造函数,再调用自己的构造函数.
	
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onClick(View arg0) {
		int id = arg0.getId();
		switch (id) {
		case R.id.run:
		   ActNative.nativeExec();
		   counterView.setText(cn.result.getResult() + "");
			break;
		case R.id.exit:
			this.finish();
			break;
		default:
			this.finish();
			break;
		}
	}

}


  
ActNative.java代码

package com.zuoshaohua.threadtest;

public class ActNative {
	public static native void nativeExec();
}

package com.zuoshaohua.threadtest;

public abstract class CounterNative {
	private int number =10 ;
	public ResultValue result;
	static{
		System.loadLibrary("native");
	}
	public CounterNative()
	{
		number = getNumber();
		result = new ResultValue();
		nativeSetup(result);
	}
	public abstract int getNumber();
	private native void nativeSetup(Object obj);
}

package com.zuoshaohua.threadtest;

public class CounterNativesub1 extends CounterNative{
	@Override
	public int getNumber() {
		// TODO Auto-generated method stub
		return 13;
	}
}
 

package com.zuoshaohua.threadtest;

import android.util.Log;

public class ResultValue {
	private int result;
	private String mThreadName;
	
	public int getResult() {
		return result;
	}

	public void setResult(int result) {
	
		this.result = result;
		Thread.currentThread().setName( Thread.currentThread().getName()+"-SetValue");
		mThreadName = Thread.currentThread().getName();
		Log.d("ResultValue",mThreadName);
		
	} 
	

}

       2. Jni层代码C代码

#include "act.h"
#include "native.h"
#include <android/log.h>
#include <stdio.h>
//该过程中需要使用到的方法.
//1.GetFieldId()
//2.GetMethodID()
//3.GetIntField()
//4.CallVoidMethod();
//我们整理一下调用这些方法需要的参数
//jclass (JNICALL *GetObjectClass)
//     (JNIEnv *env, jobject obj);

//1.
//jfieldID (JNICALL *GetFieldID)
//(JNIEnv *env, jclass clazz, const char *name, const char *sig);
//从GetFieldID方法中我们找到他需要jclass对象.
//我们只有CouterNative对象的jobject对象.
//那么我们可以使用GetObjectClass()方法来获取该对象.
//第一要获取的GetFieldID参数搞定.

//2. 
//jmethodID (JNICALL *GetMethodID)
//(JNIEnv *env, jclass clazz, const char *name, const char *sig);
//我们上层需要获取的的方法是setValue方法.这个方法的类是ResultValue.
//那么我们可以使用resultThis方法获取Class这对象的jclass对象.

//3.jint (JNICALL *GetIntField)
//(JNIEnv *env, jobject obj, jfieldID fieldID);
//因为jobject对象都是局部对象.如果需要保存到全局变量中.
//需要使用NewGlobalRef函数进行转换.
//所有在natvieSetup中需要将this转换成全局变量.

//4.void (JNICALL *CallVoidMethod)
//(JNIEnv *env, jobject obj, jmethodID methodID, ...);
//resultThis这个对象也需要使用NewGlobalRef转换成全局的变量.

jfieldID number_id;
jmethodID setv_id;
jobject counter, result;

JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_CounterNative_nativeSetup
  (JNIEnv * env, jobject this, jobject resultThis)
{
	//获取jfieldID和jmethodID需要jclass对象.获取这个两个jclass对象.

	__android_log_print(ANDROID_LOG_INFO,"zshh","aaaaaaaaa");
	jclass cnclz = (*env)->GetObjectClass(env,this);
	jclass resclz = (*env)->GetObjectClass(env,resultThis);
	
	__android_log_print(ANDROID_LOG_INFO,"zshh","bbbbbbbb");
	number_id = (*env)->GetFieldID(env, cnclz,"number","I");

	__android_log_print(ANDROID_LOG_INFO,"zshh","ccccccccc");
	setv_id = (*env)->GetMethodID(env,resclz,"setResult","(I)V");
	
	__android_log_print(ANDROID_LOG_INFO,"zshh","ddddddddd");
	//将对象转换成全局对象. 
	counter = (*env)->NewGlobalRef(env,this);
	result = (*env)->NewGlobalRef(env,resultThis);
	__android_log_print(ANDROID_LOG_INFO,"zshh","eeeeeeeeeee");
}

JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_ActNative_nativeExec
  (JNIEnv *env, jclass this)
{
	//获取number的值,累加10次之后.调用java层的setValue()设置给value
	//并在界面上显示出来.
	int i = 0;
	int number = (*env)->GetIntField(env,counter,number_id);
		
	__android_log_print(ANDROID_LOG_INFO,"zshh","number = %d", number);
	for(i = 0 ; i<10; i++) 
	{
		number += 10;
	}
	
	__android_log_print(ANDROID_LOG_INFO,"zshh","number = %d", number);
	//调用setValue进行赋值.
	(*env) -> CallVoidMethod(env,result,setv_id,number);
	
	//end
}

       3. Android.mk

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
SRC_PATH_ROOT:=$(LOCAL_PATH)/../../src  
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := native
LOCAL_SRC_FILES := native.c 
LOCAL_LDLIBS := -llog
#LOCAL_SHARED_LIBRARIES :=libc
include $(BUILD_SHARED_LIBRARY)

  4. 例程分析

         当Java层的主线程准备执nativeSetup函数时,VM就会产生一个JNIEnv机构体对象.这个对象专属主线程.

     接着将这个对象传递给nativeSetup()函数的第一参数. 如果由其他的线程也一样.VM也会替其他线程产生对应的JNIEnv对象.

     所以不同的线程进入JNI层c函数时,他们JNIEnv的参数是不同的, 但是同一个线程每次进入本地函数,

     他们使用的JNIEnv是同一个.这样我们可以避免多线程之间的线程安全问题.而且可以达成跨函数的数据共享.

        

作者:左少华
博客:http://blog.csdn.net/shaohuazuo/article/details/43149193
转载请注明出处:http://blog.csdn.net/shaohuazuo

5.JNI本地线程进入Java函数.

   1.c中的线程如何进入Java层执行函数调用.

    在上面我们都是Java的线程通过JNI调用的C.我们需要看看C中的线程如何进入Java层进行调用.

    我们在C中生成一个线程.那么这个线程同样需要VM帮助我们去产生JNIEnv对象.

       再Java线程进入C本地函数是,这个对象自动VM自动创建的.那么C本地线程进入Java

       则需要向VM进行登记告示VM,VM会为这个C线程创建一个JNIEnv对象,之后这个线程就能进入Java层了.

  2 .Demo流程说明

        Activity : 用来显示Android线程计算的数据,和人机交互的界面.

        PrimeNative: 这个类是用来和C和java沟通的类.

     C模块:诞生四个线程计算质数.计算完成之后由注册的到java虚拟机中的线程调用java层的方法将数据显示出来.


        有了上面三个类.我们们看看整例子的流程.

        第一步.我们再Activity中实例话一个PrimeNative,这个时候会调用static{}静态代码块中的代码.

              就会去加载动态库,这个时候就会调用到C模块中JNI_OnLoad()函数,我们需要在这个代码中注册我们的线程到VM中.

        第二步.当上面完成之后.Java会执行构造函数中的代码.我们在构造函数中使用调用nativeSetup()函数,主要是为了

        C能获取jclass或者jmethodID,jfieldID对象,

        第三步.从java传递过来的参数.计算该参数范围内有那些是质数.然后把这些质数从C中返回到Android,然后Android

        在进行实现.

     第四步.将一个质数返回到Android过程如下,每当线程计算完毕,并且当前计算的是一个质数的话.我们就调用java层的

         函数将这个质数显示出来.


  3 .类图

   

  *上面是整个程序的框图,我们再看看c模块线程之间程序分析图.*

  .c层代码内存线程分析图.



 4 .代码

        例一: JNI单进程进入Java层调用

         我们C程序中只有一个进程,负责计算质数.当计算的数是质数的话,就调用callBack返回该质数显示到UI中.


1.java层代码.        

package com.zuoshaohua.threadtest;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {
	
	private Button runbtn;
	private Button exitbtn;
	public static TextView counterView;
	private PrimeNative pn;
	public static MainActivity ref;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		ref = this; 
		setContentView(R.layout.activity_main);
		exitbtn = (Button) this.findViewById(R.id.exit);
		runbtn = (Button) this.findViewById(R.id.run);
		counterView = (TextView) this.findViewById(R.id.counterView);
		pn = new PrimeNative(10, 100);
		exitbtn.setOnClickListener(this);
		runbtn.setOnClickListener(this);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onClick(View arg0) {
		int id = arg0.getId();
		switch (id) {
		case R.id.run:
		   ActNative.exec();
			break;
		case R.id.exit:
			this.finish();
			break;
		default:
			this.finish();
			break;
		}
	}

}

package com.zuoshaohua.threadtest;

public class ActNative {
	public static native void exec();
}

package com.zuoshaohua.threadtest;

import android.os.Handler;
import android.os.Message;

public class PrimeNative {
	private int start;
	private int end;

	private static Handler h;
	private static String thName;
	static {
		System.loadLibrary("prime");
	}

	public PrimeNative(int start, int end) {
		this.start = start;
		this.end = end;
		if (start > 0 && start < end) {
			init(start, end); // 将需要计算的数据传递C层.
		}
		h = new Handler() {
			public void handleMessage(Message msg) {
				MainActivity.ref
						.setTitle("value = " + String.valueOf(msg.arg1));
				MainActivity.ref.counterView.append(msg.arg1+", ");
			}
		};
	}

	void callBack(int result) {
		Message m = h.obtainMessage(1, result, 3, null);
		thName = Thread.currentThread().getName();
		h.sendMessage(m);
	}

	private native void init(int start, int end);
}

  2.JNI代码

/*************************************************************************
    > File Name: prime.c
    > Author: zuoshaohua
    > Mail: zshh0604@163.com 
    > Created Time: Wed 28 Jan 2015 04:40:40 PM
 ************************************************************************/
#include"prime.h"
#include"act.h"
#include<android/log.h>
#include<stdio.h>
JavaVM * jvm;
jmethodID callbackId;
jobject  obj;
static int startNum;
static int endNum;
static const char *classPathPrime = "com/zuoshaohua/threadtest/PrimeNative";
static const char *classPathAct = "com/zuoshaohua/threadtest/ActNative";

static JNINativeMethod methodPrime[] = {    
	{"init", "(II)V", (void *)Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup}
};

static JNINativeMethod methodExec[] = {
	{"exec","()V", (void*) Java_com_zuoshaohua_threadtest_ActNative_nativeExec}
};


static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods){

	jclass clz = (*env)->FindClass(env,className);                    //通过完整的类路径得jclass结构体.	
	(*env)->RegisterNatives(env, clz, gMethods, numMethods);	
	return JNI_TRUE;                                   
}

static int registerNatives(JNIEnv* env){
	registerNativeMethods(env, classPathPrime, methodPrime, sizeof(methodPrime) / sizeof(methodPrime[0]));    
	registerNativeMethods(env, classPathAct, methodExec, sizeof(methodExec) / sizeof(methodExec[0]));    
	return JNI_TRUE;

}

jint JNI_OnLoad(JavaVM* vm, void* reserved){
	JNIEnv *env;
	jvm = vm;
	if ((*jvm)->GetEnv(jvm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) //将JNI的版本设置为1.4,并通过jvm获取JNIEnv结构体
		return -1;
	if (registerNatives(env) != JNI_TRUE)                             //注册我们的本地方法到VM中.
		return -1;
	return JNI_VERSION_1_4;
}


/*
 * Class:     com_zuoshaohua_threadtest_PrimeNative
 * Method:    nativeSetup
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup
  (JNIEnv *env, jobject this, jint start, jint end)
{	
	
	//获取start,和end.保存到一个起来.
	__android_log_print(ANDROID_LOG_INFO, "PrimeNative", "start = %d, end =%d\n", start, end);
	startNum = start;
	endNum = end;
	//1. 获取jclass对象.
	jclass clz =(*env)->GetObjectClass(env, this);
	//2. 获取callBack方法.
	callbackId =  (*env)->GetMethodID(env,clz,"callBack","(I)V");	
	//3. 将clz转化成全局变量.	
	obj = (*env)->NewGlobalRef(env,this); 	
}

int isPrime(int num)
{
	int i = 0;
	for(i = 2; i*i <= num; i++)     //这里必须是 i*i<= num, 比如25,如果 i*i<25,
	{                               //那么这个数就会被判为奇数.以为,i=5的时候. i*i = <span style="font-family:Courier New;">25,退出循环,</span>	
		if( num%i == 0 ){
			return 0;
		}
	}
	return 1;
}

/*
 * Class:     com_zuoshaohua_threadtest_ActNative
 * Method:    nativeExec
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_ActNative_nativeExec
(JNIEnv * env, jclass clz)
{
	int i = 0 ;
	for(i  =startNum; i<endNum; i++)
	{
		if(isPrime(i)) 
		{
			__android_log_print(ANDROID_LOG_INFO, "ActNative", "prime = %d", i);
			(*env)->CallVoidMethod(env,obj,callbackId,i);    //回调java层方法,将数据显示到Java页面.
		}
	}
	//我们调用java端代码将数据显示到Android界面上
	//那么我们需要将nativeSetup方法的调用对象转化成全局的对象并获取callBack方法的ID.
	__android_log_print(ANDROID_LOG_INFO, "ActNative", "Java_com_zuoshaohua_threadtest_ActNative_nativeExec");

}



例二: 一个JNI本地线程进入Java执行调用.  

需要修改jni层的代码如下:

//arm-linux-androideabi-g++: error: pthread: No such file or directory

   

/*************************************************************************
    > File Name: prime.c
    > Author: zuoshaohua
    > Mail: zshh0604@163.com 
    > Created Time: Wed 28 Jan 2015 04:40:40 PM
 ************************************************************************/
#include"prime.h"
#include"act.h"
#include<android/log.h>
#include<stdio.h>
#include<pthread.h>
JavaVM * jvm;
jmethodID callbackId;
jobject  obj;
static pthread_t primeThread;
static int startNum;
static int endNum;
static const char *classPathPrime = "com/zuoshaohua/threadtest/PrimeNative";
static const char *classPathAct = "com/zuoshaohua/threadtest/ActNative";
extern void* pthreadPirme( void*);
static JNINativeMethod methodPrime[] = {    
	{"init", "(II)V", (void *)Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup}
};

static JNINativeMethod methodExec[] = {
	{"exec","()V", (void*) Java_com_zuoshaohua_threadtest_ActNative_nativeExec}
};


static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods){

	jclass clz = (*env)->FindClass(env,className);                    //通过完整的类路径得jclass结构体.	
	(*env)->RegisterNatives(env, clz, gMethods, numMethods);	
	return JNI_TRUE;                                   
}

static int registerNatives(JNIEnv* env){
	registerNativeMethods(env, classPathPrime, methodPrime, sizeof(methodPrime) / sizeof(methodPrime[0]));    
	registerNativeMethods(env, classPathAct, methodExec, sizeof(methodExec) / sizeof(methodExec[0]));    
	return JNI_TRUE;

}

jint JNI_OnLoad(JavaVM* vm, void* reserved){
	JNIEnv *env;
	jvm = vm;
	if ((*jvm)->GetEnv(jvm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) //将JNI的版本设置为1.4,并通过jvm获取JNIEnv结构体
		return -1;
	if (registerNatives(env) != JNI_TRUE)                             //注册我们的本地方法到VM中.
		return -1;
	return JNI_VERSION_1_4;
}


/*
 * Class:     com_zuoshaohua_threadtest_PrimeNative
 * Method:    nativeSetup
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup
  (JNIEnv *env, jobject this, jint start, jint end)
{	
	
	//获取start,和end.保存到一个起来.
	__android_log_print(ANDROID_LOG_INFO, "PrimeNative", "start = %d, end =%d\n", start, end);
	startNum = start;
	endNum = end;
	//1. 获取jclass对象.
	jclass clz =(*env)->GetObjectClass(env, this);
	//2. 获取callBack方法.
	callbackId =  (*env)->GetMethodID(env,clz,"callBack","(I)V");	
	//3. 将clz转化成全局变量.	
	obj = (*env)->NewGlobalRef(env,this); 	
}

int isPrime(int num)
{
	int i = 0;
	for(i = 2; i*i <= num; i++)     //这里必须是 i*i<= num, 比如25,如果 i*i<25,
	{                               //那么这个数就会被判为奇数.以为,i=5的时候. i*i = 25,
									//但是它循环不会继续.	
		if( num%i == 0 ){
			return 0;
		}
	}
	return 1;
}

/*
 * Class:     com_zuoshaohua_threadtest_ActNative
 * Method:    nativeExec
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_ActNative_nativeExec
(JNIEnv * env, jclass clz)
{
	int i = 0 ;
#if 0 
	for(i  =startNum; i<endNum; i++)
	{
		if(isPrime(i)) 
		{
			__android_log_print(ANDROID_LOG_INFO, "ActNative", "prime = %d", i);
			(*env)->CallVoidMethod(env,obj,callbackId,i);    //回调java层方法,将数据显示到Java页面.
		}
	}
#endif
	pthread_create(&primeThread,NULL,pthreadPirme,NULL); 
	//我们调用java端代码将数据显示到Android界面上
	//那么我们需要将nativeSetup方法的调用对象转化成全局的对象并获取callBack方法的ID.
	__android_log_print(ANDROID_LOG_INFO, "ActNative", "Java_com_zuoshaohua_threadtest_ActNative_nativeExec");
        pthread_join(primeThread, NULL);
<span style="font-family:Courier New;">        </span>//回收子进程.
	
}
#if 0
#include <pthread.h>        //包含这个头文件.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
		void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.    //编译是需要链接这个动态库.
如果您使用的是ubuntu系统,可以使用man pthread_create查看函数的说明. 
参数一: pthread_t类型的指针,它是一个全局区域的一个变量. 该方法会填充这个结构体.
参数二: pthread_attr_t 它用来指定线程的属性.
参数三: 他是线程执行的函数.
参数四: 它是线程执行函数的参数.


 1.先GetEnv方法获取这个env对象.如果已经存在,则不需要执行AttachCurrentThread()了.如果不存在,则需要注册. 

#endif 
void* pthreadPirme(void* args){
	int i;
	int status;
	JNIEnv *env;
	jboolean isAttached = JNI_FALSE;
	status = (*jvm)->GetEnv(jvm, (void **) &env, JNI_VERSION_1_4);       
	if(status < 0) {
		status = (*jvm)->AttachCurrentThread(jvm,&env, NULL);//将当前线程注册到虚拟机中.    
		if(status < 0) return NULL;
		isAttached = JNI_TRUE;
	}

<span style="font-family:Courier New;">       </span> for(i  =startNum; i<endNum; i++)
	{
		if(isPrime(i)) 
		{
			__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "prime = %d", i);
			(*env)->CallVoidMethod(env,obj,callbackId,i);//回调java层方法,将数据显示到Java页面.
		}
	}	
	if(isAttached) (*jvm)->DetachCurrentThread(jvm);      //当前线程退出的话,讲线程从vm的注销.
	return NULL;
}


6.JNI本地函数的多线程安全.

例三: 多个JNI本地线程进入Java执行调用. 

/*************************************************************************
    > File Name: prime.c
    > Author: zuoshaohua
    > Mail: zshh0604@163.com 
    > Created Time: Wed 28 Jan 2015 04:40:40 PM
 ************************************************************************/
#include"prime.h"
#include"act.h"
#include<android/log.h>
#include<stdio.h>
#include<pthread.h>
#include <stdlib.h>
#define PROC  4
JavaVM * jvm;

//回调callBack函数的id.
jmethodID callbackId;

//这个对象是init函数传递下来的对象.
//Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup
//是从这个方法中得到的一个对象.
jobject  syncObj;

//存储java传递过出来的开始num和结束num;
static int startNum;
static int endNum;

//一定义一个结构体. 
//num: 需要判断num是一个质数还是一个偶数.
//mutex: 他一个共享锁,用来避免线程之间的竞争问题.
//cond:  它是一个条件变量.
typedef struct item_t
{
	int num;
	pthread_mutex_t mutex;
    pthread_cond_t  cond;
}Item_t;

//主进程会把需要计算的数存放到item中.
static Item_t * item;
static int count[PROC];


//函数的定义
static void parent(void);
static void * child(void *p);
jboolean prime(int num); 
static int  getNum(void);
static void setNum(int i);

static const char *classPathPrime = "com/zuoshaohua/threadtest/PrimeNative";
static const char *classPathAct = "com/zuoshaohua/threadtest/ActNative";


static JNINativeMethod methodPrime[] = {    
	{"init", "(II)V", (void *)Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup}
};

static JNINativeMethod methodExec[] = {
	{"exec","()V", (void*) Java_com_zuoshaohua_threadtest_ActNative_nativeExec}
};


static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods){

	jclass clz = (*env)->FindClass(env,className);                    //通过完整的类路径得jclass结构体.	
	(*env)->RegisterNatives(env, clz, gMethods, numMethods);	
	return JNI_TRUE;                                   
}

static int registerNatives(JNIEnv* env){
	registerNativeMethods(env, classPathPrime, methodPrime, sizeof(methodPrime) / sizeof(methodPrime[0]));    
	registerNativeMethods(env, classPathAct, methodExec, sizeof(methodExec) / sizeof(methodExec[0]));    
	return JNI_TRUE;

}

jint JNI_OnLoad(JavaVM* vm, void* reserved){
	JNIEnv *env;
	jvm = vm;
	if ((*jvm)->GetEnv(jvm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) //将JNI的版本设置为1.4,并通过jvm获取JNIEnv结构体
		return -1;
	if (registerNatives(env) != JNI_TRUE)                             //注册我们的本地方法到VM中.
		return -1;
	return JNI_VERSION_1_4;
}


/*
 * Class:     com_zuoshaohua_threadtest_PrimeNative
 * Method:    nativeSetup
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_PrimeNative_nativeSetup
  (JNIEnv *env, jobject this, jint start, jint end)
{	
	
	//获取start,和end.保存到一个起来.
	__android_log_print(ANDROID_LOG_INFO, "PrimeNative", "start = %d, end =%d\n", start, end);
	startNum = start;
	endNum = end;
	//1. 获取jclass对象.
	jclass clz =(*env)->GetObjectClass(env, this);
	//2. 获取callBack方法.
	callbackId =  (*env)->GetMethodID(env,clz,"callBack","(I)V");	
	//3. 将clz转化成全局变量.	
	syncObj = (*env)->NewGlobalRef(env,this); 	
}

/*
 * Class:     com_zuoshaohua_threadtest_ActNative
 * Method:    nativeExec
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_zuoshaohua_threadtest_ActNative_nativeExec
(JNIEnv * env, jclass clz)
{
	int64_t i; 
	int j, ret, flag = 0; 
	pthread_t  tid[PROC]; 

	item = (Item_t*) malloc(sizeof(Item_t));	
	if (item == NULL) {
		exit(1);
	}
	item->num = 0;
	pthread_mutex_init(&item->mutex, NULL);
	pthread_cond_init(&item->cond, NULL);
	<span style="color:#FF0000;">for (i=0;i<PROC;i++) {                                   //创建多个本地线程.

		count[i] = 0;
		ret = pthread_create(tid+i, NULL, child, (void *)i);
		if (ret != 0) {
			exit(1);
		}
	}</span>
	<span style="background-color: rgb(102, 0, 204);">parent();                            //分发任务</span>
	for(i = 0;i<PROC;i++)
	{
		pthread_join(tid[i],NULL);
	}
	pthread_mutex_destroy(&item->mutex);
	pthread_cond_destroy(&item->cond);
	free(item); 
}


//判断一个数是步是质数.如果是质素返回JNI_TRUE =1 ,否则返回JNI_FALSE  = 0 ;
jboolean prime(int num)   
{
	int i = 0;
	for(i = 2;i * i <= num; i++)
	{
		if(num  % i == 0)
		{
			return JNI_FALSE;

		}
	}
	return JNI_TRUE;
}

//下面我们写一个getNum()和setNum()的辅助方法.
static int  getNum(void)
{
	int num;
	num  = item->num;
	return num;
}

static void setNum(int i)
{
	item->num = i;	
	return;
}

//下面我们需要写一个子线程中需要处理的任务.
//p:是传入线程再线程组中的下标,也就是
static void * child(void *p)
{	
	JNIEnv *env;
	int ret ,i;
	int64_t j;		
	int status;	
	jboolean isAttached = JNI_FALSE;

	<span style="background-color: rgb(51, 204, 0);">status = (*jvm)->GetEnv(jvm, (void **) &env, JNI_VERSION_1_4); //如果当前线程中没有JNIEnv对象,则执行注册 
	if(status < 0) {
		status = (*jvm)->AttachCurrentThread(jvm,&env, NULL);//将当前线程注册到虚拟机中.    
		if(status < 0) return NULL;
		isAttached = JNI_TRUE;
	}
</span>
	//我们需要由一个while的大循环,
	//它的退出条件是当主进程把数据分发完成之后.
	//主进程会把num设置为-1,并发送广播进行通知.让所有子进程退出.
	
	j = (int64_t)p;  
	while(1)
	{
		//num是一个共享变量,所有获取前必须lock
		ret = pthread_mutex_lock(&item->mutex); 
		if(ret != 0)
		{
			__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "pthread_muext_lock error\n");
			exit(1);	
		}
		while((i = getNum())==0)
		{
			//如果i==0,那么我们需要解锁等待.当条件满足的时候.
			//pthread_cond_wait()会进行加锁,他是一个原子操作.
			//这里为时候使用while而不使用if呢.是因为一次成功的加锁之后.
			//它调用getNum,不一定能获取需要计算的数.所有只能获取的数为0,那么需要进入睡眠等待.
			ret = pthread_cond_wait(&item->cond,&item->mutex);
			if(ret != 0)
			{
				__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "pthread_muext_lock error\n");
			    exit(1);	
			}
		}
		if(i == -1)
		{
		   //我们需要解锁退出.
		   ret = pthread_mutex_unlock(&item->mutex);
		   if(ret != 0)
		   {
			  __android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "pthread_mutex_unlock(&item->mutex)\n");
		     exit(1);   
		   }
		   break; 
		}
		//如果不是上面的两种值.那么我们将num设置为0,
		//并发送广播通知正在等待的主进程,给num赋值.	
		setNum(0);
		ret = pthread_cond_broadcast(&item->cond);
		if(ret != 0)
		{
			  __android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "pthread_cond_broadcast(&item->cond)");
		     exit(1);   
		}
		ret = pthread_mutex_unlock(&item->mutex);
		if(ret != 0)
		{
			  __android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "pthread_mutex_unlock(&item->mutex)");
		     exit(1);   
		}
		//如果是一个质数,那么他需要进入Java层,将这个质数显示的界面上.	
		if(prime(i))
		{		
			//以为可能同时有多个线程进入java执行callBack函数.所以为了线程安全,我们必须调用MonitorEnter
			//相当于是对这个对象做加锁操作.
			<span style="color:#FF0000;">(*env)->MonitorEnter(env,syncObj); 
			(*env)->CallVoidMethod(env,syncObj,callbackId,i);//回调java层方法,将数据显示到Java页面.	
			(*env)->MonitorEnter(env,syncObj);</span>
		}
		count[j]++;

	}
	//打算该线程计算了多少个质数.	
	__android_log_print(ANDROID_LOG_INFO,"child","pthreadPrime[%d]", count[j]); 
	//任务完成退出当前线程.		
	<span style="background-color: rgb(0, 153, 0);">if(isAttached) (*jvm)->DetachCurrentThread(jvm);      //再从线程退出之前.我们需要将线程从vm中注销.</span>
	pthread_exit((void *)NULL); 
}

//我们再分析一下父进程需要做的事情.
//1.产生4个线程.
//2.把需要计算质数方法num中.通知子线程获取数据进行计算.
//3.当分发完这些数据之后,需要将num 设置为-1,并发送广播通知子线程退出.

static void parent(void)
{
	int i,ret,j; 
	for(i=startNum;i<endNum;i++)
	{
		while(1)
		{
			ret = pthread_mutex_lock(&item->mutex);
			if(ret != 0)
			{
				__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: mutex_lock");
				exit(1);   
			}
			//如果大于则需要解锁等待.和上面的流程一样.
			while((j= getNum()) > 0)
			{
				ret = pthread_cond_wait(&item->cond,&item->mutex);
				if(ret != 0)
				{
					__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: cond_wait");
					exit(0);
				}
			}
			if(j==0)
			{
				setNum(i);
				ret = pthread_cond_broadcast(&item->cond);
				if(ret != 0)
				{
					__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: brocast");
					exit(0);
				}

				ret = pthread_mutex_unlock(&item->mutex); 	
				if(ret != 0)
				{
					__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: mutex_unlock");
					exit(0);
				}
				break;	
			}

		}
	}
	while(1)
	{
		pthread_mutex_lock(&item->mutex);
		if (ret != 0) {
			__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: mutex_lock");
			exit(1);
		}
		while ((j = getNum()) > 0) {
			ret = pthread_cond_wait(&item->cond, &item->mutex);
			if (ret != 0) {
				__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent::cond_wait");
				exit(1);
			}
		}
		if (j == 0) {
			setNum(-1);
			ret = pthread_cond_broadcast(&item->cond);
			if (ret != 0) {
				__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: broadcast");
				exit(1);
			}
			ret = pthread_mutex_unlock(&item->mutex);
			if (ret != 0) {
				__android_log_print(ANDROID_LOG_INFO, "pthreadPrime", "parent:: mutex_unlock");
				exit(1);
			}
			break;
		}
	}		
}

3.Android.mk文件

  

  

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
SRC_PATH_ROOT:=$(LOCAL_PATH)/../../src  
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := prime
LOCAL_SRC_FILES := prime.c 
LOCAL_LDLIBS := -llog
LOCAL_SHARED_LIBRARIES := pthread
include $(BUILD_SHARED_LIBRARY)

作者:左少华
博客:http://blog.csdn.net/shaohuazuo/article/details/43149193
转载请注明出处:http://blog.csdn.net/shaohuazuo

    

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JNI线程 的相关文章

  • Visual Studio 不允许我在 Android 物理设备上进行调试

    我正在使用 Xamarin 和 Visual Studio 2013 开发 Android 应用程序 我已将 Android 小程序连接到计算机 Sansung Kies 识别了该设备 Adb 也能识别它 因为当我输入 adb device
  • 单击 RecyclerView 内的 ImageView 时更改图像资源

    每当我单击它时 单击图像 而不是项目 我都会尝试更改回收器视图内特定位置的设备图像资源 我尝试将 setOnClickListener 放入 onBindViewHolder 方法中 但只有最后一个项目受到影响 这是我的回收者视图 http
  • 关闭 Android 中的飞行模式

    如果 num gt 50 我想关闭飞行模式 我实现了这段代码 来自在 Android 中切换飞行模式 https stackoverflow com questions 5533881 toggle airplane mode in and
  • 按回键隐藏软键盘

    我有一个EditText in an Activity我希望当我打开它时它处于活动状态并且软键盘处于打开状态Activity 这是我的xml for EditText
  • 应用程序实例是否始终在任何活动之前创建?

    在 Android 中 您可以通过扩展 Application 类并在 Manifest 中声明名称来提供您自己的 Application 类实现 我的问题是 这个实现是否总是在初始活动之前创建 或者活动可以在应用程序实例有时间创建之前启动
  • 如何使用 adb 在设备上安装现有的 Android 应用程序?

    在开发过程中 我使用类似的东西 adb s 192 168 1 77 5555 uninstall com myApp app adb s 192 168 1 77 5555 install path to android debug ap
  • 连接到不可发现的蓝牙设备

    我正在开发一个安卓应用程序 只是一个一般性问题 是否可以连接到公开不可发现的设备 提前致谢 如果您之前已与该设备配对 则即使该设备未处于可发现模式 也可以再次连接到该设备 参见这篇文章 以编程方式连接到配对的蓝牙设备 https stack
  • React Native Android 发布 apk 是调试,而不是发布

    我有一个现有的 Android 应用程序 我已根据以下内容将 React Native v0 30 活动添加到项目中docs http facebook github io react native releases next docs i
  • SQLite支持android的数据类型有哪些

    谁能告诉我 SQLITE 中支持 ANDROID 的数据类型列表 我想确认 TIME 和 DATE 数据类型 这里有一个list http www sqlite org datatype3 htmlSQLite 的数据类型 支持时间和日期间
  • window.onbeforeunload 在 Android Chrome 上不会触发 [alt.解决方案?]

    我开发了一个简单的聊天应用程序 我正在使用 window onbeforeunload当有人关闭选项卡 浏览器时 基本上是当用户离开房间时 通知其他用户 这是我的代码 scope onExit function scope chatstat
  • Android模拟器分配内存失败8

    当我尝试从 Eclipse 运行 WXGA800 模拟器时 出现如下错误 Failed to allocate memory 8 This application has requested the Runtime to terminate
  • 通过列表视图检查动态生成的复选框时遇到问题

    我知道其他成员已经提出了这个问题 一些成员也给出了解决方案 但问题是我没有找到任何适合我的应用程序的解决方案 我正在创建一个应用程序 其中我有一个屏幕 它将显示动态列表视图 其中包含列表项 复选框和三个文本视图 一个用于候选人姓名 另外两个
  • jar 中的 apklib 有什么优点?

    我正在关注这个问题 https stackoverflow com questions 6059502 whats the difference between apklib and jar files但它并没有完全回答我的问题 jar 中
  • 不显示 WRITE_EXTERNAL_STORAGE 的权限对话框

    I want to download a file using DownloadManager And DownloadManager wants to WRITE EXTERNAL STORAGE permission I have in
  • 内部存储的安全性如何?

    我需要的 对于 Android 我需要永久保存数据 但也能够编辑 并且显然是读取 它 用户不应访问此数据 它可以包含诸如高分之类的内容 用户不得对其进行编辑 我的问题 我会 并且已经 使用过Internal Storage 但我不确定它实际
  • Android:确定 2.2 及更高版本上的摄像头数量

    我的应用程序需要在 Android 2 2 及更高版本上运行 我需要一种方法来确定可用摄像机的数量 有很多帖子解决了这个问题 但我找不到一个有效的 一种解决方案是简单地检测操作系统版本 任何 2 2 版本的设备都仅限于 1 个摄像头 即使该
  • Android - iphone 风格 tabhost [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 在 Android 布局 xml 文件中使用字符串格式参数 [重复]

    这个问题在这里已经有答案了 我在 String xml 文件中定义了一个使用格式参数的字符串 即
  • 如何在Android中解析xml类型的HTTPResponse

    我有一个 Android 应用程序 我使用 POST 方法来获取响应 这是我的代码 HttpResponse httpResponse httpclient execute httppost HttpEntity resEntity htt
  • Android:无法发送http post

    我一直在绞尽脑汁试图弄清楚如何在 Android 中发送 post 方法 这就是我的代码的样子 public class HomeActivity extends Activity implements OnClickListener pr

随机推荐

  • Mysql高级部分

    1 索引 索引 index 是帮助Mysql高效获取数据的数据结构 索引的目的在于提高查询效率 可以类比字典 可以简单理解为 排好序的快速查找数据结构 在数据之外 数据库系统还维护着满足特定查找算法的数据结构 这些数据结构以某种方式引用 指
  • tslib1.4的交叉编译

    tslib是touch screen lib 即支持触摸屏的库文件 要交叉编译qte4 5 2 就必须先要编译tslib1 4 今天做了这个工作 记录一下 平台 VMware Centos5 4 交叉编译器 arm linux 3 4 1
  • windows下使用cmake编译c++

    好久没有更新博客了 最近在做c 相关的 编译起来确实很痛苦 所以心血来潮 继续更新一下 主要还是一些跨平台的库 比如zlib libpng opencv ffmpeg 编译工具使用mingw作为主要编译环境支持 使用msys进行编译 一 下
  • R语言实战笔记--第八章 OLS回归分析

    R语言实战笔记 第八章 OLS回归分析 标签 空格分隔 R语言 回归分析 首先 是之前的文章 数理统计里面的简单回归分析 这里简单回顾一下 简单回归分析的原理 最小二乘法 即使回归函数与实际值之差的平方和最小 所以它在R中也称为OLS模型
  • 人工智能可以用来预测人口增长吗?

    人工智能可以用来预测人口增长 利用人工智能技术 可以对历史人口数据进行分析 并根据这些数据建立预测模型 从而预测未来的人口增长趋势 此外 人工智能还可以用于预测人口结构变化 人口流动趋势等方面的问题 这些预测结果可以为政府制定人口政策提供参
  • java读写文件大全(字节流读取得方法)

    原文地址 http blog sina com cn s blog 6a4af8630101et2t html 使用Java操作文本文件的方法详解 摘要 最初java是不支持对文本文件的处理的 为了弥补这个缺憾而引入了Reader和Writ
  • Ubuntu下的终端产生多标签和多标签切换快捷键

    ctrl alt t是打开一个terminal ctrl shift t是在terminal中打开多个标签 在多个标签中切换的方法 方法1 alt 1 alt 2 alt 3 方法二 ctrl pageUp ctrl pageDown ct
  • 【vision transformer】DETR原理及代码详解(四)

    本节是 DETR流程及 构建backbone和position embedding 相关部分的代码解析 一 DETR代码流程 STEP 1 Create model and criterion 构建模型和标准 STEP 2 Create t
  • IPv4和IPv6的互操作性

    概述 在IPv4到IPv6发展的过度阶段 必然出现v4和v6主机之间互操作的问题 下面从机制层面讨论互操作的可能性 并假设主机之间网络路由已经打通 IPv4客户端访问IPv6服务端 IPv4客户端访问IPv6服务端指的是服务端是双栈主机 客
  • OpenCV(二)——图像基本处理(二)

    目录 2 图像的几何变换 2 1 图像平移 2 2 图像缩放 2 3 图像旋转 2 4 仿射变换 2 5 透视变换
  • 电脑开机全是英文进不了系统怎么办

    现在工作都离不开电脑 但是有时候电脑罢工了 电脑开机全是英文进不了系统怎么办呢 下面就和大家讲讲电脑开机全是英文进不了系统解决办法吧 装机吧 电脑一键重装系统领域装机大师 重装系统xp win7 win8 win10 win11 怎么能缺少
  • python毕业设计基于django中小学信息技术课程考试系统

    文末获取资源 收藏关注不迷路 文章目录 一 项目介绍 二 主要使用技术 三 研究内容 四 核心代码 五 文章目录 一 项目介绍 通篇文章的撰写基础是实际的应用需要 然后在架构系统之前全面复习大学所修习的相关知识以及网络提供的技术应用教程 以
  • Neo4j 融资 3.25 亿美元,数据库史上最大的一笔投资

    加州圣马提奥 2021年6月17日 Neo4j图形技术的领先者 今天宣布了一轮F系列融资 这是一项3 25亿美元投资的一部分 亚拉齐奥 RF Pa GV 前称谷歌风投 今天的交易代表了对私人数据库公司的最大投资 使Neo4j的估值超过20亿
  • 什么是.NET?什么是.NET Core?.NET和.NET Core区别又是什么呢?

    本文首发于码友网 什么是 NET 什么是 NET Core NET和 NET Core区别又是什么呢 概述 对于 NET平台的初学者来说 有时候比较困惑 什么是 NET 什么是 NET Core NET和 NET Core区别又是什么呢 确
  • 【一句话攻略】彻底理解JS中的回调(Callback)函数

    作为JS的核心 回调函数和异步执行是紧密相关的 也是必须跨过去的一道个门槛 那么究竟什么是回调函数 Callback 其实回调函数并不复杂 明白两个重点即可 1 函数可以作为一个参数在另一个函数中被调用 2 JS是异步编程语言 这就是说JS
  • python-opencv滑动条,cv2滑动条,模糊示例

    cv2的滑动条使用起来其实挺简单的 只需记住两个函数就行 cv2 createTrackbar创建滑动条 cv2 getTrackbarPos获取滑动条当前值 其他要注意的是滑动条的名字以及滑动条创建在哪个窗口上 这个要一一对应 详见下例
  • react源码分析:babel如何解析jsx

    同作为MVVM框架 React相比于Vue来讲 上手更需要JavaScript功底深厚一些 本系列将阅读React相关源码 从jsx gt VDom gt RDOM等一些列的过程 将会在本系列中一一讲解 工欲善其事 必先利其器 经过多年的发
  • 内存泄漏的8种情况(附代码示例)

    一 内存泄漏 memroy leak 严格来说 只有对象不会再被程序用到了 但是GC又不能回收它们的情况 才叫内存泄漏 宽泛的讲 实际情况中很多时候一些不太好的实践会导致对象的生命周期变得很长甚至导致OOM 也叫 内存泄漏 申请了内存用完了
  • C++ - 类中的函数重载

    1 回顾函数重载 gt 函数重载的本质为相互独立的不同函数 gt C 中通过函数名和函数参数确定函数调用 gt 无法直接通过函数名得到重载函数的入口地址 gt 函数重载必然发生在同一个作用域中 2 类中的重载 类中的成员函数可以进行重载 g
  • JNI线程

    作者 左少华 博客 http blog csdn net shaohuazuo article details 43149193 转载请注明出处 http blog csdn net shaohuazuo 1 Android线程介绍 1 线