安卓端调用以太坊合约,从零搭建你的Web3移动应用
随着区块链技术的普及,越来越多的去中心化应用(DApp)需要在移动端落地,相比网页端使用MetaMask等浏览器插件钱包,安卓端直接与以太坊智能合约交互,可以让应用更轻量、体验更流畅,本文将手把手讲解如何使用 Web3j 库在安卓端完成以太坊智能合约的调用,涵盖环境配置、只读调用、交易发送、事件监听等核心内容。
技术选型:为什么选择 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中管理订阅的生命周期,避免内存泄漏。
常见坑与解决方案
-
NetworkOnMainThreadException:所有 Web3j 调用都放在协程Dispatchers.IO中。 -
交易长时间 Pending:Gas 价格设置过低,建议动态获取
ethGasPrice并适当上浮。 -
链 ID 不匹配导致签名无效:EIP-155 强制要求签名时带上正确的链 ID(主网为 1,Sepolia 为 11155111)。
-
方法 ID 编码错误:函数签名的 Keccak 哈希前 4 字节必须与链上合约完全一致,注意参数类型的规范化写法(如
uint256不能写成uint)。 -
APK 体积膨胀:启用 R8/ProGuard 混淆,排除未使用的 BouncyCastle 模块。
本文完整演示了安卓端调用以太坊合约的技术路径:
- Web3j 是安卓与以太坊交互的首选库
- 只读调用用
ethCall,免费且无需签名
发布于:2026-10-06,除非注明,否则均为原创文章,转载请注明出处。

