Android JNI開發從0到1,java調C,C調Java,保姆級教程詳解

前些天發現了一個蠻有意思的人工智能學習網站,8個字形容一下"通俗易懂,風趣幽默",感覺非常有意思,忍不住分享一下給大家。
👉點擊跳轉到教程

第一步首先配置Android studio的NDK開發環境,首先在Android studio中下載NDK包

在這里插入圖片描述
第二步在local.properties文件中,配置對應的NDK路徑
在這里插入圖片描述
第三歩,在app目錄下的build.gradle文件中的,android{}閉包中,指定CMakeLists.txt路徑

// 在android節點下// 指定CMakeLists.txt路徑externalNativeBuild {cmake {// 在該文件種設置所要編寫的c源碼位置,以及編譯后so文件的名字path 'CMakeLists.txt'}}

在defaultConfig閉包下配置

// 增加cmake控制屬性externalNativeBuild {cmake {// 指定編譯架構abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'}}

在app目錄下,指定CmakeLists.txt文件,根據注釋進行相應添加

# CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.
#CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
add_library(# 設置so文件名稱.Hello# 設置這個so文件為共享.SHARED# Provides a relative path to your source file(s).src/main/jni/Hello.c)
add_library(# 設置so文件名稱.Test# 設置這個so文件為共享.SHARED# Provides a relative path to your source file(s).src/main/jni/Test.c)
add_library(# 設置so文件名稱.CCallJava# 設置這個so文件為共享.SHARED# Provides a relative path to your source file(s).src/main/jni/CCallJava.c)# 添加 log 庫的鏈接
target_link_libraries(CCallJava log)
target_link_libraries(Test log)# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.# 制定目標庫.Hello# Links the target library to the log library# included in the NDK.${log-lib} )

第四步,首先進行Java代碼C代碼的操作
1.JNI2.java代碼如下

/*** @Author: ly* @Date: 2023/8/6* @Description: java調C代碼*/
public class JNI2 {static {System.loadLibrary("Test"); //加載動態鏈接庫}/*** 讓C代碼做加法運算,把結果返回* 場景:大量運算,編解碼之類的,需要性能很高的情況下,可以用C代碼** @param x* @param y* @return*/public native int add(int x, int y);/*** 從java傳入字符串,C代碼進行拼接** @param s I am from java* @return I am from java and I am from c*/public native String sayHello(String s);/*** 讓C代碼給每個元素都加上10** @param intArray* @return*/public native int[] increaseArrayEles(int[] intArray);/*** 應用:檢查密碼是否正確,如果正確返回200,否則返回400** @param pwd* @return*/public native int checkPwd(String pwd);
}

2.根據命令行javah +JNI2的全類名,生成Test.c對應的C代碼頭文件com_example_jniproject_JNI2.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jniproject_JNI2 */#ifndef _Included_com_example_jniproject_JNI2
#define _Included_com_example_jniproject_JNI2
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     com_example_jniproject_JNI2* Method:    add* Signature: (II)I*/
JNIEXPORT jint JNICALL Java_com_example_jniproject_JNI2_add(JNIEnv *, jobject, jint, jint);/** Class:     com_example_jniproject_JNI2* Method:    sayHello* Signature: (Ljava/lang/String;)Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_com_example_jniproject_JNI2_sayHello(JNIEnv *, jobject, jstring);/** Class:     com_example_jniproject_JNI2* Method:    increaseArrayEles* Signature: ([I)[I*/
JNIEXPORT jintArray JNICALL Java_com_example_jniproject_JNI2_increaseArrayEles(JNIEnv *, jobject, jintArray);/** Class:     com_example_jniproject_JNI2* Method:    checkPwd* Signature: (Ljava/lang/String;)I*/
JNIEXPORT jint JNICALL Java_com_example_jniproject_JNI2_checkPwd(JNIEnv *, jobject, jstring);#ifdef __cplusplus
}
#endif
#endif

3.根據jni協議,去寫對應的c代碼,創建Test.c文件,代碼如下

//
// Created by DELL on 2023/8/6.
//
#include "com_example_jniproject_JNI2.h"
#include "string.h"
#include <stdlib.h>#include <android/log.h>#define TAG "luyu"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定義LOGF類型JNIEXPORT char *JNICALL Java_com_example_MyClass_myMethod(JNIEnv *env, jobject obj, jstring jstr) {const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);// 使用 cstr 進行操作,并獲取結果(假設結果為 result)(*env)->ReleaseStringUTFChars(env, jstr, cstr);char *result = (char *) malloc(strlen(cstr) + 1);strcpy(result, cstr);return result;
}/*** jint:返回值* Java_全類名_方法名* JNIEnv *env*/
jint Java_com_example_jniproject_JNI2_add(JNIEnv *env, jobject jobj, jint ji, jint jj) {int result = ji + jj;return result;
};/*** 從java傳入字符串,C代碼進行拼接** @param s I am from java* @return I am from java and I am from c
*/jstring Java_com_example_jniproject_JNI2_sayHello(JNIEnv *env, jobject jobj, jstring jstring1) {char *fromJava = Java_com_example_MyClass_myMethod(env, jobj, jstring1);char *fromC = " and I am from C";//拼接函數,拼接后得到的結果放到第一個參數里面strcat(fromJava, fromC);//把拼接的結果放在第一個參數里面//jstring     (*NewStringUTF)(JNIEnv*, const char*);LOGE("fromJava==%s\n", fromJava);return (*env)->NewStringUTF(env, fromJava);
};/*** 給每個元素加10* 場景:圖片處理,顏色矩陣(就是數組),進行數組的處理*/
jintArray Java_com_example_jniproject_JNI2_increaseArrayEles(JNIEnv *env, jobject jobj, jintArray jintArray1) {//1.得到數組的長度//jsize       (*GetArrayLength)(JNIEnv*, jarray);jsize size = (*env)->GetArrayLength(env, jintArray1);//2.得到數組的元素// jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);jint *intArray = (*env)->GetIntArrayElements(env, jintArray1,JNI_FALSE);//這里傳0 false表示 在同一份內存操作,不開辟新的內存//3.遍歷數組,給每個元素加10int i;for (i = 0; i < size; i++) {*(intArray + i) += 10;}// 4. 同步修改到 Java 層(*env)->ReleaseIntArrayElements(env, jintArray1, intArray, 0);//4.返回結果return jintArray1;
};/*** 應用:檢查密碼是否正確,如果正確返回200,否則返回400*/
jint Java_com_example_jniproject_JNI2_checkPwd(JNIEnv *env, jobject jobj, jstring jstring1) {//假設服務器的密碼是123456char *origin = "123456";char *fromUser = Java_com_example_MyClass_myMethod(env, jobj, jstring1);//函數比較字符串是否相同int code = strcmp(origin, fromUser);LOGE("code==%d\n", code);if (code == 0) {return 200;} else {return 400;}
};

第五步,C代碼調用Java代碼,首先創建JNI3.java

/*** @Author: ly* @Date: 2023/8/6* @Description: C代碼調用Java代碼*/
public class JNI3 {static {System.loadLibrary("CCallJava"); //加載動態鏈接庫}//當執行這個方法的時候,讓C代碼調用//public int add(int x, int y)public native void callbackAdd();/*** 當執行這個方法的時候,讓C代碼調用* public void helloFromJava()*/public native void callbackHelloFromJava();/*** 當執行這個方法的時候,讓C代碼調用* public void printString(String s)*/public native void callbackPrintString();/*** 當執行這個方法的時候,讓C代碼調用* public static void sayHello(String s)*/public native void callbackSayHello();public int add(int x, int y) {Log.e("TAG", "add()  x=" + x + " y=" + y);return x + y;}public void helloFromJava() {Log.e("TAG", "helloFromJava");}public void printString(String s) {Log.e("TAG", "C中輸入的:" + s);}public static void sayHello(String s) {Log.e("TAG", "我是java代碼中的JNI" +".java中的sayHello(String s)靜態方法,我被C調用了:" + s);}
}

2.根據命令行,javah +JNI3全類名,生成CCallJava.c對應的頭文件
com_example_jniproject_JNI3.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jniproject_JNI3 */#ifndef _Included_com_example_jniproject_JNI3
#define _Included_com_example_jniproject_JNI3
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     com_example_jniproject_JNI3* Method:    callbackAdd* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackAdd(JNIEnv *, jobject);/** Class:     com_example_jniproject_JNI3* Method:    callbackHelloFromJava* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackHelloFromJava(JNIEnv *, jobject);/** Class:     com_example_jniproject_JNI3* Method:    callbackPrintString* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackPrintString(JNIEnv *, jobject);/** Class:     com_example_jniproject_JNI3* Method:    callbackSayHello* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackSayHello(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif

3.對應的CCallJava.c文件中的代碼如下

//
// Created by DELL on 2023/8/6.
//
#include "com_example_jniproject_JNI3.h"
#include <stdlib.h>
#include <stdio.h>#include <android/log.h>#define TAG "luyu"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定義LOGF類型/*** 讓C代碼調用Java中JNI類的public int add(int x ,int y)*/
JNIEXPORT void Java_com_example_jniproject_JNI3_callbackAdd(JNIEnv *env, jobject jobj) {//1.得到字節碼//jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");//2.得到方法//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);//最后一個參數是方法簽名jmethodID jmethodId = (*env)->GetMethodID(env, jclazz, "add", "(II)I");//3.實例化該類jobject jobject1 = (*env)->AllocObject(env, jclazz);//4.調用方法//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);jint value = (*env)->CallIntMethod(env, jobject1, jmethodId, 99, 1);//成功調用LOGE("value==%d\n", value);
};/**** 讓C代碼調用* public void helloFromJava()*/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackHelloFromJava(JNIEnv *env, jobject jobj) {//1.得到字節碼//jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");//2.得到方法//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);//最后一個參數是方法簽名jmethodID jmethodIds = (*env)->GetMethodID(env, jclazz, "helloFromJava", "()V");//3.實例化該類jobject jobject1 = (*env)->AllocObject(env, jclazz);//4.調用方法//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);(*env)->CallVoidMethod(env, jobject1, jmethodIds);
};/**** 讓C代碼調用* public void printString(String s)**/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackPrintString(JNIEnv *env, jobject jobj) {//1.得到字節碼//jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");//2.得到方法//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);//最后一個參數是方法簽名jmethodID jmethodIds = (*env)->GetMethodID(env, jclazz, "printString", "(Ljava/lang/String;)V");//3.實例化該類jobject jobject1 = (*env)->AllocObject(env, jclazz);//4.調用方法//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);// jstring     (*NewStringUTF)(JNIEnv*, const char*);jstring jst = (**env).NewStringUTF(env, "I am Android!");(*env)->CallVoidMethod(env, jobject1, jmethodIds, jst);
};/*** 讓C代碼調用* public static void sayHello(String s)**/
JNIEXPORT void JNICALL Java_com_example_jniproject_JNI3_callbackSayHello(JNIEnv *env, jobject jobj){//1.得到字節碼//jclass      (*FindClass)(JNIEnv*, const char*);jclass jclazz = (*env)->FindClass(env, "com/example/jniproject/JNI3");//2.得到方法// jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);//最后一個參數是方法簽名jmethodID jmethodIds = (*env)->GetStaticMethodID(env, jclazz, "sayHello", "(Ljava/lang/String;)V");jstring jst = (**env).NewStringUTF(env, "I am Android!");(*env)->CallStaticVoidMethod(env,jclazz,jmethodIds,jst);
};

之后,在MainActivity中調用對應的方法即可

public class MainActivity extends AppCompatActivity {private JNI2 jin2;private static final String TAG = "MainActivity";private JNI3 mJNI3;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);jin2 = new JNI2();mJNI3 = new JNI3();}/******以下是Java代碼調用C*****/public void checkPwd(View view) {int result = jin2.checkPwd("12345678");Log.i(TAG, "result==" + result);}public void increaseArrayEles(View view) {int array[] = {1, 2, 3, 4};jin2.increaseArrayEles(array);for (int i = 0; i < array.length; i++) {Log.i(TAG, "result[" + i + "]:" + array[i]);}}public void sayHello(View view) {String result = jin2.sayHello("I am from java");Log.i(TAG, "result==" + result);}public void add(View view) {int result = jin2.add(99, 1);Log.i(TAG, "result==" + result);}/******以下是C調Java代碼*****/public void callbackAdd(View view) {mJNI3.callbackAdd();}public void callbackHelloFromJava(View view) {mJNI3.callbackHelloFromJava();}public void callbackPrintString(View view) {mJNI3.callbackPrintString();}public void callbackSayHello(View view) {mJNI3.callbackSayHello();}
}

點擊按鈕便可輸出對應的日志,表示調用成功。

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35119.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35119.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35119.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何防止CSRF攻擊

背景 隨著互聯網的高速發展&#xff0c;信息安全問題已經成為企業最為關注的焦點之一&#xff0c;而前端又是引發企業安全問題的高危據點。在移動互聯網時代&#xff0c;前端人員除了傳統的 XSS、CSRF 等安全問題之外&#xff0c;又時常遭遇網絡劫持、非法調用 Hybrid API 等新…

Java超級瑪麗小游戲制作過程講解 第六天 創建背景類

package com.sxt;import java.awt.image.BufferedImage;public class BackGround {//當前場景要顯示的圖像 private BufferedImage bgImagenull;//記錄當前是第幾個場景 private int sort;//判斷是否是最后一個場景 private boolean flag;public BackGround(){}public BackGrou…

【CGroupAndroid實踐篇】四、Native Service進程啟動及控制組遷移

寫在前面 承接上一篇,在cgroup子系統初始化完成后,init會繼續執行開機流程,且通過代哦用ExecuteCommand()函數。 /system/core/init/init.cppint SecondStageMain(int argc, char** argv) {......ActionManager& am = ActionManager::GetInstance();am.ExecuteOneCo…

ASIC芯片設計全流程項目實戰課重磅上線 ,支持 65nm制程流片 !

全流程項目實戰課學什么&#xff1f; 此次推出【 ASIC芯片設計全流程項目實戰課】&#xff0c;基于IPA圖像處理加速器&#xff0c;以企業級真實ASIC項目為案例&#xff0c;學員可參與全流程項目實踐&#xff0c;以及65nm真實流片&#xff01; 眾所周知&#xff0c;放眼整個IC碩…

【Linux】【驅動】驅動框架以及掛載驅動

【Linux】【驅動】驅動框架以及掛載驅動 緒論1.配置開發環境2. 編寫驅動文件3. 編譯Makefile文件4.編譯5. 掛載驅動注意:有些開發板打開了或者禁止了printk信息&#xff0c;導致你看到的實驗現象可能不一樣&#xff0c;此時已經將文件移動到了開發板中&#xff0c;開發板查看文…

Flink CEP(Complex Event Processing)庫

復雜事件處理&#xff08;Complex Event Processing&#xff0c;CEP&#xff09;是一種用于在流式數據中識別和處理復雜事件模式的技術。Apache Flink 作為一個流式處理框架&#xff0c;也可以用于實現復雜事件處理。下面是 Flink 中實現復雜事件處理的一般原理&#xff1a; 事…

WebRTC音視頻通話-新增或修改SDP中的碼率Bitrate限制

WebRTC音視頻通話-新增或修改SDP中的碼率Bitrate限制參數 之前搭建ossrs服務&#xff0c;可以查看&#xff1a;https://blog.csdn.net/gloryFlow/article/details/132257196 之前實現iOS端調用ossrs音視頻通話&#xff0c;可以查看&#xff1a;https://blog.csdn.net/gloryFlo…

連接不上手機,adb devices為空:

首先說明一下&#xff0c;我是已經安裝了android studio,也配置了環境變量&#xff0c;但是還是連接不上手機 解決方案&#xff1a; 1.打開開發者模式 https://product.pconline.com.cn/itbk/sjtx/sjwt/1424/14246015.html 2.開啟usb調試 https://baiyunju.cc/10770 最后成功…

Nginx:Web基礎與HTTP協議

目錄 1、dns域名 1.1 dns解析方式&#xff1a; 1.2 域名解析服務器&#xff1a; 2、html 2.1 網頁、網站和主頁、域名 2.2 URL和URI 3、Web&#xff08;全球廣域網&#xff0c;也稱萬維網&#xff09; 3.1 靜態頁面 3.1.1 靜態頁面特點 3.2 動態頁面 3.2.1 動態頁面…

什么是CSS的box-sizing屬性?它有哪些取值,各有什么不同?

聚沙成塔每天進步一點點 ? 專欄簡介? CSS的box-sizing屬性? 取值? 不同之處? 寫在最后 ? 專欄簡介 前端入門之旅&#xff1a;探索Web開發的奇妙世界 記得點擊上方或者右側鏈接訂閱本專欄哦 幾何帶你啟航前端之旅 歡迎來到前端入門之旅&#xff01;這個專欄是為那些對Web…

關于Vue構建低代碼平臺的思考

一、前言 在項目實戰開發中&#xff0c;尤其是大平臺系統的搭建&#xff0c;針對不同業務場景&#xff0c;需要為用戶多次編寫用于錄入、修改、展示操作的相應表單頁面。一旦表單需求過多&#xff0c;對于開發人員來說&#xff0c;算是一種重復開發&#xff0c;甚至是繁雜的工作…

【C++起飛之路】初級—— auto、范圍for循環、宏函數和內聯函數

auto、范圍for、內聯函數、宏函數和nullptr 一、auto — 類型推導的魔法&#xff08;C 11)1、auto 是什么&#xff1f;2、工作原理3、優勢4、限制和注意事項 二、范圍for (C11)1、基本語法2、優勢3、工作原理4、注意事項5、C11&#xff1a; 范圍 for 循環的擴展&#xff1a; 三…

軟件測試基礎篇——LAMP環境搭建

LAMP 1、Linux系統的其他命令 find命令&#xff1a;在目錄下查找文件 ? 格式一&#xff1a;find 路徑 參數 文件名 ? 路徑&#xff1a;如果沒有指定路徑&#xff0c;默認是在當前目錄下 ? 參數&#xff1a;-name 根據文件名來查找&#xff0c;區分大小寫&#xff1b; -…

useState() 的使用及場景

useState是 React提供的一個Hook函數&#xff0c;用于在函數組件中添加和管理狀態。它允許你在函數組件中定義一個可變的狀態&#xff0c;并在組件的生命周期中對狀態進行更新和訪問。 使用useState可以避免使用類組件時需要定義和管理繁瑣的constructor&#xff0c;state和se…

HOT83-打家劫舍

leetcode原題鏈接&#xff1a;打家劫舍 題目描述 你是一個專業的小偷&#xff0c;計劃偷竊沿街的房屋。每間房內都藏有一定的現金&#xff0c;影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統&#xff0c;如果兩間相鄰的房屋在同一晚上被小偷闖入&#xff0c;系…

適配器模式(C++)

定義 將一個類的接口轉換成客戶希望的另一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。 應用場景 在軟件系統中&#xff0c;由于應用環境的變化&#xff0c;常常需要將“一些現存的對象 ”放在新的環境中應用&#xff0c;但是新環境要求…

【Golang】一文學完 Golang 基本語法

Golang 下載 安裝包鏈接&#xff1a;https://share.weiyun.com/InsZoHHu IDE 下載&#xff1a;https://www.jetbrains.com/go/ 第一個 golang 程序 package mainimport "fmt"func main() {fmt.Println("hello golang") }每個可執行代碼都必須包含 Pack…

Flutter 狀態管理 Provider

狀態管理必要性 Flutter基于聲明式構建UI&#xff0c;原生則是命令式&#xff0c;狀態管理是用于解決聲明式開發帶來的問題。 例&#xff1a;命令式的原生&#xff0c;數據更新需要拿到對應控件并更改其顯示值&#xff1b;而聲明式則需要更改數據值并通過setstate更新狀態&am…

sql高頻面試題-連續完成兩個指定動作的用戶統計

用戶行為分析 業務背景 某購物APP最近上線了一個新功能&#xff0c;用戶簽到后可以跳轉到大轉盤抽獎&#xff0c;抽獎獲得的獎金可以抵消購物的費用&#xff0c;以此來培養用戶使用app的習慣。 數據表介紹 現有一張用戶行為表action_log&#xff0c;主要字段如下&#xff0c…

springboot mongodb 配置多數據源

我想要的效果是&#xff0c;一個類統一管理多數據源&#xff0c;我傳個參數進去&#xff0c;它就能返回我對應的mongotemplate 但是根據"mongbodb 多數據源"的關鍵詞&#xff0c;找不到我想要的效果。 網上大多都是明確知道自己是幾個數據源&#xff0c;然后每個數…