zoukankan      html  css  js  c++  java
  • android_c++ 高级编程NDK学习笔记五

      原生线程

    1 示例项目

    2 java线程

    创建项目NativeThread

    添加原生支持android tools--->add Native support

    创建用户界面: activity_main.xml文件

    <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    tools:context=".MainActivity">

    <EditText

    android:id="@+id/edt_threads"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:ems="10"

    android:focusable="true"

    android:hint="@string/threads_edit"

    android:inputType="number">

    </EditText>

    <EditText

    android:id="@+id/edt_iterations"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:ems="10"

    android:hint="@string/iterations_edit"

    android:inputType="number"/>

    <Button

    android:id="@+id/btn_start"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/btn_start"/>

    <ScrollView

    android:id="@+id/scrollview"

    android:layout_width="match_parent"

    android:layout_height="wrap_content">

    <TextView

    android:id="@+id/txt_log"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"/>

    </ScrollView>

    </LinearLayout>

    字符串资源: string.xml文件

    <?xmlversion="1.0"encoding="utf-8"?>

    <resources>

    <stringname="app_name">NativeThreads</string>

    <stringname="action_settings">Settings</string>

    <stringname="hello_world">NativeThreads</string>

    <stringname="threads_edit">Thread count</string>

    <stringname="iterations_edit">Iteration count</string>

    <stringname="btn_start">Start Threads</string>

    </resources>

    MainActivity.java文件

     

    /**

     * 原生线程

     *

     * @version

     *

     * @Description:

     *

     * @author <ahref="mailto:zhenhuayue@sina.com">Retacn</a>

     *

     * @since 2014-7-9

     *

     */

    public class MainActivity extends Activity {

           /* start button */

           private Buttonbtn_start;

           /* threads edit */

           private EditTextedt_threads;

           /* iterations edit*/

           private EditTextedt_iterations;

           /* log textView */

           private TextViewtxt_log;

     

           @Override

           protected voidonCreate(Bundle savedInstanceState) {

                  super.onCreate(savedInstanceState);

                  setContentView(R.layout.activity_main);

                  // 本地方法 初始化原生代码

                  nativeInit();

     

                  // 实例化控件

                  findView();

           }

     

           /**

            * 实例化控件

            */

           private voidfindView() {

                  btn_start =(Button) this.findViewById(R.id.btn_start);

                  btn_start.setOnClickListener(newOnClickListener() {

     

                         @Override

                         publicvoid onClick(View v) {

                                intthreads = NativeThreadUtils.getIntNumber(edt_threads.getText().toString(), 0);

                                intiterations = NativeThreadUtils.getIntNumber(edt_iterations.getText().toString(),0);

                                if(threads > 0 && iterations > 0) {

                                       //启动给定个数的线程进行迭代

                                       startThreads(threads,iterations);

                                }

                         }

                  });

                  edt_threads =(EditText) this.findViewById(R.id.edt_threads);

                  edt_iterations= (EditText) this.findViewById(R.id.edt_iterations);

                  txt_log =(TextView) this.findViewById(R.id.txt_log);

     

           }

     

           /**

            * 启动给定个数的线程进行迭代

            *

            * @param threads

            *           线程数

            * @param iterations

            *           迭代数

            */

           private voidstartThreads(int threads, final int iterations) {

                  javaThreads(threads,iterations);

           }

     

           /**

            * 使用基于 java的线程

            *

            * @param threads

            * @param iterations

            */

           private voidjavaThreads(int threads, final int iterations) {

                  // 为每一个worker创建军基于java的线程

                  for (int i = 0; i < threads; i++) {

                         finalint id = i;

                         Threadthread = new Thread() {

                                @Override

                                publicvoid run() {

                                       //

                                       nativeWorker(id,iterations);

                                }

                         };

                         thread.start();

                  }

           }

     

           /**

            * 原生线程回调

            *

            * @param message

            *           原生消息

            */

           private voidonNativeMessage(final String message) {

                  runOnUiThread(newRunnable() {

     

                         @Override

                         publicvoid run() {

                                txt_log.append(message);

                                txt_log.append(" ");

                         }

                  });

           }

     

           @Override

           public boolean onCreateOptionsMenu(Menumenu) {

                  getMenuInflater().inflate(R.menu.main,menu);

                  return true;

           }

     

           @Override

           protected voidonDestroy() {

                  // 释放原生资源

                  nativeFree();

                  super.onDestroy();

           }

     

           // 声明本地方法

           /* 初始化本地方法 */

           private native voidnativeInit();

     

           /* 释放原生资源 */

           private native voidnativeFree();

     

           /**

            * 原生worker

            *

            * @param id

            * @param iterations

            */

           private native voidnativeWorker(int id, int iterations);

     

           /**

            * 加载本地库

            */

           static {

                  System.loadLibrary("NativeThreads");

           }

    }

    生成c++头文件

    Projectexplorer--->MainActivity.java--->run--->external tools--->generatec and c++ header file

    将会在jni目录下生成cn_yue_nativethreads_MainActivity.h头文件,示例代码如下:

    /* DO NOT EDIT THIS FILE - it is machine generated */

    #include<jni.h>

    /* Header for class cn_yue_nativethreads_MainActivity */

    #ifndef _Included_cn_yue_nativethreads_MainActivity

    #define _Included_cn_yue_nativethreads_MainActivity

    #ifdef __cplusplus

    extern"C" {

    #endif

    #undef cn_yue_nativethreads_MainActivity_MODE_PRIVATE

    #define cn_yue_nativethreads_MainActivity_MODE_PRIVATE0L

    #undefcn_yue_nativethreads_MainActivity_MODE_WORLD_READABLE

    #definecn_yue_nativethreads_MainActivity_MODE_WORLD_READABLE 1L

    #undefcn_yue_nativethreads_MainActivity_MODE_WORLD_WRITEABLE

    #definecn_yue_nativethreads_MainActivity_MODE_WORLD_WRITEABLE 2L

    #undef cn_yue_nativethreads_MainActivity_MODE_APPEND

    #define cn_yue_nativethreads_MainActivity_MODE_APPEND32768L

    #undefcn_yue_nativethreads_MainActivity_MODE_MULTI_PROCESS

    #definecn_yue_nativethreads_MainActivity_MODE_MULTI_PROCESS 4L

    #undefcn_yue_nativethreads_MainActivity_BIND_AUTO_CREATE

    #definecn_yue_nativethreads_MainActivity_BIND_AUTO_CREATE 1L

    #undefcn_yue_nativethreads_MainActivity_BIND_DEBUG_UNBIND

    #define cn_yue_nativethreads_MainActivity_BIND_DEBUG_UNBIND2L

    #undefcn_yue_nativethreads_MainActivity_BIND_NOT_FOREGROUND

    #definecn_yue_nativethreads_MainActivity_BIND_NOT_FOREGROUND 4L

    #undefcn_yue_nativethreads_MainActivity_BIND_ABOVE_CLIENT

    #define cn_yue_nativethreads_MainActivity_BIND_ABOVE_CLIENT8L

    #undefcn_yue_nativethreads_MainActivity_BIND_ALLOW_OOM_MANAGEMENT

    #definecn_yue_nativethreads_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L

    #undefcn_yue_nativethreads_MainActivity_BIND_WAIVE_PRIORITY

    #define cn_yue_nativethreads_MainActivity_BIND_WAIVE_PRIORITY32L

    #undefcn_yue_nativethreads_MainActivity_BIND_IMPORTANT

    #definecn_yue_nativethreads_MainActivity_BIND_IMPORTANT 64L

    #undefcn_yue_nativethreads_MainActivity_BIND_ADJUST_WITH_ACTIVITY

    #define cn_yue_nativethreads_MainActivity_BIND_ADJUST_WITH_ACTIVITY64L

    #undefcn_yue_nativethreads_MainActivity_CONTEXT_INCLUDE_CODE

    #definecn_yue_nativethreads_MainActivity_CONTEXT_INCLUDE_CODE 1L

    #undefcn_yue_nativethreads_MainActivity_CONTEXT_IGNORE_SECURITY

    #define cn_yue_nativethreads_MainActivity_CONTEXT_IGNORE_SECURITY2L

    #undefcn_yue_nativethreads_MainActivity_CONTEXT_RESTRICTED

    #definecn_yue_nativethreads_MainActivity_CONTEXT_RESTRICTED 4L

    #undefcn_yue_nativethreads_MainActivity_RESULT_CANCELED

    #definecn_yue_nativethreads_MainActivity_RESULT_CANCELED 0L

    #undef cn_yue_nativethreads_MainActivity_RESULT_OK

    #define cn_yue_nativethreads_MainActivity_RESULT_OK -1L

    #undefcn_yue_nativethreads_MainActivity_RESULT_FIRST_USER

    #definecn_yue_nativethreads_MainActivity_RESULT_FIRST_USER 1L

    #undefcn_yue_nativethreads_MainActivity_DEFAULT_KEYS_DISABLE

    #definecn_yue_nativethreads_MainActivity_DEFAULT_KEYS_DISABLE 0L

    #undefcn_yue_nativethreads_MainActivity_DEFAULT_KEYS_DIALER

    #define cn_yue_nativethreads_MainActivity_DEFAULT_KEYS_DIALER1L

    #undefcn_yue_nativethreads_MainActivity_DEFAULT_KEYS_SHORTCUT

    #definecn_yue_nativethreads_MainActivity_DEFAULT_KEYS_SHORTCUT 2L

    #undefcn_yue_nativethreads_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL

    #definecn_yue_nativethreads_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L

    #undefcn_yue_nativethreads_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL

    #definecn_yue_nativethreads_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L

    /*

     * 初始化本地方法

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeInit

     * Signature: ()V

     */

    JNIEXPORT void JNICALL Java_cn_yue_nativethreads_MainActivity_nativeInit(

                  JNIEnv *, jobject);

    /*

     * 释放本地资源

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeFree

     * Signature: ()V

     */

     JNIEXPORT void JNICALL Java_cn_yue_nativethreads_MainActivity_nativeFree(

                  JNIEnv *, jobject);

    /*

     * 原生worker

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeWorker

     * Signature: (II)V

     */

     JNIEXPORT void JNICALL Java_cn_yue_nativethreads_MainActivity_nativeWorker(

                  JNIEnv *, jobject, jint, jint);

    #ifdef __cplusplus

    }

    #endif

    #endif

    实现原生函数

    /*

     *cn_yue_nativethreads_MainActivity.cpp

     *

     *  Created on: 2014-7-9

     *      Author: retacn

     *

     */

    #include<stdio.h>

    #include<unistd.h>

    #include"cn_yue_nativethreads_MainActivity.h"

    //方法ID能被缓存

    staticjmethodID gOnNativeMessage = NULL;

    /*

     * 初始化本地方法

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeInit

     * Signature: ()V

     */

    void JNICALL Java_cn_yue_nativethreads_MainActivity_nativeInit(JNIEnv * env,

                  jobject obj) {

           //如果方法没有被缓存

           if (NULL == gOnNativeMessage) {

                  //从对象中取得类

                  jclass clazz = env->GetObjectClass(obj);

                  //为回调方法id

                  gOnNativeMessage=env->GetMethodID(clazz, "onNativeMessage", "(Ljava/lang/String;)V");

                  //如果没有找到方法

                  if (NULL == gOnNativeMessage) {

                         //获取异常类

                         jclass exceptionClazz = env->FindClass(

                                       "java/lang/RuntimeException");

                         //抛出异常

                         env->ThrowNew(exceptionClazz,"Unable to find methond");

                  }

           }

    }

    /*

     * 释放本地资源

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeFree

     * Signature: ()V

     */

    void JNICALL Java_cn_yue_nativethreads_MainActivity_nativeFree(JNIEnv * env,

                  jobject obj) {

    }

    /*

     * 原生worker

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeWorker

     * Signature: (II)V

     */

    void JNICALL Java_cn_yue_nativethreads_MainActivity_nativeWorker(JNIEnv * env,

                  jobject obj, jint id, jint iterations) {

           //循环给定的迭代次数

           for (jint i = 0; i < iterations; i++) {

                  //准备消息

                  char message[26];

                  sprintf(message, "worker %d: iteration %d", id, i);

                  //来自c字符串的消息

                  jstring messageString = env->NewStringUTF(message);

                  //调用原生消息方法

                  env->CallVoidMethod(obj,gOnNativeMessage, messageString);

                  //检查是否发和异常

                  if (NULL != env->ExceptionOccurred()) {

                         break;

                  }

                  sleep(1);

           }

    }

    更新android.mk构建脚本

    LOCAL_PATH := $(call my-dir)

    include$(CLEAR_VARS)

    LOCAL_MODULE    := NativeThreads

    LOCAL_SRC_FILES :=cn_yue_nativethreads_MainActivity.cpp

    include$(BUILD_SHARED_LIBRARY)

    3 posix线程

     在原生代码中使用posix线程,需要添加#include <pthread.h>

    //成功返回0

    Int pthread_create(pthread_t thread,//用该指针返回新线程的句柄

                                      pthread_attr_tconst* cttr,//新线程属性

                                      void*(*start_routine) (void*),//指向中线程启动程序的指针

                                      void*arg);//

    在示例中使用posix线程

    A 声明原生方法

           /* 使用原生posix线程 */

           privatenativevoid posixThreads(int threads, int iterations);

    B 重新生成头文件

    /*

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    posixThreads

     * Signature: (II)V

     */

    JNIEXPORT void JNICALL Java_cn_yue_nativethreads_MainActivity_posixThreads

      (JNIEnv *, jobject, jint, jint);

    C 更新原生代码

      添加#include<pthread.h>

      定义启动参数结构体

    /*原生线程参数*/

    structNativeWorkerArgs {

           jintid;

           jintiterations;

    };

      全局变量保存java vm和对象实例的全局引用

    /*java虚拟机接口指针*/

    staticJavaVM* gVm = NULL;

    /*对象的全局引用*/

    staticjobject gObj = NULL;

    ...

    /**

     * 取得java虚拟机接口指针

     */

    jintJNI_OnLoad(JavaVM* vm, void* reserved) {

           //缓存java虚拟机接口批针

           gVm = vm;

           return JNI_VERSION_1_4;

    }

    voidJava_cn_yue_nativethreads_MainActivity_nativeInit(JNIEnv * env,

                  jobject obj) {

           //为对象实例创建一个全局引用

           if (NULL == gObj) {

                  //创建全局变量

                  gObj =env->NewGlobalRef(obj);

                  if (NULL == gObj) {

                         goto exit;

                  }

                                      }

    ...

    }

      在freeNative中删除全局引用

    //删除全局引用

           if (NULL != gObj) {

                  env->DeleteGlobalRef(gObj);

                  gObj = NULL;

                                      }

      为原生worker线程添加启动程序

    /**

     * 为原生worker添加启动程序

     */

    staticvoid* nativeWorkerThread(void* args) {

           JNIEnv* env = NULL;

    //将当前线程符加到java虚拟机上

           if (0 == gVm->AttachCurrentThread(&env,NULL)) {

                  //取得原生worker线程参数

                  NativeWorkerArgs* nativeWorkerArgs = (NativeWorkerArgs*) args;

                  //在线程上下文中运行wroker

                  Java_cn_yue_nativethreads_MainActivity_nativeWorker(env,gObj,

                                nativeWorkerArgs->id,nativeWorkerArgs->iterations);

                  //释放原生线程worker线程参数

                  delete nativeWorkerArgs;

                  //从java虚拟机中分离当前线程

                  gVm->DetachCurrentThread();

           }

           return (void*) 1;

    }

      实现posixThread原生方法

    /*

     *  posix线程

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    posixThreads

     * Signature: (II)V

     */

    voidJava_cn_yue_nativethreads_MainActivity_posixThreads(JNIEnv* env,

                  jobject obj, jint threads, jint iterations) {

           //为每一个worker创建一个posix线程

           for (jint i = 0; i < threads; i++) {

                  NativeWorkerArgs* nativeWorkerArgs = newNativeWorkerArgs();

                  nativeWorkerArgs->id= i;

                  nativeWorkerArgs->iterations = iterations;

                  pthread_t thread;

                  //创建一个新线程

                  int result = pthread_create(&thread, NULL, nativeWorkerThread,

                                (void*) nativeWorkerArgs);

                  if (0 != result) {

                         //获取异常类

                         jclass exceptionClazz = env->FindClass(

                                       "java/lang/RuntimeException");

                         //抛出异常

                         env->ThrowNew(exceptionClazz,"Unable to createthread!");

                  }

           }

    }

    4 从posix线程返回结果

    Java_cn_yue_nativethreads_MainActivity_posixThreads是在线程执行后立即返回,通过pthread_join可以合其在线程结束后返回

    //函数原型 成功返回结果为0

    intpthread_join(pthread_t thid,//目标线程

    void ** ret_val);//指向空指针的指针,从启动程序中取得返回值

    更改示例程序

    /*

     *  posix线程

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    posixThreads

     * Signature: (II)V

     */

    voidJava_cn_yue_nativethreads_MainActivity_posixThreads(JNIEnv* env,

                  jobject obj, jint threads, jint iterations) {

           //线程句柄

           pthread_t* handles = newpthread_t(threads);

           //为每一个worker创建一个posix线程

           for (jint i = 0; i < threads; i++) {

                  NativeWorkerArgs* nativeWorkerArgs = newNativeWorkerArgs();

                  nativeWorkerArgs->id= i;

                  nativeWorkerArgs->iterations = iterations;

                  pthread_t thread;

                  //创建一个新线程

                  int result = pthread_create(&handles[i], NULL, nativeWorkerThread,

                                (void*) nativeWorkerArgs);

                  if (0 != result) {

                         //获取异常类

                         jclass exceptionClazz = env->FindClass(

                                       "java/lang/RuntimeException");

                         //抛出异常

                         env->ThrowNew(exceptionClazz,"Unable to createthread!");

                         goto exit;

                  }

           }

           //等待线程终止

           for (jint i = 0; i < threads; i++) {

                  void* result = NULL;

                  //连接每个句柄

                  if (0 != pthread_join(handles[i], &result)) {

                         //获取异常类

                         jclass exceptionClazz = env->FindClass(

                                       "java/lang/RuntimeException");

                         //抛出异常

                         env->ThrowNew(exceptionClazz,"Unable to jointhread!");

                  } else {

                         //准备message

                         char message[26];

                         sprintf(message, "worker %d returned %d", i, result);

                         //

                         jstring messageString = env->NewStringUTF(message);

                         //调用原生消息方法

                         env->CallVoidMethod(obj,gOnNativeMessage, messageString);

                         //检查是否产生异常

                         if (NULL != env->ExceptionOccurred()) {

                                goto exit;

                         }

                  }

           }

           exit: return;

    }

    5 posix线程同步

    两种最常用的同步机制:

    A 互斥锁(mutexes) pthread_mutex_t展示互斥锁到原生代码

    使用互斥锁同步posix线程

    初始化互斥锁

    intpthread_mutex_init(pthread_mutex_t *mutex,//互斥变量

    constpthread_mutexattr_t *attr);//互斥锁定义

    如果第二个参数为空,则使用默认属性如下:

    #define __PTHREAD_MUTEX_INIT_VALUE           0

    #definePTHREAD_MUTEX_INITIALIZER        {__PTHREAD_MUTEX_INIT_VALUE}

    如果初始化成功互斥锁处于打开状态 ,返回值为0

    锁定互斥锁

    intpthread_mutex_lock(pthread_mutex_t *mutex);//参数为互斥锁指针

    解锁互斥锁

    intpthread_mutex_destroy(pthread_mutex_t *mutex);//参数为互斥锁指针

    在示例代码中使用互斥锁

      添加互斥锁到原生代码:

    /*互斥锁*/

    staticpthread_mutex_tmutex;

      在Java_cn_yue_nativethreads_MainActivity_nativeInit函数中初始化互斥锁

    //初始化互斥锁

           if (0 != pthread_mutex_init(&mutex, NULL)) {

                  //获取异常类

                  jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");

                  //抛出异常

                  env->ThrowNew(exceptionClazz,"Unable toinitialize mutex!");

                  goto exit;

                                      }

    在Java_cn_yue_nativethreads_MainActivity_nativeFree函数中销毁互斥锁

    //销毁互斥锁

           if (0 != pthread_mutex_destroy(&mutex)) {

                  //获取异常类

                  jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");

                  //抛出异常

                  env->ThrowNew(exceptionClazz,"Unable to destorymutex!");

                                      }

    Java_cn_yue_nativethreads_MainActivity_nativeWorker函数中使用互斥锁

    /*

     * 原生worker

     * Class:     cn_yue_nativethreads_MainActivity

     * Method:    nativeWorker

     * Signature: (II)V

     */

    voidJava_cn_yue_nativethreads_MainActivity_nativeWorker(JNIEnv * env,

                  jobject obj, jint id, jint iterations) {

           //锁定互斥锁

           if (0 != pthread_mutex_lock(&mutex)) {

                  //获取异常类

                  jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");

                  //抛出异常

                  env->ThrowNew(exceptionClazz,"Unable to lock mutex!");

                  goto exit;

           }

           //循环给定的迭代次数

           for (jint i = 0; i < iterations; i++) {

                  //准备消息

                  char message[26];

                  sprintf(message, "worker %d: iteration %d", id, i);

                  //来自c字符串的消息

                  jstring messageString = env->NewStringUTF(message);

                  //调用原生消息方法

                  env->CallVoidMethod(obj,gOnNativeMessage, messageString);

                  //检查是否发和异常

                  if (NULL != env->ExceptionOccurred()) {

                         break;

                  }

                  sleep(1);

           }

           //解锁互斥锁

           if (0 != pthread_mutex_unlock(&mutex)) {

                  //获取异常类

                  jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");

                  //抛出异常

                  env->ThrowNew(exceptionClazz,"Unable to unlock mutex!");

           }

           exit: return;

    }

    运行示例程序,得到锁的线程会先执行,以此类推

    B 信号量(semaphores)

    使用信号量同步posix线程

    添加头文件#include <semaphore.h>

    初始化信号量

    externintsem_init(sem_t *sem,//信号量变量指针

                                      int pshared,//共享标志

                                      unsignedint value);//初始值

    锁定信号量

    externintsem_wait(sem_t *);

    解锁信号量

    externintsem_post(sem_t *);

    销毁信号量

    externintsem_destroy(sem_t *);

    6 posix线程的优先级和调度策略

    最常使用的调度策略:

    SCHED_FIFO:先进先出,基于线程进入列表的时间进行排序,也可以基于优先级

    SCHED_RR:循环轮转,是线程执行时间加以限制的SCHED_FIFO,其目的避免线程独占可用的cpu时间

    以上调度策略在sched.h头文件中定义,可以在pthread_create创建线程时,用pthread_attr_t的sched_policy域来定义调度策略

    也可以在运行时调用,

    intpthread_setschedparam(pthread_t thid,//目标线程指针

    int poilcy,//调度策略

    structsched_paramconst * param);//参数

    posixThread的优先级

    在pthread_create创建线程时,用pthread_attr_t的sched_policy域来定义调度优先级,也可以在运行时调用,intpthread_setschedparam函数参数中的param中提供优先级

    Sched_get_priority_max和Sched_get_priority_min来查询优先级的最大最小值

  • 相关阅读:
    数组以字符串记录(字符串转数组)
    linux下OpenSSL的RSA密钥生成
    php rsa加密解密实例 及签名验证-自己实践
    php rsa加密解密实例
    PHP的openssl加密扩展使用小结
    支付宝开放平台 配置RSA(SHA1)密钥 OpenSSL配置公钥私钥对
    HTTP缓存控制
    java去任意范围的随机数
    (转)Eclipse4.2 Tomcat启动报错 A child container failed during start
    模态框事件介绍
  • 原文地址:https://www.cnblogs.com/retacn-yue/p/6194260.html
Copyright © 2011-2022 走看看