包walltemplate中,
Maincontroller这个是建立主界面。
SendMoneyController是发送币给别人的界面。
和钱打交道有意思。
看看代码:
public void initialize() {
Coin balance = Main.bitcoin.wallet().getBalance();
checkState(!balance.isZero());
new BitcoinAddressValidator(Main.params, address, sendBtn);
new TextFieldValidator(amountEdit, text ->
!WTUtils.didThrow(() -> checkState(Coin.parseCoin(text).compareTo(balance) <= 0)));
amountEdit.setText(balance.toPlainString());
}
其中。
Coin balance = Main.bitcoin.wallet().getBalance();
是获取当前自己余额。
进去看看咋获取的。
public Coin getBalance() {
return getBalance(BalanceType.AVAILABLE);
}
然后
public Coin getBalance(BalanceType balanceType) {
lock.lock();
try {
if (balanceType == BalanceType.AVAILABLE || balanceType == BalanceType.AVAILABLE_SPENDABLE) {
List<TransactionOutput> candidates = calculateAllSpendCandidates(true, balanceType == BalanceType.AVAILABLE_SPENDABLE);
CoinSelection selection = coinSelector.select(NetworkParameters.MAX_MONEY, candidates);
return selection.valueGathered;
} else if (balanceType == BalanceType.ESTIMATED || balanceType == BalanceType.ESTIMATED_SPENDABLE) {
List<TransactionOutput> all = calculateAllSpendCandidates(false, balanceType == BalanceType.ESTIMATED_SPENDABLE);
Coin value = Coin.ZERO;
for (TransactionOutput out : all) value = value.add(out.getValue());
return value;
} else {
throw new AssertionError("Unknown balance type"); // Unreachable.
}
} finally {
lock.unlock();
}
}
关键是:
List<TransactionOutput> candidates = calculateAllSpendCandidates(true, balanceType == BalanceType.AVAILABLE_SPENDABLE);
CoinSelection selection = coinSelector.select(NetworkParameters.MAX_MONEY, candidates);
return selection.valueGathered;
然后
public List<TransactionOutput> calculateAllSpendCandidates(boolean excludeImmatureCoinbases, boolean excludeUnsignable) {
lock.lock(); //先锁住
try {
List<TransactionOutput> candidates;
if (vUTXOProvider == null) {
candidates = new ArrayList<>(myUnspents.size());
for (TransactionOutput output : myUnspents) {
if (excludeUnsignable && !canSignFor(output.getScriptPubKey())) continue;
Transaction transaction = checkNotNull(output.getParentTransaction());
if (excludeImmatureCoinbases && !transaction.isMature())
continue;
candidates.add(output);
}
} else {
candidates = calculateAllSpendCandidatesFromUTXOProvider(excludeImmatureCoinbases);
}
return candidates;
} finally {
lock.unlock();
}
}
其中vUTXOProvider是这个对象。
// If this is set then the wallet selects spendable candidate outputs from a UTXO provider.
@Nullable private volatile UTXOProvider vUTXOProvider;
myUnspents是个hashSet
// All the TransactionOutput objects that we could spend (ignoring whether we have the private key or not).
// Used to speed up various calculations.
protected final HashSet<TransactionOutput> myUnspents = Sets.newHashSet();
然后校验了之后输出一个List<TransactionOutput>的东东,用来控制交易
接着
CoinSelection selection = coinSelector.select(NetworkParameters.MAX_MONEY, candidates);
这个是关键。
protected CoinSelector coinSelector = new DefaultCoinSelector();
然后
@Override
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
ArrayList<TransactionOutput> selected = new ArrayList<>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
// TODO: Consider changing the wallets internal format to track just outputs and keep them ordered.
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
// them in order to improve performance.
// TODO: Take in network parameters when instanatiated, and then test against the current network. Or just have a boolean parameter for "give me everything"
if (!target.equals(NetworkParameters.MAX_MONEY)) {
sortOutputs(sortedOutputs);
}
// Now iterate over the sorted outputs until we have got as close to the target as possible or a little
// bit over (excessive value will be change).
long total = 0;
for (TransactionOutput output : sortedOutputs) {
if (total >= target.value) break;
// Only pick chain-included transactions, or transactions that are ours and pending.
if (!shouldSelect(output.getParentTransaction())) continue;
selected.add(output);
total += output.getValue().value;
}
// Total may be lower than target here, if the given candidates were insufficient to create to requested
// transaction.
return new CoinSelection(Coin.valueOf(total), selected);
}
意思是在其他地方往myUnspents里面增加了TransactionOutput,在这里只是读出其中的值。
然后看看send干了啥。
public void send(ActionEvent event) {
// Address exception cannot happen as we validated it beforehand.
try {
Coin amount = Coin.parseCoin(amountEdit.getText());
Address destination = Address.fromBase58(Main.params, address.getText());
SendRequest req;
if (amount.equals(Main.bitcoin.wallet().getBalance()))
req = SendRequest.emptyWallet(destination);
else
req = SendRequest.to(destination, amount);
req.aesKey = aesKey;
sendResult = Main.bitcoin.wallet().sendCoins(req);
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction result) {
checkGuiThread();
overlayUI.done();
}
@Override
public void onFailure(Throwable t) {
// We died trying to empty the wallet.
crashAlert(t);
}
});
sendResult.tx.getConfidence().addEventListener((tx, reason) -> {
if (reason == TransactionConfidence.Listener.ChangeReason.SEEN_PEERS)
updateTitleForBroadcast();
});
sendBtn.setDisable(true);
address.setDisable(true);
((HBox)amountEdit.getParent()).getChildren().remove(amountEdit);
((HBox)btcLabel.getParent()).getChildren().remove(btcLabel);
updateTitleForBroadcast();
} catch (InsufficientMoneyException e) {
informationalAlert("Could not empty the wallet",
"You may have too little money left in the wallet to make a transaction.");
overlayUI.done();
} catch (ECKey.KeyIsEncryptedException e) {
askForPasswordAndRetry();
}
}
关键在于
sendResult = Main.bitcoin.wallet().sendCoins(req);
追踪进去
/**
* Satisfies the given {@link SendRequest} using the default transaction broadcaster configured either via
* {@link PeerGroup#addWallet(Wallet)} or directly with {@link #setTransactionBroadcaster(TransactionBroadcaster)}.
* @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself.
* @return An object containing the transaction that was created, and a future for the broadcast of it.
*/
public SendResult sendCoins(SendRequest request) throws InsufficientMoneyException {
TransactionBroadcaster broadcaster = vTransactionBroadcaster;
checkState(broadcaster != null, "No transaction broadcaster is configured");
return sendCoins(broadcaster, request);
}
上面的注释删了异常部分。
意思是用默认交易传播器或者直接用指定的交易传播器。
再追踪。
/**
* <p>Sends coins according to the given request, via the given {@link TransactionBroadcaster}.</p>
*
* <p>The returned object provides both the transaction, and a future that can be used to learn when the broadcast
* is complete. Complete means, if the PeerGroup is limited to only one connection, when it was written out to
* the socket. Otherwise when the transaction is written out and we heard it back from a different peer.</p>
*
* <p>Note that the sending transaction is committed to the wallet immediately, not when the transaction is
* successfully broadcast. This means that even if the network hasn't heard about your transaction you won't be
* able to spend those same coins again.</p>
*
* @param broadcaster the target to use for broadcast.
* @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself.
* @return An object containing the transaction that was created, and a future for the broadcast of it.
* @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
* @throws IllegalArgumentException if you try and complete the same SendRequest twice
* @throws DustySendRequested if the resultant transaction would violate the dust rules.
* @throws CouldNotAdjustDownwards if emptying the wallet was requested and the output can't be shrunk for fees without violating a protocol rule.
* @throws ExceededMaxTransactionSize if the resultant transaction is too big for Bitcoin to process.
* @throws MultipleOpReturnRequested if there is more than one OP_RETURN output for the resultant transaction.
*/
public SendResult sendCoins(TransactionBroadcaster broadcaster, SendRequest request) throws InsufficientMoneyException {
// Should not be locked here, as we're going to call into the broadcaster and that might want to hold its
// own lock. sendCoinsOffline handles everything that needs to be locked.
checkState(!lock.isHeldByCurrentThread());
// Commit the TX to the wallet immediately so the spent coins won't be reused.
// TODO: We should probably allow the request to specify tx commit only after the network has accepted it.
Transaction tx = sendCoinsOffline(request);
SendResult result = new SendResult();
result.tx = tx;
// The tx has been committed to the pending pool by this point (via sendCoinsOffline -> commitTx), so it has
// a txConfidenceListener registered. Once the tx is broadcast the peers will update the memory pool with the
// count of seen peers, the memory pool will update the transaction confidence object, that will invoke the
// txConfidenceListener which will in turn invoke the wallets event listener onTransactionConfidenceChanged
// method.
result.broadcast = broadcaster.broadcastTransaction(tx);
result.broadcastComplete = result.broadcast.future();
return result;
}
这个Transaction是一个继承了一堆类的消息类
然后看看sendCoinsOffline这个。
/**
* Sends coins to the given address but does not broadcast the resulting pending transaction. It is still stored
* in the wallet, so when the wallet is added to a {@link PeerGroup} or {@link Peer} the transaction will be
* announced to the network. The given {@link SendRequest} is completed first using
* {@link Wallet#completeTx(SendRequest)} to make it valid.
*
* @return the Transaction that was created
* @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
* @throws IllegalArgumentException if you try and complete the same SendRequest twice
* @throws DustySendRequested if the resultant transaction would violate the dust rules.
* @throws CouldNotAdjustDownwards if emptying the wallet was requested and the output can't be shrunk for fees without violating a protocol rule.
* @throws ExceededMaxTransactionSize if the resultant transaction is too big for Bitcoin to process.
* @throws MultipleOpReturnRequested if there is more than one OP_RETURN output for the resultant transaction.
*/
public Transaction sendCoinsOffline(SendRequest request) throws InsufficientMoneyException {
lock.lock();
try {
completeTx(request);
commitTx(request.tx);
return request.tx;
} finally {
lock.unlock();
}
}
然后是
public void completeTx(SendRequest req) throws InsufficientMoneyException {
lock.lock();
try {
checkArgument(!req.completed, "Given SendRequest has already been completed.");
// Calculate the amount of value we need to import.
Coin value = Coin.ZERO;
for (TransactionOutput output : req.tx.getOutputs()) {
value = value.add(output.getValue());
}
log.info("Completing send tx with {} outputs totalling {} and a fee of {}/kB", req.tx.getOutputs().size(),
value.toFriendlyString(), req.feePerKb.toFriendlyString());
// If any inputs have already been added, we don't need to get their value from wallet
Coin totalInput = Coin.ZERO;
for (TransactionInput input : req.tx.getInputs())
if (input.getConnectedOutput() != null)
totalInput = totalInput.add(input.getConnectedOutput().getValue());
else
log.warn("SendRequest transaction already has inputs but we don't know how much they are worth - they will be added to fee.");
value = value.subtract(totalInput);
List<TransactionInput> originalInputs = new ArrayList<>(req.tx.getInputs());
// Check for dusty sends and the OP_RETURN limit.
if (req.ensureMinRequiredFee && !req.emptyWallet) { // Min fee checking is handled later for emptyWallet.
int opReturnCount = 0;
for (TransactionOutput output : req.tx.getOutputs()) {
if (output.isDust())
throw new DustySendRequested();
if (output.getScriptPubKey().isOpReturn())
++opReturnCount;
}
if (opReturnCount > 1) // Only 1 OP_RETURN per transaction allowed.
throw new MultipleOpReturnRequested();
}
// Calculate a list of ALL potential candidates for spending and then ask a coin selector to provide us
// with the actual outputs that'll be used to gather the required amount of value. In this way, users
// can customize coin selection policies. The call below will ignore immature coinbases and outputs
// we don't have the keys for.
List<TransactionOutput> candidates = calculateAllSpendCandidates(true, req.missingSigsMode == MissingSigsMode.THROW);
CoinSelection bestCoinSelection;
TransactionOutput bestChangeOutput = null;
List<Coin> updatedOutputValues = null;
if (!req.emptyWallet) {
// This can throw InsufficientMoneyException.
FeeCalculation feeCalculation = calculateFee(req, value, originalInputs, req.ensureMinRequiredFee, candidates);
bestCoinSelection = feeCalculation.bestCoinSelection;
bestChangeOutput = feeCalculation.bestChangeOutput;
updatedOutputValues = feeCalculation.updatedOutputValues;
} else {
// We're being asked to empty the wallet. What this means is ensuring "tx" has only a single output
// of the total value we can currently spend as determined by the selector, and then subtracting the fee.
checkState(req.tx.getOutputs().size() == 1, "Empty wallet TX must have a single output only.");
CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
bestCoinSelection = selector.select(params.getMaxMoney(), candidates);
candidates = null; // Selector took ownership and might have changed candidates. Don't access again.
req.tx.getOutput(0).setValue(bestCoinSelection.valueGathered);
log.info(" emptying {}", bestCoinSelection.valueGathered.toFriendlyString());
}
for (TransactionOutput output : bestCoinSelection.gathered)
req.tx.addInput(output);
if (req.emptyWallet) {
final Coin feePerKb = req.feePerKb == null ? Coin.ZERO : req.feePerKb;
if (!adjustOutputDownwardsForFee(req.tx, bestCoinSelection, feePerKb, req.ensureMinRequiredFee))
throw new CouldNotAdjustDownwards();
}
if (updatedOutputValues != null) {
for (int i = 0; i < updatedOutputValues.size(); i++) {
req.tx.getOutput(i).setValue(updatedOutputValues.get(i));
}
}
if (bestChangeOutput != null) {
req.tx.addOutput(bestChangeOutput);
log.info(" with {} change", bestChangeOutput.getValue().toFriendlyString());
}
// Now shuffle the outputs to obfuscate which is the change.
if (req.shuffleOutputs)
req.tx.shuffleOutputs();
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
if (req.signInputs)
signTransaction(req);
// Check size.
final int size = req.tx.unsafeBitcoinSerialize().length;
if (size > Transaction.MAX_STANDARD_TX_SIZE)
throw new ExceededMaxTransactionSize();
// Label the transaction as being self created. We can use this later to spend its change output even before
// the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much
// point - the user isn't interested in a confidence transition they made themselves.
req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
// Label the transaction as being a user requested payment. This can be used to render GUI wallet
// transaction lists more appropriately, especially when the wallet starts to generate transactions itself
// for internal purposes.
req.tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
// Record the exchange rate that was valid when the transaction was completed.
req.tx.setExchangeRate(req.exchangeRate);
req.tx.setMemo(req.memo);
req.completed = true;
log.info(" completed: {}", req.tx);
} finally {
lock.unlock();
}
}
这里我们很清楚的看见,交换的是一个一个的TransactionInput
然后扣减费用,甚至要扣减交易费。
代码里大量的使用了线程锁。
扣减之后,就要签名:
/**
* <p>Given a send request containing transaction, attempts to sign it's inputs. This method expects transaction
* to have all necessary inputs connected or they will be ignored.</p>
* <p>Actual signing is done by pluggable {@link #signers} and it's not guaranteed that
* transaction will be complete in the end.</p>
*/
public void signTransaction(SendRequest req) {
lock.lock();
try {
Transaction tx = req.tx;
List<TransactionInput> inputs = tx.getInputs();
List<TransactionOutput> outputs = tx.getOutputs();
checkState(inputs.size() > 0);
checkState(outputs.size() > 0);
KeyBag maybeDecryptingKeyBag = new DecryptingKeyBag(this, req.aesKey);
int numInputs = tx.getInputs().size();
for (int i = 0; i < numInputs; i++) {
TransactionInput txIn = tx.getInput(i);
if (txIn.getConnectedOutput() == null) {
// Missing connected output, assuming already signed.
continue;
}
try {
// We assume if its already signed, its hopefully got a SIGHASH type that will not invalidate when
// we sign missing pieces (to check this would require either assuming any signatures are signing
// standard output types or a way to get processed signatures out of script execution)
txIn.getScriptSig().correctlySpends(tx, i, txIn.getConnectedOutput().getScriptPubKey());
log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", i);
continue;
} catch (ScriptException e) {
log.debug("Input contained an incorrect signature", e);
// Expected.
}
Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey();
RedeemData redeemData = txIn.getConnectedRedeemData(maybeDecryptingKeyBag);
checkNotNull(redeemData, "Transaction exists in wallet that we cannot redeem: %s", txIn.getOutpoint().getHash());
txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript));
}
TransactionSigner.ProposedTransaction proposal = new TransactionSigner.ProposedTransaction(tx);
for (TransactionSigner signer : signers) {
if (!signer.signInputs(proposal, maybeDecryptingKeyBag))
log.info("{} returned false for the tx", signer.getClass().getName());
}
// resolve missing sigs if any
new MissingSigResolutionSigner(req.missingSigsMode).signInputs(proposal, maybeDecryptingKeyBag);
} finally {
lock.unlock();
}
}
其中
Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey();
是获取一个加密的字符串
RedeemData redeemData = txIn.getConnectedRedeemData(maybeDecryptingKeyBag);
是获取一个解密的ecKey
关键是这里
for (TransactionSigner signer : signers) {
if (!signer.signInputs(proposal, maybeDecryptingKeyBag))
log.info("{} returned false for the tx", signer.getClass().getName());
}
// resolve missing sigs if any
new MissingSigResolutionSigner(req.missingSigsMode).signInputs(proposal, maybeDecryptingKeyBag);
由于大量使用了共享变量,而且多态的使用,使得代码可读性很差
签完名了。执行这个
/**
* <p>Updates the wallet with the given transaction: puts it into the pending pool, sets the spent flags and runs
* the onCoinsSent/onCoinsReceived event listener. Used in two situations:</p>
*
* <ol>
* <li>When we have just successfully transmitted the tx we created to the network.</li>
* <li>When we receive a pending transaction that didn't appear in the chain yet, and we did not create it.</li>
* </ol>
*
* <p>Triggers an auto save.</p>
*/
public void commitTx(Transaction tx) throws VerificationException {
checkArgument(maybeCommitTx(tx), "commitTx called on the same transaction twice");
}
然后是这个:
public boolean maybeCommitTx(Transaction tx) throws VerificationException {
tx.verify();
lock.lock();
try {
if (pending.containsKey(tx.getHash()))
return false;
log.info("commitTx of {}", tx.getHashAsString());
Coin balance = getBalance();
tx.setUpdateTime(Utils.now());
// Put any outputs that are sending money back to us into the unspents map, and calculate their total value.
Coin valueSentToMe = Coin.ZERO;
for (TransactionOutput o : tx.getOutputs()) {
if (!o.isMineOrWatched(this)) continue;
valueSentToMe = valueSentToMe.add(o.getValue());
}
// Mark the outputs we're spending as spent so we won't try and use them in future creations. This will also
// move any transactions that are now fully spent to the spent map so we can skip them when creating future
// spends.
updateForSpends(tx, false);
Set<Transaction> doubleSpendPendingTxns = findDoubleSpendsAgainst(tx, pending);
Set<Transaction> doubleSpendUnspentTxns = findDoubleSpendsAgainst(tx, unspent);
Set<Transaction> doubleSpendSpentTxns = findDoubleSpendsAgainst(tx, spent);
if (!doubleSpendUnspentTxns.isEmpty() ||
!doubleSpendSpentTxns.isEmpty() ||
!isNotSpendingTxnsInConfidenceType(tx, ConfidenceType.DEAD)) {
// tx is a double spend against a tx already in the best chain or spends outputs of a DEAD tx.
// Add tx to the dead pool and schedule confidence listener notifications.
log.info("->dead: {}", tx.getHashAsString());
tx.getConfidence().setConfidenceType(ConfidenceType.DEAD);
confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.TYPE);
addWalletTransaction(Pool.DEAD, tx);
} else if (!doubleSpendPendingTxns.isEmpty() ||
!isNotSpendingTxnsInConfidenceType(tx, ConfidenceType.IN_CONFLICT)) {
// tx is a double spend against a pending tx or spends outputs of a tx already IN_CONFLICT.
// Add tx to the pending pool. Update the confidence type of tx, the txns in conflict with tx and all
// their dependencies to IN_CONFLICT and schedule confidence listener notifications.
log.info("->pending (IN_CONFLICT): {}", tx.getHashAsString());
addWalletTransaction(Pool.PENDING, tx);
doubleSpendPendingTxns.add(tx);
addTransactionsDependingOn(doubleSpendPendingTxns, getTransactions(true));
for (Transaction doubleSpendTx : doubleSpendPendingTxns) {
doubleSpendTx.getConfidence().setConfidenceType(ConfidenceType.IN_CONFLICT);
confidenceChanged.put(doubleSpendTx, TransactionConfidence.Listener.ChangeReason.TYPE);
}
} else {
// No conflict detected.
// Add to the pending pool and schedule confidence listener notifications.
log.info("->pending: {}", tx.getHashAsString());
tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.TYPE);
addWalletTransaction(Pool.PENDING, tx);
}
if (log.isInfoEnabled())
log.info("Estimated balance is now: {}", getBalance(BalanceType.ESTIMATED).toFriendlyString());
// Mark any keys used in the outputs as "used", this allows wallet UI's to auto-advance the current key
// they are showing to the user in qr codes etc.
markKeysAsUsed(tx);
try {
Coin valueSentFromMe = tx.getValueSentFromMe(this);
Coin newBalance = balance.add(valueSentToMe).subtract(valueSentFromMe);
if (valueSentToMe.signum() > 0) {
checkBalanceFuturesLocked(null);
queueOnCoinsReceived(tx, balance, newBalance);
}
if (valueSentFromMe.signum() > 0)
queueOnCoinsSent(tx, balance, newBalance);
maybeQueueOnWalletChanged();
} catch (ScriptException e) {
// Cannot happen as we just created this transaction ourselves.
throw new RuntimeException(e);
}
isConsistentOrThrow();
informConfidenceListenersIfNotReorganizing();
saveNow();
} finally {
lock.unlock();
}
return true;
}
关键1:
// Mark the outputs we're spending as spent so we won't try and use them in future creations. This will also
// move any transactions that are now fully spent to the spent map so we can skip them when creating future
// spends.
updateForSpends(tx, false);
标记transaction已经使用过了
Set<Transaction> doubleSpendPendingTxns = findDoubleSpendsAgainst(tx, pending);
Set<Transaction> doubleSpendUnspentTxns = findDoubleSpendsAgainst(tx, unspent);
Set<Transaction> doubleSpendSpentTxns = findDoubleSpendsAgainst(tx, spent);
检查是否使用过2次
然后
markKeysAsUsed(tx);
把交易标记成已经使用过了。
然后检查如果扣减成功。
Coin valueSentFromMe = tx.getValueSentFromMe(this);
Coin newBalance = balance.add(valueSentToMe).subtract(valueSentFromMe);
if (valueSentToMe.signum() > 0) {
checkBalanceFuturesLocked(null);
queueOnCoinsReceived(tx, balance, newBalance);
}
if (valueSentFromMe.signum() > 0)
queueOnCoinsSent(tx, balance, newBalance);
maybeQueueOnWalletChanged();
我们假设这里是发送一个比特币出去。
protected void queueOnCoinsSent(final Transaction tx, final Coin prevBalance, final Coin newBalance) {
checkState(lock.isHeldByCurrentThread());
for (final ListenerRegistration<WalletCoinsSentEventListener> registration : coinsSentListeners) {
registration.executor.execute(new Runnable() {
@Override
public void run() {
registration.listener.onCoinsSent(Wallet.this, tx, prevBalance, newBalance);
}
});
}
}
这里起了一个监听器。
起了一个线程。
妈了个波的,关键核心用jni写的native的代码。看个毛啊。
/**
* An event listener that relays events to a native C++ object. A pointer to that object is stored in
* this class using JNI on the native side, thus several instances of this can point to different actual
* native implementations.
*/
public class NativeWalletCoinsSentEventListener implements WalletCoinsSentEventListener {
public long ptr;
@Override
public native void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance);
}
评价一哈这代码:
代码很绕,看的很费劲。
大量共享变量,看的很绕很绕。
但是咱们自己写代码的时候,用共享变量用的很爽耶。哈哈。
果然程序员是一样的。