您现在的位置是:网站首页> Android

Android中Java与C++交互、库开发与使用及研发积累

  • Android
  • 2025-07-09
  • 1018人已阅读
摘要

Android中Java与C++交互、库开发与使用及研发积累

Android可以用最简单的办法也就是类静态变量来实现Actvity之间的数据传递


Android JAVA调用异步函数并等待异步返回的例子

Android中System.loadLibrary("ModuleAPI");实际加载的动态库文件名libModuleAPI.so

Java声明的本地方法如何对应so里的函数

带C代码的Android项目NDK配置

手机连接等硬件问题汇集

Android快速回顾

Android Studio 国内镜像代理设置

简单理理AndroidStudio 生成SO并调用

AndroidStudio引用第三方so库的正确姿势

Android引用jar和so

Java数据类型对照C++类型

经验技术收集

函数对外交互数据接口方式

当出现莫名的编译错误时候可以清除项目再编译,Run->Clean Project

库开发使用

Android Studio引用本地jar包的方法

如何将android apk编译为arr包

Android引用aar包的两种方法

Android studio 导出,导入 aar包

Android 的aar和jar的区别有哪些,使用中需要注意哪些

常见问题

Android打aar没有包含so的解决办法

新版Android Studio火烈鸟 在新建项目工程时 无法选java的语言模板解决方法

现有项目里加多个应用项目

其他技术

JNI开发相关技术收集

设置Android Studio可编译过的环境

Android 开发实用技术收集

Android Studio项目问题解决办法收集

界面相关技术收集

实用源码下载

实用功能、开源软件、库的收集

Android版本和API版本的对应关系

Android 中,实现异步任务有哪些方法并给出例子

Thread传参及更改UI



带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

1.png

配置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并调用

工程结构如下

1.png

文件结构如下:

1.png


在调用的地方定义在 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文件,如下图:

1.png


2.png


 


2、AndroidStudio打开项目,并切换到 Android 栏,并打开Gradle Scripts\build.gradle(Module:app1.app) ,加入 节点


sourceSets{

    main{

        jniLibs.srcDirs "libs"

    }

}

如下图:

3.png 


 


3、加完之后,有一个刷新(同步)的操作 ,之后在app下就可以看到jniLibs文件夹,如下:

4.png


通过JNI定义实现调用第三方库so如上面的native-lib.cpp文件



Android引用jar和so

其实jni、jniLibs一个是C++源码提供一个是C++编译成so提供,libs、java一个是以Java编译的jar,一个是以Java代码提供

1.png

该工程源码:点击下载: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;




}


1.png

2.png


3.png

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} )


数据类型对照

1.png



Android打aar没有包含so的解决办法

引言

在Android开发中,我们常常会将一些功能封装成库文件(aar)进行使用。然而有时候在打aar包时,可能会遇到一种情况:aar文件中没有包含.so文件。这时候我们需要告诉刚入行的小白如何解决这个问题。

解决方案概述

解决这个问题的基本思路是,在打aar包时将.so文件也打包进去。下面是整个解决方案的流程图:

1.png

开始

创建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.png

勾选1

如图所示 如果勾选 No Activity 这个模板 是可以选java语言模板的

但是里面没有默认的Activity


勾选2

和以前的用法一样 可以勾选java语言模板

也有默认的mainActivity

2.png



现有项目里加多个应用项目

菜单:File->New->New Module->Phone & Tablet 或者引入现存的 File->New->Import Module...

1.png


2.png

模块里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同行者

Top