安卓端调用以太坊合约,从零搭建你的Web3移动应用

博主:neragonerago 2026-10-06 00:22:50 4

随着区块链技术的普及,越来越多的去中心化应用(DApp)需要在移动端落地,相比网页端使用MetaMask等浏览器插件钱包,安卓端直接与以太坊智能合约交互,可以让应用更轻量、体验更流畅,本文将手把手讲解如何使用 Web3j 库在安卓端完成以太坊智能合约的调用,涵盖环境配置、只读调用、交易发送、事件监听等核心内容。

安卓端调用以太坊合约,从零搭建你的Web3移动应用


技术选型:为什么选择 Web3j

在安卓生态中,与以太坊交互的主流方案有三种:

方案 特点
Web3j Java/Kotlin 原生库,轻量、文档完善,最适合安卓
集成 Geth/以太坊节点 重量级,需要同步全节点,不适合移动端
通过钱包 App 跳转(DeepLink/WalletConnect) 依赖第三方钱包,体验割裂

对于大多数场景,Web3j + 远程 RPC 节点是最佳实践:手机端不需要跑节点,只通过 JSON-RPC 与 Infura、Alchemy 等节点服务通信即可。


环境准备

添加依赖

在项目 build.gradle 中添加:

dependencies {
    // Web3j 核心库(安卓版)
    implementation 'org.web3j:core:4.10.3-android'
}

注意:务必使用 -android 后缀的版本,它针对安卓做了优化,去掉了不必要的 Java 依赖。

网络权限

在 AndroidManifest.xml 中声明:

<uses-permission android:name="android.permission.INTERNET" />

准备 RPC 节点地址

注册 Infura 或 Alchemy 账号,创建项目后获取类似下面的地址:

https://sepolia.infura.io/v3/你的项目ID

连接以太坊网络

val web3j = Web3j.build(HttpService("https://sepolia.infura.io/v3/你的项目ID"))
// 验证连接(必须放在子线程)
val clientVersion = web3j.web3ClientVersion().send()
Log.d("Web3", "连接成功: ${clientVersion.web3ClientVersion}")

⚠️ 重要提醒:网络请求严禁放在主线程,否则会触发 NetworkOnMainThreadException,建议使用 Kotlin 协程或 RxJava 管理。


加载钱包凭证

调用合约的写操作需要私钥签名,开发阶段可用测试私钥:

val credentials = Credentials.create("你的私钥")
Log.d("Web3", "钱包地址: ${credentials.address}")

生产环境安全建议:

  • 私钥绝不能硬编码或存储在明文 SharedPreferences 中
  • 推荐使用 Android Keystore System 加密存储
  • 或接入 WalletConnect,让用户使用自己的钱包签名

调用合约

假设有一个简单的存储合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleStorage {
    uint256 private value;
    event ValueChanged(uint256 newValue);
    function setValue(uint256 _value) public {
        value = _value;
        emit ValueChanged(_value);
    }
    function getValue() public view returns (uint256) {
        return value;
    }
}

只读调用

不消耗 Gas、不需要签名,直接本地或节点执行:

val contractAddress = "0x合约地址"
val function = Function(
    "getValue",
    listOf(),
    listOf(object : TypeReference<Uint256>() {})
)
val encodedFunction = FunctionEncoder.encode(function)
val response = web3j.ethCall(
    Transaction.createEthCallTransaction(null, contractAddress, encodedFunction),
    DefaultBlockParameterName.LATEST
).send()
val decoded = FunctionReturnDecoder.decode(
    response.value, 
    function.outputParameters
)
val value = (decoded[0] as Uint256).value
Log.d("Web3", "读取到的值: $value")

写操作(发送交易)

写操作需要签名并支付 Gas:

// 获取当前 Nonce
val nonce = web3j.ethGetTransactionCount(
    credentials.address, 
    DefaultBlockParameterName.PENDING
).send().transactionCount
// 编码函数
val function = Function(
    "setValue",
    listOf(Uint256(BigInteger.valueOf(100))),
    listOf()
)
val encodedFunction = FunctionEncoder.encode(function)
// 获取 Gas 价格(EIP-1559)
val gasPrice = web3j.ethGasPrice().send().gasPrice
// 构建原始交易
val rawTransaction = RawTransaction.createTransaction(
    nonce,
    gasPrice,
    BigInteger.valueOf(100000), // Gas Limit
    contractAddress,
    BigInteger.ZERO,            // 不转 ETH
    encodedFunction
)
// 签名并发送
val chainId = 11155111L // Sepolia 测试网
val signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials)
val hexValue = Numeric.toHexString(signedMessage)
val txHash = web3j.ethSendRawTransaction(hexValue).send()
Log.d("Web3", "交易哈希: ${txHash.transactionHash}")

进阶:使用 Web3j 自动生成的合约包装类

手动编码容易出错,Web3j 提供了代码生成工具:

# 安装命令行工具后执行
web3j generate solidity -a SimpleStorage.abi -b SimpleStorage.bin \
    -o app/src/main/java -p com.example.myapp.contracts

生成的包装类让调用像写普通方法一样简单:

val contract = SimpleStorage.load(
    contractAddress, web3j, credentials,
    StaticGasProvider(gasPrice, BigInteger.valueOf(100000))
)
// 只读调用
val value = contract.getValue().send()
// 写操作
val receipt = contract.setValue(BigInteger.valueOf(100)).send()

监听合约事件

通过 EthFilter 订阅合约事件:

val filter = EthFilter(DefaultBlockParameterName.LATEST, 
                       DefaultBlockParameterName.LATEST, 
                       contractAddress)
web3j.ethLogFlowable(filter).subscribe({ log ->
    // 解析事件日志
    Log.d("Web3", "捕获事件: ${log.data}")
}, { error ->
    error.printStackTrace()
})

安卓上建议在 ViewModel 中管理订阅的生命周期,避免内存泄漏。


常见坑与解决方案

  1. NetworkOnMainThreadException:所有 Web3j 调用都放在协程 Dispatchers.IO 中。

  2. 交易长时间 Pending:Gas 价格设置过低,建议动态获取 ethGasPrice 并适当上浮。

  3. 链 ID 不匹配导致签名无效:EIP-155 强制要求签名时带上正确的链 ID(主网为 1,Sepolia 为 11155111)。

  4. 方法 ID 编码错误:函数签名的 Keccak 哈希前 4 字节必须与链上合约完全一致,注意参数类型的规范化写法(如 uint256 不能写成 uint)。

  5. APK 体积膨胀:启用 R8/ProGuard 混淆,排除未使用的 BouncyCastle 模块。


本文完整演示了安卓端调用以太坊合约的技术路径:

  • Web3j 是安卓与以太坊交互的首选库
  • 只读调用用 ethCall,免费且无需签名

The End

发布于:2026-10-06,除非注明,否则均为区块链社区- 欧亿APP下载原创文章,转载请注明出处。