上链ON-CHAIN BLOCK 21 / 26
卷五 · Android 原生BLOCK 21

Kotlin + web3j


终于回到主场。前面所有链上概念,现在用你最顺手的语言重做一遍。web3j 是 JVM 上的以太坊库,在 Android 里它就是你的"链上 Retrofit"。这一章把读余额、发交易、监听事件三件事用 Kotlin + 协程写清楚,你会发现——除了目标从 REST 后端换成了区块链,写法几乎没变。

引入 web3j

Kotlin · build.gradle.kts
dependencies {
    // web3j 的 Android 精简发行版
    implementation("org.web3j:core:4.12.0-android")
}

建立连接就是给它一个 RPC 端点(第 7 章),得到一个 Web3j 实例——它就是你的 Provider(第 18 章):

Kotlin · 建立连接
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 的老规矩完全一致。

读链:查余额

Kotlin · 协程里查余额
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
    }
Android 类比

Convert.fromWei 就是帮你处理第 7、14 章那个 10¹⁸ 换算的工具——别自己用 Double 算金额,精度会崩。链上金额一律用 BigInteger/BigDecimal,和你在金融类 App 里绝不用浮点存钱是同一条纪律。

写链:发一笔交易

写操作需要一个能签名的凭证(第 18 章的 Signer)。web3j 里是 Credentials,由私钥构造。注意:私钥怎么安全保管是下一章的核心话题,这里先聚焦流程。

Kotlin · 发送 ETH 转账
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 包装类,把合约函数变成本地方法调用:

bash · 从 ABI 生成包装类
web3j generate solidity -a MyToken.abi -o ./src -p com.example.contracts
Kotlin · 像调本地对象一样调合约
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()
}
Android 类比

生成的包装类之于合约,正如 Retrofit 接口之于 REST API:你面对的是强类型的方法,而不是手拼 calldata。ABI(第 8 章)在这里扮演的角色,和 OpenAPI/Swagger 生成 Retrofit 接口时的 schema 一模一样。整个开发体验会让你有强烈的既视感。

监听事件:合约的数据流

web3j 用 RxJava 暴露事件流,你可以桥接成 Kotlin Flow,得到你熟悉的响应式体验(呼应第 12、20 章):

Kotlin · 事件转 Flow
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 ApiServiceweb3j + 合约包装类
RepositoryWeb3Repository(封装读写与事件)
ViewModel照旧,collect Flow / 调 suspend
Compose UI照旧
要点入账

web3j 让链上开发完全融入你的 Android 技能栈:Web3j 实例是 Provider,Credentials 是 Signer,ABI 生成的包装类是 Retrofit 接口,事件桥接成 Flow。所有 .send() 走 IO 协程,金额用 BigInteger。唯一还没解决的大问题是:那个 Credentials 里的私钥,在手机上到底该怎么存才安全?——这正是下一章的全部内容,也是移动端 Web3 最要命的一环。

本章小结

◆ 返回目录