关键词:以太坊安卓钱包、私钥导入、KeyStore、助记词、greenDAO、ETHWallet、账号管理、ReactiveX、钱包开发
上一篇我们讲解了如何在安卓中从零开始创建以太坊助记词钱包,本篇将从 三种主流导入方式(私钥、KeyStore、助记词)与 持久化存储+多账号切换 两个维度,给出可直接落地的代码级演示。
思维导图:用户为什么需要“导入模式”?
- 换机或重装 App 时需还原资产
- 探索第三方工具后想转回自研钱包
- 为了隔断旧钱包潜在风险,先把所有币转移到“新生成账号”,再把旧账号导入仅做查看——既能验证资产,又降低被盗概率
因此 UI 层应在显眼位置 二次提示用户备份:“若老钱包私钥可能已泄露,请先建新钱包+转账,再导入旧钱包。”
三种账号导入方案
1. 私钥导入——最直接的密钥复原
核心:将 64 位十六进制私钥转为 ECKeyPair,再调用既定 generateWallet()。
public static ETHWallet loadWalletByPrivateKey(String privateKey, String pwd) {
ECKeyPair ecKeyPair = ECKeyPair.create(Numeric.toBigInt(privateKey));
return generateWallet(generateNewWalletName(), pwd, ecKeyPair);
}⚠️ 注意:这里的 pwd 仅用于本地 KeyStore 文件加解密,链上交易并不使用它。FAQ
Q:私钥带回车、0x 前缀能否兼容?
A:可用 Numeric.cleanHexPrefix() 清掉前缀,trim() 掉空白即可。
Q:如何防范粘贴板嗅探?
A:为私钥输入框启用 android:inputType="textVisiblePassword" + android:importantForAutofill="no",必要时配合 自定义键盘 或 清剪贴板。
2. KeyStore 文件导入——兼顾安全与易用
- 把 json 字符串反序列化为
WalletFile - 用本地密码解密得到
ECKeyPair
public static ETHWallet loadWalletByKeystore(String keystore, String pwd) {
WalletFile walletFile = objectMapper.readValue(keystore, WalletFile.class);
return generateWallet(generateNewWalletName(), pwd,
Wallet.decrypt(pwd, walletFile));
}提示 UI 提供 文件选择器 与 文本粘贴 双通道,提升用户体验。
3. 助记词导入——BIP44 派生自由自在
- 输入:12/15/18/24 个助记词 + 路径(如
m/44'/60'/0'/0/) - 过程:生成种子 → 根据路径派生私钥 → 生成地址
DeterministicSeed ds = new DeterministicSeed(
Arrays.asList(mnemonic.split(" ")),
null, "", System.currentTimeMillis()/1000);
return generateWalletByMnemonic(name, ds, pathArray, pwd);与创建新助记词钱包最大的区别:随机熵不由系统产生,而是完全来自用户输入。记得校验路径长度及前缀防止非法参数。
持久化:把 ETHWallet 写进 SQLite 而不是内存
无论哪种方式,最终都会得到一个 ETHWallet 对象,它包含地址、名称、加密密码、keystore 路径、助记词、是否选中等多种元信息。为了让下一次进 App 仍能读取,必须落库。
1) greenDAO 实体映射
@Entity
public class ETHWallet {
@Id(autoincrement = true)
private Long id;
public String address;
private String name;
private String password; // 加密后的 pwd
private String keystorePath;
private String mnemonic;
private boolean isCurrent;
private boolean isBackup;
}2) 初始化数据库(建议放在 Application.onCreate)
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "wallet.db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoSession daoSession = new DaoMaster(db).newSession();
ETHWalletDao ethWalletDao = daoSession.getETHWalletDao();3) 一行插入
ethWalletDao.insert(ethWallet);多账号切换:一个简单读改写流程
用户可能持有多个账号,为了在交易、查看余额时定位当前活跃账号,我们使用布尔字段 isCurrent 标识。
// 新钱包入库前把其余账号设为未选中
public static void insertNewWallet(ETHWallet ethWallet) {
updateCurrent(-1); // 取消旧选中
ethWallet.setCurrent(true);
ethWalletDao.insert(ethWallet);
}
// 切换选中
public static ETHWallet updateCurrent(long id) {
List<ETHWallet> all = ethWalletDao.loadAll();
ETHWallet active = null;
for (ETHWallet w : all) {
w.setCurrent(w.getId() == id);
ethWalletDao.update(w);
if (w.getId() == id) active = w;
}
return active;
}小技巧
- 数据库触发器/事件总线可在状态变化时刷新 UI
- 对 WalletDaoUtils 统一加
@Dao或 @Singleton 便于单元测试
FAQ
Q:多线程并发切换会不会出现双 “current”?
A:通过事务或 SQL 更新语句 UPDATE ethwallet SET isCurrent = (id = ?) 方言可瞬间一次性修改,天然避免竞态。
Q:需要缓存到内存吗?
A:建议把 currentWallet 置于全局单例,并监听数据库变化。首次启动从磁盘读取后常驻内存,减少 I/O。
打通“创建/导入 → 落库”链路:ReactiveX 示例
为何不用 AsyncTask?耗时加解密、磁盘 I/O 若放在主线程会阻塞 UI,引发 ANR。 ReactiveX 清晰分工:
public Single<ETHWallet> loadWalletByPrivateKey(final String pk, final String pwd) {
return Single.fromCallable(() -> {
ETHWallet wallet = ETHWalletUtils.loadWalletByPrivateKey(pk, pwd);
if (wallet != null) WalletDaoUtils.insertNewWallet(wallet);
return wallet;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}subscribeOn(Schedulers.io()):解密、插入数据库统统在子线程observeOn(AndroidSchedulers.mainThread()):最终拿到结果即可直接刷新 UI
👉 一键查看如何在 10 行代码内优雅切换到 RxJava3
小结与展望
通过本文,你已掌握:
- 三种安全导入(私钥、KeyStore、助记词)的 底线做法与易错点
- greenDAO 对象持久化最佳实践
- 多账号的 增删改查+切换 全流程
- ReactiveX 在 钱包开发中的线程解放技巧
下一篇我们会把目光集中到 资产显示与价格更新,手把手封装一个可读可扩展的 Token 列表!
若有疑问,欢迎在评论区或 小专栏区块核心 提问,我们下一篇见。