一、APP客户端进程与后台服务进程的AIDL通信

  AIDL(Android Interface definition language-“接口定义语言”) 是 Android 提供的一种进程间通信 (IPC:Inter-Process Communication) 机制,支持的数据类型:

  1. Java 的原生类型;
  2. String 和CharSequence;
  3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型;  以上三种类型都不需要导入(import);
  4. AIDL 自动生成的接口  需要导入(import);
  5. 实现android.os.Parcelable 接口的类.  需要导入(import)。

Android studio工程建立如下:

app和remoteserver按常规应用建立,remoteservicecontract通过新建Android Library生成:

也可以将原本的应用模块改成库模块:

然后在remoteservicecontract建立aidl目录并新建AIDL文件:

建立如下三个AIDL接口:

aidl文件的声明和java实现如下:

(1)Entity.aidl 是声明本地实现的 android.os.Parcelable 接口的类

  1. // Entity.aidl
  2. package com.example.remoteserver;
  3. parcelable Entity;

java实现:

  1. package com.example.remoteserver;
  2.  
  3. import android.os.Parcel;
  4. import android.os.Parcelable;
  5. import android.util.Log;
  6.  
  7. public class Entity implements Parcelable {
  8. private int age;
  9. private String name;
  10. private final String TAG = "Engity";
  11.  
  12. public Entity() {
  13. }
  14.  
  15. public Entity(int age, String name) {
  16. Log.i(TAG,"new age="+age+",name="+name);
  17. this.age = age;
  18. this.name = name;
  19. }
  20.  
  21. protected Entity(Parcel in) {
  22. age = in.readInt();
  23. name = in.readString();
  24. }
  25.  
  26. public static final Creator<Entity> CREATOR = new Creator<Entity>() {
  27. @Override
  28. public Entity createFromParcel(Parcel in) {
  29. return new Entity(in);
  30. }
  31.  
  32. @Override
  33. public Entity[] newArray(int size) {
  34. return new Entity[size];
  35. }
  36. };
  37.  
  38. public int getAge() {
  39. Log.i(TAG,"get age="+age);
  40. return this.age;
  41. }
  42.  
  43. public void setAge(int age) {
  44. Log.i(TAG,"set age="+age);
  45. this.age = age;
  46. }
  47.  
  48. public String getName() {
  49. Log.i(TAG,"get name="+name);
  50. return this.name;
  51. }
  52.  
  53. public void setName(String name) {
  54. Log.i(TAG,"set name="+name);
  55. this.name = name;
  56. }
  57.  
  58. @Override
  59. public int describeContents() {
  60. return 0;
  61. }
  62.  
  63. @Override
  64. public void writeToParcel(Parcel dest, int flags) {
  65. dest.writeInt(age);
  66. dest.writeString(name);
  67. }
  68.  
  69. @Override
  70. public String toString() {
  71. return String.format("age=%s, name=%s", getAge(), getName());
  72. }
  73. }
  1. 2IRemoteService.aidl声明服务端供客户端调用的接口:
  1. // IRemoteService.aidl
  2. package com.example.remoteserver;
  3. import com.example.remoteserver.Entity;
  4. import com.example.remoteserver.ITVCallback;
  5.  
  6. // Declare any non-default types here with import statements
  7.  
  8. interface IRemoteService {
  9.  
  10. void doSomeThing(int anInt,String aString);
  11.  
  12. void addEntity(in Entity entity);
  13.  
  14. void setEntity(int index,in Entity entity);
  15.  
  16. List<Entity> getEntity();
  17.  
  18. void asyncCallSomeone( String para, ITVCallback callback);
  19.  
  20. }
  1. java实现:
  1. package com.example.remoteserver;
  2.  
  3. import android.Manifest;
  4. import android.app.Service;import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.pm.PackageManager;
  7. import android.location.LocationManager;
  8. import android.os.Build;
  9. import android.os.Handler;
  10. import android.os.IBinder;
  11. import android.os.Looper;
  12. import android.os.RemoteCallbackList;
  13. import android.os.RemoteException;
  14. import android.provider.Settings;
  15. import android.support.annotation.Nullable;
  16. import android.support.v4.content.ContextCompat;
  17. import android.util.Log;
  18. import android.view.LayoutInflater;
  19. import android.view.View;
  20. import android.view.ViewGroup;
  21. import android.widget.BaseAdapter;
  22. import android.widget.Toast;
  23.  
  24. import java.nio.ByteBuffer;
  25. import java.util.ArrayList;
  26. import java.util.List;
  27.  
  28. public class RemoteService extends Service {
  29. public static final String TAG = "RemoteService";
  30. private List<Entity> data = new ArrayList<Entity>();
       int mStartMode; // indicates how to behave if the service is killed
  31. final RemoteCallbackList<ITVCallback> remoteCallbackList = new RemoteCallbackList<>();
  32.  
  33. public void onCreate() {
  34. // Used to load the 'native-lib' library on application startup.
  35. System.loadLibrary("RemoteServiceJNI"); //加载native接口的c库
  36. pthreadState = true;
  37. DataThread datathread = new DataThread();
  38. datathread.start();
  39. Nano_Printf("service onCreate");
  40.  
  41. Nano_Printf(String.format("<%s>",stringFromJNI())); //调用JNI接口
  42. }
  43. public int onStartCommand(Intent intent, int flags, int startId) {
  44. Nano_Printf("service onStartCommand");
  45. return mStartMode;
  46. }
  47. /*返回Binder对象实例*/
  48. public IBinder onBind(Intent intent) {
  49. Nano_Printf("service on bind,intent = %s",intent.toString());
  50. return binder;
  51. }
  52.  
  53. public void onDestroy() {
  54. Nano_Printf("service onDestroy");
  55. pthreadState = false;
  56. // 取消掉所有的回调
  57. remoteCallbackList.kill();
  58. }
  59.  
  60. private void Nano_Printf(String...args) {
  61. String str = "";
  62. for(int i = 0; i < args.length; i++){
  63. str += args[i];
  64. if( i != args.length - 1){
  65. str += ", ";
  66. }
  67. }
  68. Log.d(TAG, str);
  69. }

  70.    /*生成的 Binder 对象实例,实现接口定义的方法*/
  71. private final IRemoteService.Stub binder = new IRemoteService.Stub() {
  72.  
  73. @Override
  74. public void doSomeThing(int anInt, String aString) throws RemoteException {
  75. Log.i(TAG, String.format("rcv:%s, %s", anInt, aString));
  76. }
  77.  
  78. @Override
  79. public void addEntity(Entity entity) throws RemoteException {
  80. Log.i(TAG, String.format("rcv:entity = %s", entity));
  81. data.add(entity);
  82. }
  83.  
  84. @Override
  85. public List<Entity> getEntity() throws RemoteException {
  86. Log.i(TAG, String.format("get:List<Entity> = %s", data));
  87. return data;
  88. }
  89.  
  90. public void setEntity(int index, Entity entity) throws RemoteException {
  91. Log.i(TAG, String.format("set:entity[%d] = %s", index, entity));
  92. data.set(index, entity);
  93. }
  94.  
  95. @Override
         /*客户端调用asyncCallSomeone接口并传过来callback实例,服务端注册callback并回调修改结果*/
  96. public void asyncCallSomeone(String para, ITVCallback callback) throws RemoteException {
  97.  
  98. Log.i(TAG, String.format("asyncCallSomeone..."));
  99.  
  100. remoteCallbackList.register(callback);
  101.  
  102. final int len = remoteCallbackList.beginBroadcast();
  103. for (int i = 0; i < len; i++) {
  104. remoteCallbackList.getBroadcastItem(i).onSuccess(para + "_callbck");
  105. }
  106. remoteCallbackList.finishBroadcast();
  107. }
  108. };
  109.  
  110. /*native interface*/
  111. public native String stringFromJNI();
  112. }
  1. 3ITVCallback.aidl声明客户端向服务端注册的回调接口:
  1. // Callback.aidl
  2. package com.example.remoteserver;
  3.  
  4. // Declare any non-default types here with import statements
  5.  
  6. interface ITVCallback {
  7. /**
  8. * Demonstrates some basic types that you can use as parameters
  9. * and return values in AIDL.
  10. */
  11. void onSuccess(String aString);
  12. }

app客户端Java实现:

  1. package com.example.administrator.sheldon_aidl;
  2.  
  3. import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.nfc.Tag;
    import android.os.IBinder;
    import android.os.Looper;
    import android.os.RemoteException;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Toast;
  4.  
  5. /*导入资源库中AIDL定义的类*/
    import com.example.remoteserver.Entity;
    import com.example.remoteserver.ITVCallback;
    import com.example.remoteserver.IRemoteService;
  6.  
  7. import java.util.List;
  8.  
  9. public class MainActivity extends AppCompatActivity {
  10.  
  11. private boolean mBound = false;
    private IRemoteService iRemoteService;
  12.  
  13. @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  1. findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    if (!mBound) {
    alert("未连接到远程服务");
    return;
    }
    try {
    Entity entity = new Entity(100, "sheldon");
    if (iRemoteService != null){
    iRemoteService.addEntity(entity); //调用服务端的接口添加成员变量
  2.  
  3. iRemoteService.registerCallBack(mCallback);
    }
  4.  
  5. } catch (RemoteException e) {
    e.printStackTrace();
    }
    }
    });
  1. findViewById(R.id.modify).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    if (!mBound) {
    alert("未连接到远程服务");
    return;
    }
  2.  
  3. if (iRemoteService != null) {
    try {
    List<Entity> entityList = iRemoteService.getEntity();
    int pos = 1;
    if(entityList.size()>pos){
    entityList.get(pos).setAge(1314);
    entityList.get(pos).setName("li");
    iRemoteService.setEntity(pos,entityList.get(pos)); //调用服务端的接口修改成员变量
    }
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    }
    }
    });
  4.  
  5. findViewById(R.id.callback).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    if (!mBound) {
    alert("未连接到远程服务");
    return;
    }
  6.  
  7. if (iRemoteService != null) {
    try {
    final String para = "canshu";
    iRemoteService.asyncCallSomeone(para, mCallback); //调用服务端的接口并传入回调
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    }
    }
    });
    }
  8.  
  9. private void alert(String str) {
    //解决在子线程中调用Toast的异常情况处理(还是有异常)
    //Looper.prepare();
    Toast.makeText(this, str, 0).show();
    //Looper.loop();
    }
  10.  
  11. @Override
    protected void onStart() {
    super.onStart();
    if (!mBound) {
    attemptToBindService();    // 尝试绑定服务
    }
    }
  12.  
  13. @Override
    protected void onStop() {
    super.onStop();
    if (mBound) {
    unbindService(mServiceConnection); // 解绑服务
    mBound = false;
    }
    }
  14.  
  15. /**
    * 尝试与服务端建立连接
    */
    private void attemptToBindService() {
    Intent intent = new Intent();
    intent.setAction("com.example.REMOTE.myserver"); //这里的action由..\remoteserver\src\main\AndroidManifest.xml中指定
    intent.setPackage("com.example.remoteserver");  //这里即为服务端进程包名
    bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
  16.  
  17.   /*实现 ServiceConnection 接口,在其中拿到IRemoteService AIDL类*/
    private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    Log.i(getLocalClassName(), "service connected");
    iRemoteService = IRemoteService.Stub.asInterface(service);
  18.  
  19. mBound = true;
  20.  
  21. if (iRemoteService != null) {
    try {
    iRemoteService.doSomeThing(0, "anything string");
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    }
    }
  22.  
  23. @Override
    public void onServiceDisconnected(ComponentName name) {
    Log.i(getLocalClassName(), "service disconnected");
    mBound = false;
    }
    };
  24.  
  25. /*实现callback接口*/
    private ITVCallback mCallback = new ITVCallback.Stub() {
    @Override
    public void onSuccess(String aString) throws RemoteException { //回调接口被服务端调用,获得结果并用Toast显示
    Log.d("nano-client ", String.format("service arrived %s",aString));
    alert(String.format("回调: %s", aString));
    }
    };
    }
  1. 客户端和服务端的通信AIDL接口定义在remoteservicecontract库中,需要在各模块导入使用,
    如果各模块在同一个Android Studio工程开发,可通过修改build.gradle直接应用:

也可以将生成的aar,提供给另一个工程导入使用:

  1. 二、后台服务的JNI接口实现:
    目录结构如下:
  1. 1.声明native方法,如 RemoteService.java 中声明的:
  1.  
  1. /*native interface*/
  2. public native String stringFromJNI();
  1. 2.通过javah生成native格式的头文件 com_example_remoteserver_RemoteService.h

  javah -d 【头文件生成路径】 -classpath 【java文件路径】-jni 【包名.类名】

  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class com_example_remoteserver_RemoteService */
  4.  
  5. #ifndef _Included_com_example_remoteserver_RemoteService
  6. #define _Included_com_example_remoteserver_RemoteService
  7. #ifdef __cplusplus
  8. extern "C" {
  9. #endif
  10. /*
  11. * Class: com_example_remoteserver_RemoteService
  12. * Method: stringFromJNI
  13. * Signature: ()Ljava/lang/String;
    */
  1. JNIEXPORT jstring JNICALL Java_com_example_remoteserver_RemoteService_stringFromJNI
  2. (JNIEnv *, jobject);
  3.  
  4. #ifdef __cplusplus
  5. }
  6. #endif
  7. #endif
  1.  

3.根据生成的jni头文件建立 RemoteServiceJNI.c 文件实现其接口:

  1. #include <jni.h>
  2. #include <string.h>
  3. #include <android/log.h>
  4.  
  5. #define TAG "nano-jni"
  6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
  7. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
  8. #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
  9. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
  10. #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
  11.  
  12. #ifdef __cplusplus
  13. extern "C" {
  14. #endif
  15.  
  16. /*
  17. * Class: com_example_remoteserver_RemoteService
  18. * Method: stringFromJNI
  19. * Signature: ()Ljava/lang/String;
    参数说明:
       Native的对应函数名要以“Java_”开头,后面依次跟上Java的“package名”、“class名”、“函数名”,中间以下划线“_” 分割,在package名中的“.”也要改为“_”。

  关于函数的参数和返回值也有相应的规则。对于Java中的基本类型如int 、double 、char 等,
  在Native端都有相对应的类型来表示,如jint 、jdouble 、jchar 等;其他的对象类型则统统由jobject 来表示,
  (String 是个例外,由于其使用广泛,故在Native代码中有jstring 这个类型来表示)。
  而对于Java中的数组,在Native中由jarray 对应,具体到基本类型和一般对象类型的数组则有jintArray
  和jobjectArray 分别对应(String 数组在这里没有例外,同样用jobjectArray 表示)。
  另外在JNI的Native函数中,其前两个参数JNIEnv *jobject 是必需的,前者是一个JNIEnv 结构体的指针,这个结构体中定义了很多JNI的接口函数指针,
  使开发者可以使用JNI所定义的接口功能;后者指代的是调用这个JNI函数的Java对象,有点类似于C++中的this 指针。
  在上述两个参数之后,还需要根据Java端的函数声明依次对应添加参数,如下Java中声明的JNI函数没有参数,则Native的对应函数只有类型为JNIEnv *和jobject 的两个参数。

  1. */
  2. JNIEXPORT jstring JNICALL Java_com_example_remoteserver_RemoteService_stringFromJNI
  3. (JNIEnv *env, jobject thiz){
  4. return (*env)->NewStringUTF(env, "Hi! Sheldon, I`m JNI ~");
  5. }
  6.  
  7. #ifdef __cplusplus
  8. }
  9. #endif

4.编译c文件生成so:

  1. Android studio gradle3.0版本以下可以配置NDK编译c/cpp文件:
    修改对应模块的build.gradledefaultConfig中添加:
  1. //gradle3.0以上已经不支持该方式
  2. ndk {
  3. moduleName "libRemoteServiceJNI" //指定生成的so文件名
  4. ldLibs "log", "z", "m" //添加log库
  5. abiFilters "armeabi", "armeabi-v7a", "x86" //支持cpu的类型
  6. }

而gradle3.0以上版本需要用CMake工具编译:

首先Android studio安装CMake工具:

然后同样在defaultConfig{}中添加编译参数:

  1. // 使用Cmake工具
  2. externalNativeBuild {
  3. cmake {
  4. cppFlags ""
  5. //生成多个版本的so文件
  6. abiFilters 'armeabi-v7a' //,'arm64-v8a','x86','x86_64'
  7. }
  8. }

另外在defaultConfig{}的外一层即android{}中配置编译脚本的路径:

  1. // 配置CMakeLists.txt路径
  2. externalNativeBuild {
  3. cmake {
  4. path "CMakeLists.txt" //编译脚本
  5. }
  6. }

比较关键的是CMakeLists.txt编译脚本,具体内容如下:

  1. ###############################
  2. #.cmake verson,指定cmake版本
  3. cmake_minimum_required(VERSION 3.4.)
  4.  
  5. #.C++ 的编译选项是 CMAKE_CXX_FLAGS
  6. # 指定编译参数,可选
  7. #SET(CMAKE_C_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")
  8.  
  9. #.设置cmake生成so输出的路径
  10. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
  11.  
  12. #.包含头文件,导入第三方动态库
  13. include_directories(
  14. ${CMAKE_SOURCE_DIR}/src/main/jni/include
  15. )
  16.  
  17. #.指定源文件和编译生成so名及类型
  18. # 生成在intermediates/cmake/和以上指定的目录下(指定的话build.gradle设置pickFirst避免冲突)
  19. add_library(RemoteServiceJNI SHARED
  20. ${CMAKE_SOURCE_DIR}/src/main/jni/RemoteServiceJNI.c)
  21.  
  22. #.设置需要生成so的第三方链接库
  23. target_link_libraries(
  24. RemoteServiceJNI
  25. log
  26. android
  27. )
  28.  
  29. #添加子目录,将会调用子目录中的CMakeLists.txt
  30. #ADD_SUBDIRECTORY(one)
  31. #ADD_SUBDIRECTORY(two)
  32. ###############################

配置好编译环境后,点击make project生成so在remoteserver\build\intermediates\cmake\debug\obj\armeabi-v7a\libRemoteServiceJNI.so

在java中加载调用即可:

如果遇到: More than one file was found with OS independent path 'lib/armeabi-v7a/xxx.so' 的报错,则在build.gradle中的android {}里添加:

  1. packagingOptions { //For Error: More than one file was found with OS independent path
  2. pickFirst 'lib/armeabi-v7a/libnano_socket.so'
  3. pickFirst 'lib/armeabi-v7a/libRemoteServiceJNI.so'
  4. }

完整工程已上传到GitHub: https://github.com/dragonforgithub/sheldon_aidl.git

  1.  

Android : App客户端与后台服务的AIDL通信以及后台服务的JNI接口实现的更多相关文章

  1. Android App加固原理与技术历程

    App为什么会被破解入侵 随着黑客技术的普及化平民化,App,这个承载我们移动数字工作和生活的重要工具,不仅是黑客眼中的肥肉,也获得更多网友的关注.百度一下"App破解"就有529 ...

  2. eShopOnContainers 看微服务⑤:消息通信

    1.消息通信 传统的单体应用,组件间的调用都是使用代码级的方法函数.比如用户登录自动签到,增加积分.我们可以在登录函数调用积分模块的某个函数,为了解耦我们使用以来注入并放弃new Class()这种方 ...

  3. “快的打车”创始人陈伟星的新项目招人啦,高薪急招Java服务端/Android/Ios 客户端研发工程师/ mysql DBA/ app市场推广专家,欢迎大家加入我们的团队! - V2EX

    "快的打车"创始人陈伟星的新项目招人啦,高薪急招Java服务端/Android/Ios 客户端研发工程师/ mysql DBA/ app市场推广专家,欢迎大家加入我们的团队! - ...

  4. Android服务之AIDL

    在android开发过程中,为了让其他的应用程序,也可以访问本应用程序的服务,android系统采用远程过程调用来实现.android通过接口来公开定义的服务.我们将能够夸进程访问的服务成为AIDL服 ...

  5. Android -- service的开启方式, start开启和绑定开启服务,调用服务的的方法, aidl调用远程服务

    1. 概述 bindService() 绑定服务  可以得到服务的代理人对象,间接调用服务里面的方法. 绑定服务: 间接调用服务里面的方法.           如果调用者activity被销毁了, ...

  6. 玩转OneNET物联网平台之MQTT服务④ —— 远程控制LED(设备自注册)+ Android App控制

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  7. 玩转OneNET物联网平台之MQTT服务⑦ —— 远程控制LED(数量无限制)+ Android App控制 优化第一版

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  8. 鉴权应用服务器 app客户端 web服务端 安全令牌(SecurityToken)、临时访问密钥(AccessKeyId, AccessKeySecret)

    设置EndPoint和凭证 移动终端是一个不受信任的环境,把AccessKeyId和AccessKeySecret直接保存在终端用来加签请求,存在极高的风险.建议只在测试时使用明文设置模式,业务应用推 ...

  9. Android中AIDL通信机制分析

    一.背景 ·1.AIDL出现的原因 在android系统中,每一个程序都是运行在自己的进程中,进程之间无法进行通讯,为了在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需 ...

随机推荐

  1. word之选中文本

    在word和notepad中: 特别是在文件很大,如果用鼠标下滑的话,不知道会滑多久呢, 快捷键+鼠标点击截至处

  2. day01 格式化输出和while循环的两个小练习

    练习1.模拟登陆判断(3次机会) r_name = 'xianyu' r_password = ' i = 3 # 用来控制循环次数 while i > 0: name = input('请输入 ...

  3. linux中make的有关规则的特性

    我过去认为 makefile 只是一种将一组组的 shell 命令列出来的简便方法:过了一段时间我了解到它们是有多么的强大.灵活以及功能齐全.这篇文章带你领略其中一些有关规则的特性. 规则 规则是指示 ...

  4. 51Nod 1058 N的阶乘的长度

    输入N求N的阶乘的10进制表示的长度.例如6! = 720,长度为3.   Input 输入N(1 <= N <= 10^6) Output 输出N的阶乘的长度 Input示例 6 Out ...

  5. K8S学习笔记之Kubernetes 部署策略详解

    0x00 概述 在Kubernetes中有几种不同的方式发布应用,所以为了让应用在升级期间依然平稳提供服务,选择一个正确的发布策略就非常重要了. 选择正确的部署策略是要依赖于我们的业务需求的,下面我们 ...

  6. opencv学习之路(24)、轮廓查找与绘制(三)——凸包

    一.简介 二.绘制点集的凸包 #include<opencv2/opencv.hpp> using namespace cv; void main() { //---绘制点集的凸包 Mat ...

  7. servlet数据库登录

    一.首先建立如下目录: 二.在html文件中编写代码 三.编写实体类 四.编写服务器相关代码 五.编写数据库代码 六.运行截图 输入错误: 输入正确: 链接:https://pan.baidu.com ...

  8. zabbix链接规则

    通过磁盘  Disk for discovery    custom.vfs.dev.discovery 配置自动发现参考

  9. 【HNOI 2018】寻宝游戏

    Problem Description 某大学每年都会有一次 \(Mystery\ Hunt\) 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为新 ...

  10. ehcache 简介和基本api使用

    文章转载自: https://blog.csdn.net/zhouzhiwengang/article/details/59838105 1.ehcahce简介 在开发高并发量,高性能的网站应用系统时 ...