Kotlin + web3j
终于回到主场。前面所有链上概念,现在用你最顺手的语言重做一遍。web3j 是 JVM 上的以太坊库,在 Android 里它就是你的"链上 Retrofit"。这一章把读余额、发交易、监听事件三件事用 Kotlin + 协程写清楚,你会发现——除了目标从 REST 后端换成了区块链,写法几乎没变。
引入 web3j
dependencies {
// web3j 的 Android 精简发行版
implementation("org.web3j:core:4.12.0-android")
}
建立连接就是给它一个 RPC 端点(第 7 章),得到一个 Web3j 实例——它就是你的 Provider(第 18 章):
val web3 = Web3j.build(HttpService("https://your-sepolia-rpc")) // 查最新区块号(≈ 第7章的 eth_blockNumber) val blockNumber = web3.ethBlockNumber().send().blockNumber
web3j 的 .send() 是同步阻塞调用,绝不能在主线程跑,否则 ANR。全部包进 Dispatchers.IO 的协程里。下面的示例都用 withContext(Dispatchers.IO) 包裹——这跟你处理任何网络/IO 的老规矩完全一致。
读链:查余额
suspend fun getBalance(address: String): BigDecimal = withContext(Dispatchers.IO) { val wei = web3.ethGetBalance(address, DefaultBlockParameterName.LATEST) .send().balance // 返回 wei(第7章) Convert.fromWei(wei.toBigDecimal(), Convert.Unit.ETHER) // 换算成 ETH }
Convert.fromWei 就是帮你处理第 7、14 章那个 10¹⁸ 换算的工具——别自己用 Double 算金额,精度会崩。链上金额一律用 BigInteger/BigDecimal,和你在金融类 App 里绝不用浮点存钱是同一条纪律。
写链:发一笔交易
写操作需要一个能签名的凭证(第 18 章的 Signer)。web3j 里是 Credentials,由私钥构造。注意:私钥怎么安全保管是下一章的核心话题,这里先聚焦流程。
suspend fun sendEth(credentials: Credentials, to: String, amountEth: String): String = withContext(Dispatchers.IO) { // web3j 的便捷方法:自动取 nonce、估 gas、签名、广播 val receipt = Transfer.sendFunds( web3, credentials, to, BigDecimal(amountEth), Convert.Unit.ETHER ).send() receipt.transactionHash // 交易哈希(第2章) }
更底层地,你也能手动构造交易(设 nonce、gasLimit、maxFeePerGas —— 全是第 4 章那些字段),用 RawTransactionManager 签名后 ethSendRawTransaction 广播。便捷方法适合起步,精细控制用手动方式。
调合约:用生成的包装类
web3j 能读你的合约 ABI(第 8 章),用命令行工具或 Gradle 插件生成一个类型安全的 Kotlin/Java 包装类,把合约函数变成本地方法调用:
web3j generate solidity -a MyToken.abi -o ./src -p com.example.contracts
val token = MyToken.load(TOKEN_ADDRESS, web3, credentials, gasProvider) // 读(view,免费,第7章 call) val balance: BigInteger = withContext(Dispatchers.IO) { token.balanceOf(myAddress).send() } // 写(发交易,花 gas) val receipt = withContext(Dispatchers.IO) { token.transfer(toAddress, BigInteger("1000000000000000000")).send() }
生成的包装类之于合约,正如 Retrofit 接口之于 REST API:你面对的是强类型的方法,而不是手拼 calldata。ABI(第 8 章)在这里扮演的角色,和 OpenAPI/Swagger 生成 Retrofit 接口时的 schema 一模一样。整个开发体验会让你有强烈的既视感。
监听事件:合约的数据流
web3j 用 RxJava 暴露事件流,你可以桥接成 Kotlin Flow,得到你熟悉的响应式体验(呼应第 12、20 章):
fun transferEvents(token: MyToken): Flow<TransferEvent> = callbackFlow { val disposable = token .transferEventFlowable(DefaultBlockParameterName.LATEST, DefaultBlockParameterName.LATEST) .subscribe({ ev -> trySend(ev) }, { close(it) }) awaitClose { disposable.dispose() } } // 在 ViewModel 里 collect,像观察任何 Flow 一样刷新 UI
架构建议
把链交互封进一个 Repository,ViewModel 只 collect Flow / 调 suspend 方法,和你现有的 MVVM 架构无缝融合。链只是多了一个数据源:
| 你的 MVVM 层 | 链上版本 |
|---|---|
| Retrofit ApiService | web3j + 合约包装类 |
| Repository | Web3Repository(封装读写与事件) |
| ViewModel | 照旧,collect Flow / 调 suspend |
| Compose UI | 照旧 |
web3j 让链上开发完全融入你的 Android 技能栈:Web3j 实例是 Provider,Credentials 是 Signer,ABI 生成的包装类是 Retrofit 接口,事件桥接成 Flow。所有 .send() 走 IO 协程,金额用 BigInteger。唯一还没解决的大问题是:那个 Credentials 里的私钥,在手机上到底该怎么存才安全?——这正是下一章的全部内容,也是移动端 Web3 最要命的一环。
本章小结
- web3j(Android 发行版)是 JVM 上的以太坊库;Web3j=Provider,Credentials=Signer。
- 所有 .send() 是阻塞调用,必须在 Dispatchers.IO 协程里;金额用 BigInteger/BigDecimal + Convert 换算。
- 从 ABI 生成类型安全包装类,像调 Retrofit 接口一样调合约;view 免费、写操作花 gas。
- 事件可桥接成 Flow,融入 MVVM;链只是 Repository 里多出来的一个数据源。