[NDK/JNI系列04] JNI接口方法表、基础API与异常API

简介: [NDK/JNI系列04] JNI接口方法表、基础API与异常API
  • 学习了如何在C++中调用Java的一些类和方法,例如System.currentTimeMillis(), Integer.parseInt(), String.substring()和ArrayList。
  • 学习了如何使用JNI函数来查找类、获取方法ID、调用方法、创建对象、传递参数和返回值。
  • 学习了如何使用JNI函数来检查、清除、打印和抛出Java异常。
  • 学习了如何使用命名空间、常量和辅助函数来组织和简化的C++代码。
  • 学习了如何在Kotlin中定义和调用原生方法,并使用System.loadLibrary()来加载本地库。

4.1 JNI接口方法表

  • JNI接口方法表是一个结构体指针,它包含了JNI提供的所有函数的指针。JNI接口方法表的类型是JNIEnv,它是一个二级指针,指向一个JNIEnv_结构体,该结构体只有一个成员,即指向JNI函数表的指针。
  • JNI接口方法表是线程相关的,每个线程都有自己的JNIEnv。在原生函数中,第一个参数就是JNIEnv,可以通过它调用JNI函数。例如:
JNIEXPORT void JNICALL Java_com_example_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 调用JNI函数
    jclass cls = (*env)->FindClass(env, "java/lang/String");
    // ...
}
  • 在C++中,JNIEnv被定义为一个类,它包含了一个指向JNI函数表的指针,并且为每个JNI函数提供了一个成员函数,可以直接调用。例如:
JNIEXPORT void JNICALL Java_com_example_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 调用JNI函数
    jclass cls = env->FindClass("java/lang/String");
    // ...
}
  • JNIEnv的类型是一个指向JNI接口方法表的指针,它的定义如下:
typedef const struct JNINativeInterface *JNIEnv;
struct JNINativeInterface {
    void *reserved0;
    void *reserved1;
    void *reserved2;
 
    void *reserved3;
    jint (JNICALL *GetVersion)(JNIEnv *env);
 
    jclass (JNICALL *DefineClass)
      (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
       jsize len);
    jclass (JNICALL *FindClass)
      (JNIEnv *env, const char *name);
 
    /* ... more functions ... */
};
  • JNI接口方法表中的函数可以分为以下几类:
  • 版本信息:GetVersion
  • 类操作:DefineClass, FindClass, GetSuperclass, IsAssignableFrom等
  • 异常处理:Throw, ThrowNew, ExceptionOccurred, ExceptionDescribe等
  • 全局引用操作:NewGlobalRef, DeleteGlobalRef等
  • 局部引用操作:NewLocalRef, DeleteLocalRef等
  • 弱全局引用操作:NewWeakGlobalRef, DeleteWeakGlobalRef等
  • 监视器操作:MonitorEnter, MonitorExit等
  • Java对象操作:AllocObject, NewObject等
  • 字段访问:GetFieldID, GetObjectField等
  • 方法调用:GetMethodID, CallObjectMethod等
  • 数组操作:NewArray, GetArrayLength等
  • 字符串操作:NewStringUTF, GetStringUTFChars等
  • 直接缓冲区操作:NewDirectByteBuffer, GetDirectBufferAddress等

4.2 JNI基础API的使用

  • JNI基础API是一些常用的JNI函数,它们可以实现以下功能:
  • 查找Java类、字段和方法的ID
  • 调用Java实例方法和静态方法
  • 获取和设置Java实例字段和静态字段的值
  • 创建Java对象和数组
  • 操作Java字符串和原始类型数组
  • 抛出和处理Java异常
  • JNI基础API的函数名都遵循一定的命名规则,例如:
  • FindClass: 查找一个Java类。
  • GetMethodID / GetStaticMethodID: 获取Java方法的ID。
  • CallMethod / CallStaticMethod: 调用Java方法。
  • GetFieldID / GetStaticFieldID: 获取Java字段的ID。
  • GetField / SetField: 获取或设置Java字段的值。
  • 其中,表示返回值或参数的类型,可以是以下之一:
  • Boolean: 布尔型
  • Byte: 字节型
  • Char: 字符型
  • Short: 短整型
  • Int: 整型
  • Long: 长整型
  • Float: 浮点型
  • Double: 双精度浮点型
  • Object: 对象型
  • Void: 空类型
  • 下面是一些JNI基础API的使用示例:
// 查找java.lang.String类
jclass stringClass = (*env)->FindClass(env, "java/lang/String");
 
// 获取java.lang.String类的构造方法ID,参数为字节数组和编码名称
jmethodID stringConstructor = (*env)->GetMethodID(env, stringClass, "<init>", "([BLjava/lang/String;)V");
 
// 获取java.lang.String类的length方法ID,无参数,返回值为整型
jmethodID stringLength = (*env)->GetMethodID(env, stringClass, "length", "()I");
 
// 创建一个字节数组对象,长度为10
jbyteArray byteArray = (*env)->NewByteArray(env, 10);
 
// 填充字节数组对象的内容为"Hello JNI"
jbyte buf[10] = {'H', 'e', 'l', 'l', 'o', ' ', 'J', 'N', 'I', '\0'};
(*env)->SetByteArrayRegion(env, byteArray, 0, 10, buf);
 
// 创建一个字符串对象,表示"UTF-8"编码
jstring encoding = (*env)->NewStringUTF(env, "UTF-8");
 
// 调用java.lang.String类的构造方法,创建一个字符串对象,内容为"Hello JNI"
jstring str = (*env)->NewObject(env, stringClass, stringConstructor, byteArray, encoding);
 
// 调用java.lang.String类的length方法,获取字符串的长度
jint len = (*env)->CallIntMethod(env, str, stringLength);
 
// 打印字符串的长度
printf("The length of the string is %d\n", len);

4.3 JNI异常API的使用

  • JNI异常API是一些用于处理Java异常的JNI函数,它们可以实现以下功能:
  • 检查是否发生了Java异常
  • 清除Java异常
  • 获取Java异常对象
  • 抛出Java异常
  • 抛出新的Java异常
  • JNI异常API的函数名都以Exception开头,例如:
  • ExceptionOccurred: 检查是否发生了Java异常。
  • ExceptionClear: 清除Java异常。
  • ExceptionDescribe: 打印Java异常的堆栈跟踪信息。
  • ExceptionGetCause: 获取Java异常的原因对象。
  • Throw: 抛出Java异常对象。
  • ThrowNew: 抛出新的Java异常。
  • 下面是一些JNI异常API的使用示例:
// 查找java.lang.Integer类
jclass integerClass = (*env)->FindClass(env, "java/lang/Integer");
 
// 获取java.lang.Integer类的parseInt方法ID,参数为字符串,返回值为整型
jmethodID parseInt = (*env)->GetStaticMethodID(env, integerClass, "parseInt", "(Ljava/lang/String;)I");
 
// 创建一个字符串对象,内容为"123"
jstring str = (*env)->NewStringUTF(env, "123");
 
// 调用java.lang.Integer类的parseInt方法,将字符串转换为整数
jint num = (*env)->CallStaticIntMethod(env, integerClass, parseInt, str);
 
// 检查是否发生了Java异常
jthrowable exception = (*env)->ExceptionOccurred(env);
if (exception != NULL) {
    // 清除Java异常
    (*env)->ExceptionClear(env);
 
    // 获取Java异常的原因对象
    jthrowable cause = (*env)->ExceptionGetCause(env, exception);
 
    // 打印Java异常的堆栈跟踪信息
    (*env)->ExceptionDescribe(env, exception);
 
    // 抛出新的Java异常,类型为java.lang.RuntimeException,信息为"JNI error"
    jclass runtimeExceptionClass = (*env)->FindClass(env, "java/lang/RuntimeException");
    (*env)->ThrowNew(env, runtimeExceptionClass, "JNI error");
}
else {
    // 打印转换后的整数
    printf("The number is %d\n", num);
}

4.3 完整例子

Chapter04.java

class Chapter04 : AppCompatActivity() {
    // 定义一些原生方法,用于调用测试函数
    external fun testSystemCurrentTimeMillis(): Long // 原生方法:测试System.currentTimeMillis()函数的调用
    external fun testIntegerParseInt(str: String): Int // 原生方法:测试Integer.parseInt()函数的调用
    external fun testStringSubstring(str: String, beginIndex: Int, endIndex: Int): String // 原生方法:测试String.substring()函数的调用
    external fun testArrayList(): Any // 原生方法:测试ArrayList的调用
    var TAG = "Chapter04"; // 定义TAG变量用于日志输出
 
    private lateinit var binding: Chapter04Binding
 
    // 在onCreate方法中调用原生方法,并打印结果
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        this.setTitle("Chapter04")
        binding = Chapter04Binding.inflate(layoutInflater)
        setContentView(binding.root)
 
        // Example of a call to a native method
        binding.sampleText.text = stringFromJNI() // 调用原生方法,返回字符串,并显示在UI中
 
        // 调用测试函数并打印结果
        val time = testSystemCurrentTimeMillis() // 调用原生方法获取当前时间戳
        Log.d(TAG, "当前时间为:$time")
 
        try {
            val num = testIntegerParseInt("123A") // 调用原生方法尝试将字符串解析为整数(故意触发错误)
            // val num = testIntegerParseInt("123") // 调用原生方法将字符串解析为整数(正确示例)
            Log.d(TAG, "解析得到的数字为:$num")
        } catch (e: Exception) {
            e.printStackTrace()
        }
 
        val result = testStringSubstring("Hello JNI", 0, 5) // 调用原生方法截取字符串的子串
        Log.d(TAG, "截取得到的子串为:$result")
 
        val firstElement = testArrayList() // 调用原生方法获取ArrayList的第一个元素
        Log.d(TAG, "ArrayList的第一个元素为:$firstElement")
    }
 
    /**
     * 一个由本应用程序打包的 'jnidemo' native库实现的原生方法。
     */
    external fun stringFromJNI(): String // 原生方法:获取一个字符串
 
    companion object {
        // 用于在应用程序启动时加载 'jnidemo' 库。
        init {
            System.loadLibrary("jnidemo")
        }
    }
}

chapter04.cpp

#include <jni.h>
#include <string>
 
using namespace chapter04; // 使用chapter04命名空间
 
// 定义一些常量,表示类名和方法签名
const char* SYSTEM_CLASS = "java/lang/System";
const char* INTEGER_CLASS = "java/lang/Integer";
const char* STRING_CLASS = "java/lang/String";
const char* ARRAYLIST_CLASS = "java/util/ArrayList";
 
const char* LONG_RETURN_VOID_ARG = "()J";
const char* INT_RETURN_STRING_ARG = "(Ljava/lang/String;)I";
const char* STRING_RETURN_INT_INT_ARG = "(II)Ljava/lang/String;";
const char* VOID_RETURN_VOID_ARG = "()V";
const char* BOOLEAN_RETURN_VOID_ARG = "()Z";
const char* BOOLEAN_RETURN_OBJECT_ARG = "(Ljava/lang/Object;)Z"; // 定义这个常量
const char* VOID_RETURN_OBJECT_ARG = "(Ljava/lang/Object;)V";
const char* OBJECT_RETURN_INT_ARG = "(I)Ljava/lang/Object;";
 
// 定义一个辅助函数,用于检查并打印JNI异常
void checkAndPrintException(JNIEnv *env) {
    jthrowable exception = env->ExceptionOccurred(); // 检查是否发生了Java异常
    if (exception != NULL) {
        env->ExceptionDescribe(); // 打印Java异常的堆栈跟踪信息
    }
}
 
// 定义一个测试函数,用于调用Java的System.currentTimeMillis()方法
jlong testSystemCurrentTimeMillis(JNIEnv *env) {
    // 查找System类
    jclass systemClass = env->FindClass(SYSTEM_CLASS);
    checkAndPrintException(env);
 
    // 获取currentTimeMillis方法的ID
    jmethodID currentTimeMillisMethod = env->GetStaticMethodID(systemClass, "currentTimeMillis", LONG_RETURN_VOID_ARG);
    checkAndPrintException(env);
 
    // 调用currentTimeMillis方法,获取当前时间的毫秒数
    jlong time = env->CallStaticLongMethod(systemClass, currentTimeMillisMethod);
    checkAndPrintException(env);
 
    // 返回时间值
    return time;
}
 
// 定义一个测试函数,用于调用Java的Integer.parseInt()方法
jint testIntegerParseInt(JNIEnv *env, jstring str) {
    // 查找Integer类
    jclass integerClass = env->FindClass(INTEGER_CLASS);
    checkAndPrintException(env);
 
    // 获取parseInt方法的ID
    jmethodID parseIntMethod = env->GetStaticMethodID(integerClass, "parseInt", INT_RETURN_STRING_ARG);
    checkAndPrintException(env);
 
    // 调用parseInt方法,将字符串转换为整数
    jint num = env->CallStaticIntMethod(integerClass, parseIntMethod, str);
    checkAndPrintException(env);
 
    // 检查是否发生了Java异常
    jthrowable exception = env->ExceptionOccurred();
    if (exception != NULL) {
        // 清除Java异常
        env->ExceptionClear();
 
        // 获取Java异常的原因对象(如果API版本低于24,注释掉这一行)
        // jthrowable cause = env->ExceptionGetCause(exception);
 
        // 打印Java异常的堆栈跟踪信息
        env->ExceptionDescribe(); // 修改函数调用,不传入任何参数
        checkAndPrintException(env);
 
        // 抛出新的Java异常,类型为java.lang.RuntimeException,信息为"JNI error"
        jclass runtimeExceptionClass = env->FindClass("java/lang/RuntimeException");
        env->ThrowNew(runtimeExceptionClass, "JNI error 》 testIntegerParseInt");
    }
 
    // 返回转换后的整数
    return num;
}
 
 
// 定义一个测试函数,用于调用Java的String.substring()方法
jstring testStringSubstring(JNIEnv *env, jstring str, jint beginIndex, jint endIndex) {
    // 查找String类
    jclass stringClass = env->FindClass(STRING_CLASS);
    checkAndPrintException(env);
 
    // 获取substring方法的ID
    jmethodID substringMethod = env->GetMethodID(stringClass, "substring", STRING_RETURN_INT_INT_ARG);
    checkAndPrintException(env);
 
    // 调用substring方法,截取字符串的一部分
    jstring result = (jstring) env->CallObjectMethod(str, substringMethod, beginIndex, endIndex);
    checkAndPrintException(env);
 
    // 检查是否发生了Java异常
    jthrowable exception = env->ExceptionOccurred();
    if (exception != NULL) {
        // 清除Java异常
        env->ExceptionClear();
 
        // 返回空字符串
        result = env->NewStringUTF("");
    }
 
    // 返回截取后的字符串
    return result;
}
 
// 定义一个测试函数,用于创建一个Java的ArrayList对象,并向其中添加一些元素,然后获取其中的第一个元素
jobject testArrayList(JNIEnv *env) {
    // 查找ArrayList类
    jclass arrayListClass = env->FindClass(ARRAYLIST_CLASS);
    checkAndPrintException(env);
 
    // 获取ArrayList类的构造方法ID,无参数,返回值为void
    jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", VOID_RETURN_VOID_ARG);
    checkAndPrintException(env);
 
    // 获取ArrayList类的add方法ID,参数为对象,返回值为布尔型
    jmethodID arrayListAdd = env->GetMethodID(arrayListClass, "add", BOOLEAN_RETURN_OBJECT_ARG);
    checkAndPrintException(env);
 
    // 获取ArrayList类的get方法ID,参数为整型,返回值为对象
    jmethodID arrayListGet = env->GetMethodID(arrayListClass, "get", OBJECT_RETURN_INT_ARG);
    checkAndPrintException(env);
 
    // 创建一个ArrayList对象
    jobject arrayList = env->NewObject(arrayListClass, arrayListConstructor);
    checkAndPrintException(env);
 
    // 创建一些字符串对象,并向ArrayList中添加
    jstring str1 = env->NewStringUTF("Hello");
    jstring str2 = env->NewStringUTF("JNI");
    jstring str3 = env->NewStringUTF("World");
    env->CallBooleanMethod(arrayList, arrayListAdd, str1);
    env->CallBooleanMethod(arrayList, arrayListAdd, str2);
    env->CallBooleanMethod(arrayList, arrayListAdd, str3);
    checkAndPrintException(env);
 
    // 获取ArrayList中的第一个元素
    jobject firstElement = env->CallObjectMethod(arrayList, arrayListGet, 0);
    checkAndPrintException(env);
 
    // 返回第一个元素
    return firstElement;
}
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_ln28_jnidemo_Chapter04_stringFromJNI( // 修改函数名前缀,与Chapter04.kt中对应
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
 
extern "C" JNIEXPORT jlong JNICALL
Java_com_ln28_jnidemo_Chapter04_testSystemCurrentTimeMillis( // 修改函数名前缀,与Chapter04.kt中对应
        JNIEnv* env,
        jobject /* this */) {
    return testSystemCurrentTimeMillis(env);
}
 
extern "C" JNIEXPORT jint JNICALL
Java_com_ln28_jnidemo_Chapter04_testIntegerParseInt( // 修改函数名前缀,与Chapter04.kt中对应
        JNIEnv* env,
        jobject /* this */,
        jstring str) {
    return testIntegerParseInt(env, str);
}
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_ln28_jnidemo_Chapter04_testStringSubstring( // 修改函数名前缀,与Chapter04.kt中对应
        JNIEnv* env,
        jobject /* this */,
        jstring str,
        jint beginIndex,
        jint endIndex) {
    return testStringSubstring(env, str, beginIndex, endIndex);
}
 
extern "C" JNIEXPORT jobject JNICALL
Java_com_ln28_jnidemo_Chapter04_testArrayList( // 修改函数名前缀,与Chapter04.kt中对应
        JNIEnv* env,
        jobject /* this */) {
    return testArrayList(env);
}

异常打印:

java.lang.NumberFormatException: For input string: "123A"
java.lang.RuntimeException: JNI error 》 testIntegerParseInt
异常被捕获,程序没有崩溃。

4.4 疑问和问题解答

JNI接口方法表:

  1. JNI接口方法表是什么?它的作用是什么?
    JNI接口方法表是一个结构体,其中包含了所有可用的JNI函数的指针。可以通过在C/C++代码中使用JNIEnv指针来调用这些函数。这个结构体为提供了一个与Java交互的接口,如访问和修改Java对象的字段,调用Java方法,处理Java异常等。
  2. 如何使用JNI接口方法表?
    在C/C++代码中,可以通过JNIEnv指针来访问JNI接口方法表中的函数。例如,可以使用(*env)->GetObjectField(env, obj, fieldID)来访问一个Java对象的字段。
  3. JNI接口方法表中的函数有哪些?我需要了解所有的函数吗?
    JNI接口方法表中包含了大约100多个函数,这些函数提供了丰富的功能,如创建Java对象,访问和修改Java对象的字段,调用Java方法,抛出和处理Java异常等。不需要了解所有的函数,只需要掌握常用的一些函数就可以了。
  4. 有没有一个示例展示如何使用JNI接口方法表的某个函数?
    当然,例如,下面的代码展示了如何使用JNI接口方法表的GetObjectField函数和SetIntField函数来访问和修改Java对象的字段:
jclass cls = (*env)->GetObjectClass(env, obj);
jfieldID fieldID = (*env)->GetFieldID(env, cls, "intValue", "I");
jint value = (*env)->GetIntField(env, obj, fieldID);
value++;
(*env)->SetIntField(env, obj, fieldID, value);

       5.我是否可以修改JNI接口方法表?

不,不能修改JNI接口方法表。这个结构体是由JNI环境提供的,只能通过JNIEnv指针来访问它,不能修改它。

JNI基础API:

  1. JNI基础API包含哪些函数?
    JNI基础API包含了许多函数,如创建Java对象,访问和修改Java对象的字段,调用Java方法,抛出和处理Java异常等。一些常用的函数包括NewObjectGetObjectClassGetFieldIDGetObjectFieldSetObjectFieldCallVoidMethod等。
  2. JNI基础API的主要作用是什么?
    JNI基础API的主要作用是提供一个接口,让可以在C/C++代码中与Java交互。可以通过JNI基础API来创建Java对象,访问和修改Java对象的字段,调用Java方法,抛出和处理Java异常等。
  3. 如何使用JNI基础API访问和修改Java对象的字段?
    可以使用GetFieldID函数来获取一个字段的ID,然后使用Get<Type>FieldSet<Type>Field函数来访问和修改该字段。例如:

jclass cls = (*env)->GetObjectClass(env, obj);

jfieldID fieldID = (*env)->GetFieldID(env, cls, "intValue", "I");

jint value = (*env)->GetIntField(env, obj, fieldID);

value++;

(*env)->SetIntField(env, obj, fieldID, value);

       4.如何使用JNI基础API调用Java方法

可以使用GetMethodID或者GetStaticMethodID函数来获取一个方法的ID,然后使用Call<Type>Method或者CallStatic<Type>Method函数来调用该方法。例如:

jclass cls = (*env)->GetObjectClass(env, obj);

jmethodID mid = (*env)->GetMethodID(env, cls, "print", "(I)V");

(*env)->CallVoidMethod(env, obj, mid, 123);

       5.如何创建和处理Java对象?

可以使用NewObject函数来创建一个新的Java对象,然后使用各种Get<Type>FieldSet<Type>Field函数来访问和修改其字段。如果需要处理Java对象的数组,可以使用NewObjectArrayGetObjectArrayElementSetObjectArrayElement等函数。

JNI异常API:

  1. JNI异常API是用来做什么的?
    JNI异常API用于在native代码中抛出和处理Java异常。当在C/C++代码中调用Java方法时,可能会发生Java异常,可以使用JNI异常API来检查和处理这些异常。2
  2. 如果在native方法中发生了Java异常,我应该如何处理?可以使用ExceptionOccurred函数来检查是否发生了异常,然后使用ExceptionDescribe函数来打印异常的堆栈轨迹,使用ExceptionClear函数来清除异常。如果想在native代码中处理异常,可以使用ThrowNew函数来抛出一个新的异常。
jmethodID mid = (*env)->GetMethodID(env, cls, "method", "()V");
if ((*env)->ExceptionOccurred(env)) {
    (*env)->ExceptionDescribe(env);
    (*env)->ExceptionClear(env);
    return;
}

       3.我应该如何使用JNI异常API抛出一个新的Java异常

可以使用ThrowNew函数来抛出一个新的Java异常。需要指定异常类和异常信息。

jclass exCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
if (exCls != NULL) {
    (*env)->ThrowNew(env, exCls, "Illegal argument");
}

       4.JNI异常API中的ExceptionCheckExceptionOccurred有什么区别?

ExceptionCheckExceptionOccurred函数都可以用来检查是否发生了Java异常,但是ExceptionCheck函数不会清除异常状态,而ExceptionOccurred函数会返回一个异常对象并清除异常状态。

       5。如果我忽略了一个Java异常(没有清除或者抛出),会发生什么?

如果在native方法中忽略了一个Java异常,这个方法将会立即返回,不会执行后面的代码。如果这个native方法是由Java调用的,Java将会收到这个异常。所以,如果不打算处理Java异常,应该至少使用ExceptionDescribe函数来打印异常的堆栈轨迹,然后使用ExceptionClear函数来清除异常,避免影响后续代码的执行。

(1) JNI 提示 | Android NDK | Android Developers - Android 开发者. JNI 提示  |  Android NDK  |  Android Developers.

(2) NDK系列:JNI基础 - 掘金. NDK系列:JNI基础 - 掘金.

相关文章
|
1天前
|
监控 数据挖掘 API
京东商品API接口:电商数据自动化的钥匙
京东商品API接口为电商领域带来了巨大的便利和潜力。通过本篇文章,读者应该能够了解如何使用京东商品API接口,并通过示例代码学习如何实际调用API获取商品数据。 随着技术的不断发展,京东商品API接口将继续为电商自动化、数据分析和市场研究提供支持,帮助商家和开发者在竞争激烈的市场中保持领先。我们鼓励读者继续探索和学习,充分利用京东商品API接口带来的机遇。
|
11天前
|
监控 安全 数据挖掘
Email 接口API有哪些?具体分析一下阿里云和AOK的优点
本文介绍了常见的Email接口API,如阿里云邮件推送、AOKSend、SendGrid、Mailgun和Amazon SES。阿里云API以其高稳定性和数据分析功能脱颖而出,支持批量发送和多语言;而AOKSend API以易于集成、高安全性和优秀客户支持为亮点。企业在选择时应考虑自身需求和预算,以优化邮件营销效果。
|
11天前
|
定位技术 API
Angular 调用导入百度地图API接口,2024春招BAT面试真题详解
Angular 调用导入百度地图API接口,2024春招BAT面试真题详解
|
12天前
|
JSON 安全 API
解锁淘宝商品评论API接口:电商数据分析的新视角
淘宝商品评论API接口是淘宝开放平台提供的一组API接口,允许开发者通过编程方式获取淘宝商品评论数据。这些接口可以获取到商品的详细信息、用户评论、评分等数据,为电商数据分析提供了丰富的素材。
|
12天前
|
缓存 负载均衡 安全
探索API接口开发(定制与开发接口)
在当今数字化、互联互通的时代,API(应用程序编程接口)已经成为连接不同软件、服务和应用的关键桥梁。API接口开发,作为软件架构和系统设计的重要组成部分,不仅影响着数据交换的效率,更决定了整个系统的灵活性和可扩展性。本文将深入探讨API接口开发的各个方面,包括其重要性、开发流程、最佳实践以及面临的挑战。
|
13天前
|
文字识别 API 开发工具
印刷文字识别产品使用合集之API接口无法调用如何解决
印刷文字识别(Optical Character Recognition, OCR)技术能够将图片、扫描文档或 PDF 中的印刷文字转化为可编辑和可搜索的数据。这项技术广泛应用于多个领域,以提高工作效率、促进信息数字化。以下是一些印刷文字识别产品使用的典型场景合集。
|
13天前
|
监控 供应链 数据挖掘
抖音电商新篇章:douyin.item_get API接口的深度应用
douyin.item_get接口最基本的应用就是获取商品的详细信息。商家可以通过输入商品的ID或链接,获取商品的标题、价格、图片、描述、规格等关键信息。这些信息对于商家来说至关重要,可以帮助他们了解商品的销售情况、竞争态势以及市场需求。
|
13天前
|
安全 API 开发者
智能体-Agent能力升级!新增Assistant API & Tools API服务接口
ModelScope-Agent是一个交互式创作空间,它支持LLM(Language Model)的扩展能力,例如工具调用(function calling)和知识检索(knowledge retrieval)。它已经对相关接口进行了开源,以提供更原子化的应用LLM能力。用户可以通过Modelscope-Agent上的不同代理(agent),结合自定义的LLM配置和消息,调用这些能力。
|
13天前
|
JSON 搜索推荐 数据挖掘
电商数据分析的利器:电商关键词搜索API接口(标题丨图片丨价格丨链接)
淘宝关键词搜索接口为电商领域的数据分析提供了丰富的数据源。通过有效利用这一接口,企业和研究人员可以更深入地洞察市场动态,优化营销策略,并提升用户体验。随着电商平台技术的不断进步,未来的API将更加智能和个性化,为电商行业带来更多的可能性。
|
13天前
|
存储 缓存 运维
DataWorks操作报错合集之DataWorks根据api,调用查询文件列表接口报错如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。

热门文章

最新文章