您现在的位置是:网站首页> Android
Android中Java与C++交互、库开发与使用及研发积累
- Android
- 2025-07-09
- 1018人已阅读
Android中Java与C++交互、库开发与使用及研发积累
Android可以用最简单的办法也就是类静态变量来实现Actvity之间的数据传递
Android中System.loadLibrary("ModuleAPI");实际加载的动态库文件名libModuleAPI.so
经验技术收集
当出现莫名的编译错误时候可以清除项目再编译,Run->Clean Project
库开发使用
Android 的aar和jar的区别有哪些,使用中需要注意哪些
常见问题
新版Android Studio火烈鸟 在新建项目工程时 无法选java的语言模板解决方法
其他技术
带C代码的Android项目NDK配置
Android Studio 中 Ndk的环境配置以及简单使用
配置NDK路径
下载完NDK后,你需要在项目的local.properties文件中设置NDK的路径。
在你的项目根目录下找到或创建一个local.properties文件。
在local.properties文件中添加以下行,替换<path_to_ndk>为你的NDK的实际路径:
ndk.dir=/Users/yourusername/Library/Android/sdk/ndk/21.3.6528147
ndk.dir=d\:\\Andoid\\ndk-bundle
sdk.dir=C\:\\Users\\xn\\AppData\\Local\\Android\\Sdk
配置CMake或ndk-build
根据你使用的构建系统(CMake或ndk-build),你需要在项目的build.gradle文件中进行一些配置。
对于使用CMake的项目:
在app模块的build.gradle文件中,确保你有以下配置:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2" // 或者你需要的版本
}
}
}
...
}
在src/main/cpp目录下创建或修改CMakeLists.txt文件来配置你的原生代码。
对于使用ndk-build的项目:
在app模块的build.gradle文件中,确保你有以下配置:
android {
...
defaultConfig {
...
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
}
...
}
在src/main/jni目录下创建或修改Android.mk文件来配置你的原生代码。
简单理理AndroidStudio 生成SO并调用
工程结构如下
文件结构如下:
在调用的地方定义在 MainActivity类里
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
...
}
在native-lib.cpp中实现
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_xn_helloso_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
函数名称就是Java+包地址+类+函数名
编译需要在module下的build.gradle文件里加
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
生成的so动态库在:app\build\intermediates\cmake\debug\obj
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.
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.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
# 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.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
AndroidStudio引用第三方so库的正确姿势
1、把so文件复制到 \app1\app\libs\ 文件夹下,但是要注意,so文件是放在对应的平台文件夹之下(如arm64-v8a,armeabi-v7a, x86,x86_64),这点非常重要,否则不能成功引用,每个平台文件夹下都放上该so文件,如下图:
2、AndroidStudio打开项目,并切换到 Android 栏,并打开Gradle Scripts\build.gradle(Module:app1.app) ,加入 节点
sourceSets{
main{
jniLibs.srcDirs "libs"
}
}
如下图:
3、加完之后,有一个刷新(同步)的操作 ,之后在app下就可以看到jniLibs文件夹,如下:
通过JNI定义实现调用第三方库so如上面的native-lib.cpp文件
Android引用jar和so
其实jni、jniLibs一个是C++源码提供一个是C++编译成so提供,libs、java一个是以Java编译的jar,一个是以Java代码提供
该工程源码:点击下载:SDKApp.rar
ModuleAPI.c
#include <jni.h>
#include <ModuleAPI.h>
#include <Logger.h>
#include <SerialPort.h>
#include <jni.h>
static const char *TAG = "ModuleAPI";
//#define MSG_CRC_INIT 0xFFFF
//#define MSG_CCITT_CRC_POLY 0x1021
//void CRC_calcCrc8(uint16_t *crcReg, uint16_t poly, uint16_t u8Data)
//{
// uint16_t i;
// uint16_t xorFlag;
// uint16_t bit;
// uint16_t dcdBitMask = 0x80;
// for(i=0; i<8; i++)
// {
// xorFlag = *crcReg & 0x8000;
// *crcReg <<= 1;
// bit = ((u8Data & dcdBitMask) == dcdBitMask);
// *crcReg |= bit;
// if(xorFlag)
// {
// *crcReg = *crcReg ^ poly;
// }
// dcdBitMask >>= 1;
// }
//}
//
//uint16_t CalcCRC(uint8_t *msgbuf,uint8_t msglen)
//{
// uint16_t calcCrc = MSG_CRC_INIT;
// uint8_t i;
// for (i = 1; i < msglen; ++i)
// CRC_calcCrc8(&calcCrc, MSG_CCITT_CRC_POLY, msgbuf[i]);
// return calcCrc;
//}
#define MSG_CRC_INIT 0xFFFF
#define MSG_CCITT_CRC_POLY 0x1021
#define uint16 unsigned short
#define uint8 unsigned char
void CRC_calcCrc8(uint16 *crcReg, uint16 poly, uint16 u8Data) {
uint16 i;
uint16 xorFlag;
uint16 bit;
uint16 dcdBitMask = 0x80;
for (i = 0; i < 8; i++) {
xorFlag = *crcReg & 0x8000;
*crcReg <<= 1;
bit = ((u8Data & dcdBitMask) == dcdBitMask);
*crcReg |= bit;
if (xorFlag) {
*crcReg = *crcReg ^ poly;
}
dcdBitMask >>= 1;
}
}
uint16 CalcCRC(uint8 *msgbuf, uint8 msglen) {
uint16 calcCrc = MSG_CRC_INIT;
uint8 i;
for (i = 0; i < msglen; ++i)
CRC_calcCrc8(&calcCrc, MSG_CCITT_CRC_POLY, msgbuf[i]);
return calcCrc;
}
JNIEXPORT jint JNICALL Java_com_xlzn_hcpda_ModuleAPI_SerailOpen(JNIEnv* env, jobject thiz, jstring juart,jint baudrate, jint databits,jint stopbits, jint check) {
jboolean iscopy;
LOGD(TAG, "Java_com_xlzn_hcpda_ModuleAPI_SerailOpen");
const char *path_uart = (*env)->GetStringUTFChars(env, juart, &iscopy);
int result= SerialPort_Open(path_uart, baudrate, databits, stopbits, check);
(*env)->ReleaseStringUTFChars(env, juart, path_uart);
return result;
return 0;
}
JNIEXPORT jint JNICALL Java_com_xlzn_hcpda_ModuleAPI_SerailClose(JNIEnv *env, jobject thiz,int uart_fd) {
int result= SerialPort_Close(uart_fd);
return result;
}
JNIEXPORT jint JNICALL Java_com_xlzn_hcpda_ModuleAPI_SerailSendData(JNIEnv *env, jobject thiz, int uart_fd,jbyteArray send_data,int sendLen) {
unsigned char uData[sendLen];
jbyte *jpszData = (*env)->GetByteArrayElements(env, send_data, 0);
for (int i = 0; i < sendLen; i++) {
uData[i] = jpszData[i];
}
int reuslt= SerialPort_Send(uData,sendLen,uart_fd);
(*env)->ReleaseByteArrayElements(env, send_data , jpszData, 0);
return reuslt;
}
JNIEXPORT jint JNICALL Java_com_xlzn_hcpda_ModuleAPI_SerailReceive(JNIEnv *env, jobject thiz, jint uart_fd,jbyteArray receive_data,int receive_dataLen) {
unsigned char uData[receive_dataLen];
int reuslt= SerialPort_Receive(uData,receive_dataLen,uart_fd);
if(reuslt>0){
jbyte *jpszData = (*env)->GetByteArrayElements(env, receive_data, 0);
for (int i = 0; i < reuslt; i++) {
jpszData[i] = uData[i];
}
(*env)->ReleaseByteArrayElements(env, receive_data , jpszData, 0);
}
return reuslt;
}
JNIEXPORT jint JNICALL Java_com_xlzn_hcpda_ModuleAPI_CalcCRC(JNIEnv *env, jobject thiz, jbyteArray jdata, jint data_len,jbyteArray jout_crc){
jbyte *jpszData = (*env)->GetByteArrayElements(env, jdata, 0);
jbyte *outData = (*env)->GetByteArrayElements(env, jout_crc, 0);
uint16_t result=CalcCRC(jpszData,data_len);
outData[0]=result>>8;
outData[1]=result&0xFF;
(*env)->ReleaseByteArrayElements(env, jdata , jpszData, 0);
(*env)->ReleaseByteArrayElements(env, jout_crc , outData, 0);
return 0;
}
ModuleAPI.java
package com.xlzn.hcpda;
public class ModuleAPI {
private static ModuleAPI moduleAPI=new ModuleAPI();
private ModuleAPI(){}
public static ModuleAPI getInstance(){
return moduleAPI;
}
static {
System.loadLibrary("ModuleAPI");
}
public native int SerailOpen(String uart, int baudrate, int databits, int stopbits, int parity);
public native int SerailClose(int uart_fd);
public native int SerailSendData(int uart_fd,byte[] sendData,int sendLen);
public native int SerailReceive(int uart_fd,byte[] receiveData,int receiveDataLen);
public native int CalcCRC(byte[] data,int dataLen,byte[] outCrc);
public static int getVersionCode = BuildConfig.API_VERSION;
}
build.gradle设置相关内容:
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 31
versionCode 16
versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
externalNativeBuild {
cmake {
abiFilters "armeabi-v7a" //abi体系结构下的so库
}
}
}
signingConfigs {
debug {
File strFile = new File("/android.keystore")
storeFile file(strFile)
storePassword "123456"
keyPassword "123456"
keyAlias "keyAlias"
}
release {
File strFile = new File("/release.jks")
storeFile file(strFile)
storePassword "123456"
keyPassword "123456"
keyAlias "keyAlias"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
android.libraryVariants.all { variant ->
variant.outputs.all {
outputFileName = "HCUHF_${versionCodeA()}_${releaseTime()}.aar"
}
}
buildConfigField("int", "API_VERSION", "${releaseTime()}")
}
debug {
signingConfig signingConfigs.debug
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("int", "API_VERSION", "${releaseTime()}")
}
}
sourceSets {
main {
jni.srcDirs = ['src/main/jni']
jniLibs.srcDirs = ['src/main/jni/libs']
java.srcDirs = ['src/main/java']
}
}
externalNativeBuild {
cmake {
path file('src/main/jni/CMakeLists.txt')
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation files('libs\\classes.jar')
implementation files('libs\\jxl.jar')
implementation files('libs\\xUtils-2.5.5.jar')
implementation files('libs\\classes.jar')
}
static def releaseTime() {
new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08:00"))
}
static def versionName() {
"\"v1.0.7\""
}
static def versionCodeA() {
"v1.0.7"
}
CMakeLists.txt内容
cmake_minimum_required (VERSION 3.4.1)
project(ModuleAPI)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/inc")
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c"
)
add_library(
ModuleAPI
SHARED
${SRC_LIST}
)
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.
ModuleAPI
# Links the target library to the log library
# included in the NDK.
${log-lib} )
Android打aar没有包含so的解决办法
引言
在Android开发中,我们常常会将一些功能封装成库文件(aar)进行使用。然而有时候在打aar包时,可能会遇到一种情况:aar文件中没有包含.so文件。这时候我们需要告诉刚入行的小白如何解决这个问题。
解决方案概述
解决这个问题的基本思路是,在打aar包时将.so文件也打包进去。下面是整个解决方案的流程图:
开始
创建Android Library工程
在工程的main目录下创建jniLibs目录
将.so文件复制到jniLibs目录下
在工程的build.gradle文件中添加ndk配置
编译并打包aar
结束
下面我们将逐步解释每一步需要做什么。
步骤详解
1. 创建Android Library工程
首先,在Android Studio中创建一个新的Android Library工程。
2. 创建jniLibs目录
在工程的main目录下创建一个名为jniLibs的目录。这个目录将用于存放.so文件。
3. 复制.so文件
将需要打包的.so文件复制到jniLibs目录下。注意,如果有多个.so文件,都需要复制到该目录下。
4. 添加ndk配置
在工程的build.gradle文件中添加ndk的配置。具体的代码如下所示:
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
...
}
这段代码的作用是告诉Android Studio,要将jniLibs目录作为so库的来源。
5. 编译并打包aar
最后,编译并打包aar。可以通过在终端执行以下命令来实现:
./gradlew assembleRelease
这将在工程的build/outputs/aar目录下生成一个aar文件。
至此,我们完成了解决方案的所有步骤。
总结
本文介绍了解决Android打aar没有包含.so文件的问题的详细步骤。通过创建jniLibs目录,将.so文件复制到该目录下,并在build.gradle文件中添加ndk配置,最终成功编译并打包出包含.so文件的aar。希望本文对刚入行的小白有所帮助。
(以上为所需代码,请用“行内代码”的markdown语法标识出来)
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
...
}
./gradlew assembleRelease
Android 的aar和jar的区别有哪些,使用中需要注意哪些
在 Android 开发中,.aar 文件和 .jar 文件是两种常见的依赖库文件格式,它们在功能、结构和使用方式上存在一些区别。以下是它们的区别以及使用中需要注意的事项:
一、区别
1.文件结构
.jar 文件:
是 Java Archive(Java 归档文件)的缩写,主要用于打包 Java 类文件和资源文件。
它是一个简单的压缩文件,通常只包含 .class 文件(编译后的 Java 类)和一些资源文件(如配置文件、图片等)。它不支持 Android 特有的资源文件(如 .xml 布局文件、drawable 图形资源等)。
.aar 文件:
是 Android Archive(Android 归档文件)的缩写,是 Android 特有的库文件格式。
它是一个更复杂的压缩文件,除了包含 .class 文件和资源文件外,还支持 Android 的资源文件(如 .xml 布局文件、drawable 图形资源、values 资源文件等),并且可以包含 AndroidManifest.xml 文件。这使得 .aar 文件能够更好地支持 Android 的 UI 组件和资源管理。
2.功能支持
.jar 文件:
主要用于提供 Java 类库的支持,适合于那些不涉及 Android UI 资源的通用 Java 功能库(如工具类库、网络请求库等)。
.aar 文件:
除了提供 Java 类库支持外,还支持 Android 的 UI 组件和资源管理。例如,如果你开发了一个包含自定义 View 或布局的库,或者需要提供一些主题资源,那么 .aar 文件是更好的选择。
3.使用方式
.jar 文件:
在项目中使用 .jar 文件时,通常只需要将其添加到项目的 libs 目录下,然后在 build.gradle 文件中通过 implementation files('libs/your-library.jar') 来引入。
.aar 文件:
使用 .aar 文件时,同样将其放在 libs 目录下,然后在 build.gradle 文件中通过 implementation files('libs/your-library.aar') 来引入。不过,由于 .aar 文件支持资源文件,因此在编译时,Gradle 会自动将 .aar 文件中的资源文件合并到主项目的资源文件中。
4.大小和性能
.jar 文件:
通常体积较小,因为只包含必要的 .class 文件和少量资源文件。
.aar 文件:
由于支持更多的资源文件,体积可能会相对较大。不过,这并不一定意味着性能会受到影响,因为 .aar 文件的资源管理机制可以更好地优化资源的加载。
二、使用中需要注意的事项
1.依赖冲突
当项目中同时使用多个 .jar 或 .aar 文件时,可能会出现依赖冲突的情况。例如,两个库文件中可能包含相同名称的类或资源文件,这会导致编译错误或运行时问题。
解决方法:在 build.gradle 文件中,通过 exclude 关键字排除冲突的依赖。例如:
gradle
implementation('com.example:library1:1.0.0') {
exclude module: 'conflict-library'
}
另外,可以使用 dependencies 任务来查看项目的依赖树,分析冲突的来源。
2.资源冲突
对于 .aar 文件,由于它支持资源文件,可能会出现资源冲突的情况。例如,两个 .aar 文件中可能包含相同名称的资源文件(如 drawable/ic_logo.png)。
解决方法:在 build.gradle 文件中,通过 resConfigs 关键字指定要保留的资源。例如:
gradle
android {
defaultConfig {
resConfigs 'en', 'zh'
}
}
另外,可以通过重命名资源文件或调整资源的优先级来解决冲突。
3.版本管理
在使用 .jar 或 .aar 文件时,需要注意版本管理。不同版本的库文件可能包含不同的功能和修复,因此需要确保项目中使用的库文件版本是兼容的。
建议在 build.gradle 文件中使用固定的版本号,避免自动更新导致的兼容性问题。例如:
gradle
implementation 'com.example:library:1.0.0'
4.编译和打包
在使用 .jar 或 .aar 文件时,需要注意编译和打包的顺序。如果库文件中包含依赖关系,需要确保先编译依赖的库文件,再编译主项目。
在 build.gradle 文件中,可以通过 dependencies 块来指定依赖关系。例如:
gradle
dependencies {
implementation project(':library-module')
}
5.资源的访问
对于 .aar 文件中的资源,可以通过 R 类来访问。例如,如果 .aar 文件中包含一个名为 ic_logo.png 的图片资源,可以在代码中通过 R.drawable.ic_logo 来访问。
注意:在访问 .aar 文件中的资源时,需要确保资源的名称和类型是正确的,否则会导致编译错误或运行时问题。
6.ProGuard 和混淆
在使用 .jar 或 .aar 文件时,需要注意 ProGuard 和混淆的配置。如果库文件中包含需要混淆的代码,需要在 ProGuard 配置文件中指定相应的规则。
例如,如果库文件中包含一个名为 com.example.library 的包,可以在 ProGuard 配置文件中添加以下规则:
-keep class com.example.library.** { *; }
另外,对于 .aar 文件中的资源文件,ProGuard 不会进行混淆,因此不需要额外配置。
7.动态加载
在某些情况下,可能需要动态加载 .jar 或 .aar 文件。例如,通过 DexClassLoader 或 PathClassLoader 来加载动态库。
注意:动态加载库文件时,需要确保库文件的路径是正确的,并且库文件的版本与主项目兼容。另外,动态加载可能会导致性能问题和安全性问题,需要谨慎使用。
总结来说,.jar 文件和 .aar 文件在 Android 开发中各有用途,.jar 文件适合通用 Java 功能库,而 .aar 文件更适合包含 Android 资源的库。在使用时,需要注意依赖冲突、资源冲突、版本管理等问题,以确保项目的稳定性和兼容性。
Android 中,实现异步任务有哪些方法并给出例子
Android 中有多种实现异步任务的方法,每种方法都有其适用的场景和优缺点。以下是一些常见的异步任务实现方法及示例代码:
1. AsyncTask(已过时,但仍然可以了解其用法)
AsyncTask 是 Android 中用于处理简单异步操作的类。它允许在后台线程中执行耗时操作,并将结果发布到 UI 线程。
java
class MyAsyncTask extends AsyncTask<Void, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
// 在这里执行 UI 线程操作,例如显示进度条
}
@Override
protected String doInBackground(Void... params) {
// 在这里执行耗时操作,例如网络请求或文件操作
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); // 模拟耗时操作
publishProgress(i * 10); // 更新进度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "任务完成"; // 返回结果
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 在这里更新 UI,例如更新进度条
int progress = values[0];
Log.d("Progress", "当前进度: " + progress + "%");
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
// 在这里处理结果,例如更新 UI
Log.d("Result", result);
}
}
// 在 Activity 中执行
new MyAsyncTask().execute();
2. Handler
Handler 是 Android 中用于在不同线程之间传递消息的类。通过 Handler,可以在后台线程中执行任务,并将结果传递到主线程。
java
Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 在这里处理消息,例如更新 UI
if (msg.what == 1) {
String result = (String) msg.obj;
Log.d("Handler", "结果: " + result);
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 在这里执行耗时操作
try {
Thread.sleep(3000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将结果发送到主线程
Message message = mHandler.obtainMessage();
message.what = 1;
message.obj = "任务完成";
mHandler.sendMessage(message);
}
}).start();
3. Thread + Runnable
直接使用 Thread 和 Runnable 是最基本的方式。适用于简单的异步操作,但需要手动处理线程切换。
java
复制
new Thread(new Runnable() {
@Override
public void run() {
// 在这里执行耗时操作
try {
Thread.sleep(3000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在主线程中更新 UI
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d("Thread", "任务完成");
}
});
}
}).start();
4. RxJava
RxJava 是一种响应式编程库,非常适合处理复杂的异步操作和数据流。
java
复制
// 添加依赖
// implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
// implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
Observable.just("Hello, RxJava!")
.subscribeOn(Schedulers.io()) // 在后台线程执行
.observeOn(AndroidSchedulers.mainThread()) // 在主线程处理结果
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.d("RxJava", "订阅成功");
}
@Override
public void onNext(@NonNull String s) {
Log.d("RxJava", "结果: " + s);
}
@Override
public void onError(@NonNull Throwable e) {
Log.e("RxJava", "错误: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d("RxJava", "任务完成");
}
});
5. WorkManager
WorkManager 是 Android Jetpack 中的库,用于在后台执行异步任务,适合需要保证任务完成的场景(例如上传日志或同步数据)。
java
// 添加依赖
// implementation 'androidx.work:work-runtime-ktx:2.7.1'
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.setInputData(new Data.Builder().putString("KEY", "Hello, WorkManager!").build())
.build();
WorkManager.getInstance(getApplicationContext())
.enqueue(workRequest);
// 定义 Worker
public class MyWorker extends Worker {
public MyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
String input = getInputData().getString("KEY");
Log.d("WorkManager", "执行任务: " + input);
return Result.success(); // 返回成功状态
}
}
6. Kotlin 协程(推荐)
Kotlin 协程是现代 Android 开发中推荐的异步编程方式,语法简洁且易于管理。
kotlin
// 添加依赖
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
private val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
// 在主线程中启动协程
val result = withContext(Dispatchers.IO) {
// 在后台线程中执行耗时操作
delay(3000) // 模拟耗时操作
"任务完成"
}
// 返回主线程处理结果
Log.d("Coroutine", "结果: $result")
}
总结
简单场景:AsyncTask(已过时)、Thread + Runnable
复杂数据流:RxJava
后台保证任务:WorkManager
现代推荐:Kotlin 协程
在实际开发中,建议优先使用 Kotlin 协程 或 RxJava,因为它们更现代且功能强大。
Thread传参及更改UI
String parameter = "Hello, Thread!"; // 要传递的参数
new Thread(() -> {
try {
// 模拟耗时操作(例如网络请求)
Thread.sleep(3000);
// 在主线程中更新 UI
runOnUiThread(() -> {
textView.setText("任务完成!参数: " + parameter);
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
parameter 变量是隐式捕获的,因此可以直接在 Runnable 的实现代码中使用。这种方式简单直观,适合传递少量的参数
如果需要传递多个参数或者更复杂的逻辑,可以创建一个包含参数的 Runnable 类
String parameter = "Hello, Thread!"; // 要传递的参数
Thread thread = new Thread(new MyRunnable(parameter));
thread.start();
private static class MyRunnable implements Runnable {
private final String param;
public MyRunnable(String param) {
this.param = param;
}
@Override
public void run() {
try {
// 模拟耗时操作(例如网络请求)
Thread.sleep(3000);
// 在主线程中更新 UI
// 这里需要传入 MainActivity 的实例来调用 runOnUiThread
// 假设在 MainActivity 中定义了一个方法来更新 UI
// MainActivity.this.runOnUiThread(() -> {
// textView.setText("任务完成!参数: " + param);
// });
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
函数对外交互数据接口方式
使用interface
// 定义一个形状接口
interface Shape {
double getArea(); // 计算面积的抽象方法
double getPerimeter(); // 计算周长的抽象方法
}
// 实现圆形类
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
// 实现矩形类
class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
@Override
public double getPerimeter() {
return 2 * (width + height);
}
}
// 使用接口作为参数的函数
public class Main {
public static void printShapeInfo(Shape shape) {
System.out.println("面积: " + shape.getArea());
System.out.println("周长: " + shape.getPerimeter());
}
public static void main(String[] args) {
Shape circle = new Circle(5.0);
Shape rectangle = new Rectangle(4.0, 6.0);
System.out.println("圆形信息:");
printShapeInfo(circle);
System.out.println("\n矩形信息:");
printShapeInfo(rectangle);
}
}
Android原始支持例子:
private Button bt_sort ;
private MyAdapter myAdapter;
bt_sort = mainActivity.findViewById(R.id.bt_sort);
bt_sort.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Collections.sort(mainActivity.tagEntityList);
if (myAdapter != null) {
myAdapter.notifyDataSetChanged();
}
}
});
安卓中删除list中的某一项,并不是删除了一个view,而是删除了adapter中的数据源的list里面的一项,然后adapter.notifydatasetchanged()通知list去刷新界面,这时候就会删除某一项。
而RecycleView这个列表支持单独删除一项,并伴有动画,调用adapter.notifyRemove(position)即可。
这个setOnClickListener实现
View.java 中的源码实现
// 保存点击监听器的成员变量
private OnClickListener mOnClickListener;
/**
* 为View设置点击监听器
*
* @param l 点击回调接口
*/
public void setOnClickListener(@Nullable OnClickListener l) {
// 确保视图可点击
if (!isClickable()) {
setClickable(true);
}
// 将传入的监听器赋值给成员变量
mOnClickListener = l;
}
/**
* 内部定义的点击事件处理接口
*/
public interface OnClickListener {
void onClick(View v);
}
新版Android Studio火烈鸟 在新建项目工程时 无法选java的语言模板解决方法
最近下载最新版androidstudio时 发现不能勾选java语言模板了
如果快速点击下一步 新建项目 默认是kotlin语言模板 这可能和google主推kt语言有关
解决方法
勾选1
如图所示 如果勾选 No Activity 这个模板 是可以选java语言模板的
但是里面没有默认的Activity
勾选2
和以前的用法一样 可以勾选java语言模板
也有默认的mainActivity
现有项目里加多个应用项目
菜单:File->New->New Module->Phone & Tablet 或者引入现存的 File->New->Import Module...
模块里build.gradle
表示应用:
plugins {
id 'com.android.application'
}
表示库:
plugins {
id 'com.android.library'
}
Android中System.loadLibrary("ModuleAPI");实际加载的动态库文件名libModuleAPI.so
1. 文件名格式
完整的动态库文件名格式为:libModuleAPI.so
2. 文件所在位置
该文件一般存于应用的lib/目录或者 Android 系统的/system/lib目录(此目录针对系统库而言)。要是应用采用了 Android App Bundle(AAB)格式,动态库就会被存放在 APK 的lib/ABI/目录下。这里的 ABI 涵盖armeabi-v7a、arm64-v8a、x86、x86_64等。
3. 加载流程说明
当调用System.loadLibrary("ModuleAPI")时,Java 虚拟机(JVM)会按照下面的步骤来加载动态库:
先把逻辑名称ModuleAPI转换为对应的动态库文件名,也就是libModuleAPI.so。
接着在应用的私有库目录(即/data/data/包名/lib)中查找这个文件。
若在私有库目录中没找到,就会到系统库目录(像/system/lib或者/system/lib64)里继续查找。
4. 特别注意事项
对于 C/C++ 开发者而言,在编译动态库时,要保证生成的文件名是libModuleAPI.so。
若应用运用了 JNI(Java Native Interface),就需要在 Java 类里通过native关键字声明本地方法,并且在 C/C++ 代码中实现这些方法。
5. 示例代码
下面是一个 JNI 调用的简单示例:
java
public class NativeUtils {
// 加载动态库
static {
System.loadLibrary("ModuleAPI");
}
// 声明本地方法
public native String getNativeString();
}
在 C/C++ 代码中,需要按照 JNI 规范来实现getNativeString方法
Java声明的本地方法如何对应so里的函数
在 Java 中声明的本地方法与 SO 库中的函数对应关系是通过 JNI(Java Native Interface)规范实现的。下面详细解释它们之间的映射规则和实现方式:
1. 命名规则:JNI 函数签名
Java 本地方法与 SO 库中的函数通过特定的命名规则建立关联:
格式:Java_全类名_方法名
规则:
全类名:将 Java 类的包名中的.替换为_。
方法名:与 Java 中声明的本地方法名一致。
特殊字符处理:
若类名或方法名中包含_,需在 SO 库函数名中使用_1替代。
若包含.,需使用_0002e替代(ASCII 码转义)。
示例:
java
// Java类:com.example.NativeUtils
public class NativeUtils {
static {
System.loadLibrary("ModuleAPI");
}
// 声明本地方法
public native String getNativeString();
public native int calculateSum(int a, int b);
}
对应的 SO 库(C/C++)中的函数名应为:
c
运行
// JNI函数实现
JNIEXPORT jstring JNICALL
Java_com_example_NativeUtils_getNativeString(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello from native!");
}
JNIEXPORT jint JNICALL
Java_com_example_NativeUtils_calculateSum(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
2. JNIEnv 与 jobject 参数
所有 JNI 函数的前两个参数固定为:
JNIEnv*:指向 JNI 环境的指针,用于调用 JNI 函数(如创建 Java 对象、访问字段等)。
jobject:
若为实例方法,指向调用该方法的 Java 对象。
若为静态方法,指向该 Java 类的jclass对象。
3. 数据类型映射
Java 与本地代码(C/C++)之间的参数和返回值需要通过 JNI 类型转换:
Java 类型 JNI 类型(C/C++) 描述
int jint 32 位整数
long jlong 64 位整数
boolean jboolean 布尔值
String jstring Java 字符串
Object jobject Java 对象
Class jclass Java 类
数组 jarray 各类数组(如jintArray、jobjectArray)
4. 字符串处理示例
Java 的String与 C/C++ 的char*之间需要显式转换:
java
// Java方法
public native String processString(String input);
c
运行
// C/C++实现
JNIEXPORT jstring JNICALL
Java_com_example_NativeUtils_processString(JNIEnv *env, jobject obj, jstring input) {
// 1. 将Java字符串转换为C字符串
const char* c_str = (*env)->GetStringUTFChars(env, input, NULL);
if (c_str == NULL) {
return NULL; // 内存分配失败
}
// 2. 处理字符串(示例:添加前缀)
char result[100];
sprintf(result, "Processed: %s", c_str);
// 3. 释放C字符串并返回Java字符串
(*env)->ReleaseStringUTFChars(env, input, c_str);
return (*env)->NewStringUTF(env, result);
}
5. 注册方式:静态注册 vs 动态注册
静态注册(默认方式)
原理:根据函数名的命名规则自动映射。
缺点:函数名冗长,修改 Java 类名或包名时需同步修改 SO 库。
动态注册
原理:通过JNI_OnLoad函数手动注册 Java 方法与本地函数的映射关系。
优点:函数名可自定义,避免命名冲突。
动态注册示例:
c
运行
// 定义本地方法表
static JNINativeMethod methods[] = {
{"getNativeString", "()Ljava/lang/String;", (void*)getNativeString},
{"calculateSum", "(II)I", (void*)calculateSum}
};
// JNI_OnLoad函数:在SO库加载时自动调用
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// 获取Java类
jclass clazz = (*env)->FindClass(env, "com/example/NativeUtils");
if (clazz == NULL) {
return JNI_ERR;
}
// 注册方法
if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods)/sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
6. 总结
静态注册:通过命名规则自动映射,简单但不灵活。
动态注册:通过JNI_OnLoad手动注册,灵活但需额外编码。
JNIEnv:核心接口,提供 Java 与本地代码交互的函数。
类型转换:Java 与 C/C++ 类型需通过 JNI 类型中转。
通过以上规则,Java 声明的本地方法就能准确对应到 SO 库中的具体函数。
上一篇:Java学习笔记
下一篇:Android同行者