androidandroid-servicefreezebitcoinj

Android UI freeze when service is downloading progress


I'm using the library bitcoinj to create a Android wallet. The case is that I want the blockchain to be downloaded in the background so that the user can start using the other features of the app, but when it starts the download and opens a new activity, the application freezes, you can't use anything, not even return to the previous screen. This is part of the code:

public class BitcoinService extends Service {
    private final PeerDataEventListener blockchainDownloadListener = new AbstractPeerEventListener() {
        private final long CALLBACK_TIME = 1000L;
        private final AtomicLong lastMessageTime = new AtomicLong(0);

        private int height;
        private int blocksLeft;
        private Block block;

        @Override
        public void onBlocksDownloaded(final Peer peer, final Block block, final FilteredBlock filteredBlock, final int blocksLeft) {
            config.maybeIncrementBestChainHeightEver(blockChain.getChainHead().getHeight());

            delayHandler.removeCallbacksAndMessages(null);

            final long now = System.currentTimeMillis();

            this.block = block;
            this.height = (int) peer.getBestHeight() - blocksLeft;
            this.blocksLeft = blocksLeft;

            if (now - lastMessageTime.get() > CALLBACK_TIME) {
                delayHandler.post(RUNNER);
            } else {
                delayHandler.postDelayed(RUNNER, CALLBACK_TIME);
            }
        }

        private final Runnable RUNNER = new Runnable() {
            @Override
            public void run() {
                notifyBlockchainProgress(height, (height + blocksLeft));
                Log.e(TAG, "LAST_BLOCK=" + height + ", REMAINS=" + blocksLeft);
                if (blocksLeft == 0) {
                    broadcastBlockchainDownloaded();
                }
            }
        };
    };

    private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
        @SuppressLint("Wakelock")
        @Override
        public void onReceive(final Context context, final Intent intent) {
            Log.d(TAG, "acquiring wakelock");
            wakeLock.acquire();

            // consistency check
            final int walletLastBlockSeenHeight = wallet.getLastBlockSeenHeight();
            final int bestChainHeight = blockChain.getBestChainHeight();
            if (walletLastBlockSeenHeight != -1 && walletLastBlockSeenHeight != bestChainHeight) {
                final String message = "wallet/blockchain out of sync: " + walletLastBlockSeenHeight + "/" + bestChainHeight;
                Log.e(TAG, message);
            }

            Log.i(TAG, "starting peergroup");
            peerGroup = new PeerGroup(Constants.WALLET.NETWORK_PARAMETERS, blockChain);
            peerGroup.setDownloadTxDependencies(false);

            peerGroup.setUserAgent("TestWallet", "0.0.1");

            peerGroup.setMaxConnections(6);
            peerGroup.setConnectTimeoutMillis(15000);
            peerGroup.setPeerDiscoveryTimeoutMillis(10000);

            // start peergroup
            peerGroup.startAsync();
            peerGroup.startBlockChainDownload(blockchainDownloadListener);
        }
    };

    private final Handler delayHandler = new Handler();
    private PeerGroup peerGroup;
    private WakeLock wakeLock;
    private PeerConnectivityListener peerConnectivityListener;
    private NotificationManager nm;
    private Configuration config;
    private Wallet wallet;

    @Override
    public void onCreate()  {
        wallet = new Wallet(Constants.WALLET.NETWORK_PARAMETERS);
        nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        final String lockName = getPackageName() + " blockchain sync";

        final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
        config = Configuration.getInstance();

        broadcastPeerState(0);

        blockChainFile = Constants.WALLET.BLOCKCHAIN_FILE;
        final boolean blockChainFileExists = blockChainFile.exists();

        if (!blockChainFileExists) {
            new File(Constants.WALLET.WALLET_PATH).mkdirs();
        }
        try {
            blockStore = new SPVBlockStore(Constants.WALLET.NETWORK_PARAMETERS, blockChainFile);
            blockStore.getChainHead(); // detect corruptions as early as possible

            final long earliestKeyCreationTime = wallet.getEarliestKeyCreationTime();

            if (!blockChainFileExists && earliestKeyCreationTime > 0){
                try {
                    config.setDeletingBlockchain(true);
                    final long start = System.currentTimeMillis();
                    final InputStream checkpointsInputStream = getAssets().open("bitcoin/" + Constants.WALLET.CHECKPOINTS_FILENAME);
                    CheckpointManager.checkpoint(Constants.WALLET.NETWORK_PARAMETERS, checkpointsInputStream, blockStore, earliestKeyCreationTime);
                    Log.i(TAG, String.format("checkpoints loaded from '%1$s', took %2$dms", Constants.WALLET.CHECKPOINTS_FILENAME, System.currentTimeMillis() - start));
                } catch (final IOException x) {
                    Log.e(TAG, "problem reading checkpoints, continuing without", x);
                }
            }
        } catch (final BlockStoreException x) {
            blockChainFile.delete();

            final String msg = "blockstore cannot be created";
            Log.e(TAG, msg, x);
        }

        try {
            blockChain = new BlockChain(Constants.WALLET.NETWORK_PARAMETERS, walletHelper.getMainWallet(), blockStore);
        } catch (final BlockStoreException x) {
            throw new Error("blockchain cannot be created", x);
        }

        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
        registerReceiver(connectivityReceiver, intentFilter); // implicitly start PeerGroup
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        Log.e(TAG, "onStartCommand");
        ...
        return START_NOT_STICKY;
    }

    private void notifyBlockchainProgress(int progress, int max) {
        boolean isCompleted = progress == max;
        if (config.isDeletingBlockchain()) {
            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
            long percent = Math.round(Math.floor((progress * 100) / max));
            mBuilder.setContentTitle("Blockchain download")
                .setContentText(" Synchronization in progress  - " + percent + "%")
                .setSmallIcon(R.drawable.ic_logo_splash)
                .setProgress(max, progress, false)
                .setAutoCancel(false);

            if (isCompleted) {
                mBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                    .setProgress(0, 0, false)
                    .setAutoCancel(true)
                    .setContentText(getString(R.string.synchronization_completed));
                config.setDeletingBlockchain(false);
            }

            Notification notif = mBuilder.build();

            if (isCompleted) {
                notif.flags = Notification.FLAG_FOREGROUND_SERVICE;
            } else {
                notif.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE;
            }

            nm.notify(2, notif);
        }

    }

    public void broadcastBlockchainDownloaded() {
        Intent i = new Intent(UiRefreshReceiver.REFRESH_UI);
        LocalBroadcastManager.getInstance(this).sendBroadcast(i);
    }
}

I used part of the code of the official project of the Android wallet


Solution

  • Service in android runs in UI thread. You need to put all the job into different thread inside your Service