如何在不阻塞 UI 线程的情况下传输 Android 资产?

How do I transfer an Android asset without blocking the UI thread?

我正在尝试按照 android 开发人员培训来转移资产,该培训说要使用此代码:

@Override
public void onDataChanged(DataEventBuffer dataEvents) {
  for (DataEvent event : dataEvents) {
    if (event.getType() == DataEvent.TYPE_CHANGED &&
        event.getDataItem().getUri().getPath().equals("/image")) {
      DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
      Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
      Bitmap bitmap = loadBitmapFromAsset(profileAsset);
      // Do something with the bitmap
    }
  }
}

public Bitmap loadBitmapFromAsset(Asset asset) {
    if (asset == null) {
        throw new IllegalArgumentException("Asset must be non-null");
    }
    ConnectionResult result =
           mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);
    if (!result.isSuccess()) {
        return null;
    }
    // convert asset into a file descriptor and block until it's ready
    InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
            mGoogleApiClient, asset).await().getInputStream();
            mGoogleApiClient.disconnect();

    if (assetInputStream == null) {
        Log.w(TAG, "Requested an unknown Asset.");
        return null;
    }
    // decode the stream into a bitmap
    return BitmapFactory.decodeStream(assetInputStream);
}

所以我用大致相同的方式做了同样的事情:

    // Build a new GoogleApiClient for the Wearable API
    googleClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                @Override
                public void onConnected(Bundle bundle) {
                    Wearable.DataApi.addListener(googleClient, onDataChangedListener);
                }

                @Override
                public void onConnectionSuspended(int i) {

                }
            })
            .addApi(Wearable.API)
            .build();
    googleClient.connect();

在我的 onDatachanged 方法中我有:

public DataApi.DataListener onDataChangedListener = new DataApi.DataListener() {
    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        Log.d(TAG, "Data changed: " + dataEvents);

        for (DataEvent event : dataEvents) {
            Log.d(TAG, "Data received: " + event.getDataItem().getUri());

            if (event.getType() == DataEvent.TYPE_CHANGED &&
                event.getDataItem().getUri().getPath().equals("/audio")) {
                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
                Asset audioAsset = dataMapItem.getDataMap().getAsset("audioAsset");
                audioBytes = loadBytesFromAsset(audioAsset);
            }

            // Set play button enabled
            handler.post(onNewAudio());
        }
    }
}

使用我的 loadBytesFromAsset() 方法:

public byte[] loadBytesFromAsset(Asset asset) {
    if (asset == null) {
        throw new IllegalArgumentException("Asset must be non-null");
    }

    result = googleClient.blockingConnect(3000, TimeUnit.MILLISECONDS);
    if(!result.isSuccess()){
        return null;
    }

    // Convert asset into a file descriptor and block until it's ready
    InputStream assetInputStream = Wearable.DataApi.getFdForAsset(googleClient, asset).await().getInputStream();
    googleClient.disconnect();

    if (assetInputStream == null) {
        Log.w(TAG, "Requested an unknown Asset.");
        return null;
    }
    // Decode the stream into a byte[]
    return getBytesFromInputStream(assetInputStream);
}

这似乎完全按照 Android 开发人员培训建议的那样进行,但是当我 运行 它时,'loadBytesFromAsset()' 方法崩溃并出现异常,提示我无法调用 blockingConnect () 在 UI 线程上。有谁知道如何解决这个问题?我应该如何监听然后检索资产?提前致谢。

(免责声明:我从未真正测试过这个答案,但这只是我通读 API)

请看这个 - https://developers.google.com/android/reference/com/google/android/gms/common/api/GoogleApiClient

您可以看到 connectBlocking 旁边还有 connect。文档说

Connects the client to Google Play services. This method returns immediately, and connects to the service in the background. If the connection is successful, onConnected(Bundle) is called and enqueued items are executed. On a failure, onConnectionFailed(ConnectionResult) is called.

所以你需要做的是调用 registerConnectionCallbacks 并传递给它实现 onConnectedConnectionCallbacks。这些回调将 运行 在 UI 线程中(就像您当前的回调 运行 在那里一样)。此外,您还可以对 isConnectionFailedListenerRegistered 执行相同的操作,当连接失败时将调用它。 这实际上是您在代码的第一段中已经在做的事情,只是您在构建器中设置了侦听器。

这将需要对您的代码进行一些更改,但我认为不会太严重。

好的,我让它工作了(有点),仍然有 onDataChanged 没有被调用的问题,但是 UI 线程和 blocking.connect 调用的问题通过重新执行解决了这样的代码 post here。他们的做法是让 class 实现 DataApi.DataListener、GoogleApiClient.ConnectionCallbacks 和 GoogleApiClient.OnConnectionFailedListener 接口,如下所示:

public class MainActivity extends Activity implements
        DataApi.DataListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{
    private TextView mTextView;
    private static final long CONNECTION_TIME_OUT_MS = 100;
    private static final String ON_MESSAGE = "On!";
    private static final String OFF_MESSAGE = "Off!";
    private static final String TAG = "Moto360DisplayControl";
    private GoogleApiClient client;
    private String nodeId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initApi();
    }

    private void initApi() {
        client = getGoogleApiClient(this);
        retrieveDeviceNode();
    }

    private GoogleApiClient getGoogleApiClient(Context context) {
        return new GoogleApiClient.Builder(context)
            .addApi(Wearable.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();
    }

    private void retrieveDeviceNode() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
                NodeApi.GetConnectedNodesResult result =
                    Wearable.NodeApi.getConnectedNodes(client).await();
                List<Node> nodes = result.getNodes();
                if (nodes.size() > 0) {
                    nodeId = nodes.get(0).getId();
                }
                client.disconnect();
            }
        }).start();
    }

    @Override
    protected void onStart() {
        super.onStart();
        client.connect();
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        Wearable.DataApi.addListener(client, this);
        Toast.makeText(this, "AddedListener!", Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionSuspended(int num) {
        Toast.makeText(this, "ConnectionSuspended", Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionFailed(ConnectionResult res) {
        Toast.makeText(this, "ConnectionFailed", Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onStop() {
        Wearable.DataApi.removeListener(client, this);
        client.disconnect();
        super.onStop();
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        Toast.makeText(this, "DataChanged!", Toast.LENGTH_LONG).show();
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().equals("/image")) {
                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
                Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
                Bitmap bitmap = loadBitmapFromAsset(profileAsset);
                // Do something with bitmap
                Toast.makeText(this, "DataChanged!", Toast.LENGTH_LONG).show();
            }
        }
    }

    public Bitmap loadBitmapFromAsset(Asset asset) {
        if (asset == null) {
            throw new IllegalArgumentException("Asset must be non-null");
        }

        ConnectionResult result = client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
        if (!result.isSuccess()) {
            return null;
        }

        // Convert asset into a file descriptor and block until it's ready
        InputStream assetInputStream = Wearable.DataApi.getFdForAsset(client, asset).await().getInputStream();
        client.disconnect();

        if (assetInputStream == null) {
            Log.w(TAG, "Requested an unknown Asset.");
            return null;
        }

        // Decode the stream into a bitmap
        return BitmapFactory.decodeStream(assetInputStream);
    }
}

使用这个方法,问题解决了,我还在努力解决onDataChanged没有被调用的问题,我已经问过了

鉴于您拥有的代码结构,在我看来您是在 onConnected() 回调(这是正确的位置)中注册您的 onDataChangedListener。在您的 onDataChangedListener#onDataChanged() 回调(在主线程上调用)中,您正在调用 loadBytesFromAsset()。在这种方法中,您不需要再次连接您的 google api 客户端;它应该在那时已经连接,所以不需要调用阻塞连接方法。检查以确保您已连接 (apiClient.isConnected()) 是一个很好的做法,然后继续做您想做的事。

也没有必要在离开您的应用程序之前断开您的 API 客户端(事实上,最好不要这样做)除非您真的确定您不想在您需要连接的应用程序;最好只在 activity 的 onStop() 中调用断开连接(并在 onStart() 中建立连接)。

就是说,如果您需要在这些回调(在主线程上)中执行的任何过程是一个很长的过程,那么您将需要生成一个单独的线程(例如,使用 AsyncTask、IntentService 或诸如此类的东西)并在那里处理漫长的过程。