在线程之间传递对象应该尽量使用对象的副本,待传递的对象应该实现Parcelable接口。例如下面的sendToTarget方法就是不正确的,虽然它可以正常运行(可能会出莫名其妙的问题),但是不建议这样做。

//不建议的方法
 private void sendToTarget(Handler handler, int action, Parcelable parcelable) throws IOException {
    Message msg = handler.obtainMessage();
    Bundle bundle = msg.getData();
    bundle.putParcelable("xxx",parcelable);
    msg.what = action;
    msg.sendToTarget();
}

正确的使用方法

实现Parcelable接口的对象应该转换为字节数组(byte[]),然后再将字节数组传递给其它线程使用。代码如下:

public class SelfUtils {
    //region TODO:对象序列化及反序列化


    /**
     * Parcelable 序列化时使用的字节缓冲区,常量虽然会占用一定的内存,但是运行性能较好
     */
    private static final ByteArrayOutputStream PARCELABLE_STREAM = new ByteArrayOutputStream(256);

    /**
     * 将对象转换为字节数组
     *
     * @param parcelable 转换对象
     * @return 字节数组
     * @throws IOException IOException
     */
    public static byte[] bytesFromParcel(Parcelable parcelable) throws IOException {
        Parcel parcel = Parcel.obtain();
        PARCELABLE_STREAM.reset();
        parcelable.writeToParcel(parcel, 0);
        PARCELABLE_STREAM.write(parcel.marshall());
        PARCELABLE_STREAM.flush();
        return PARCELABLE_STREAM.toByteArray();
    }

    /**
     * 字节数组转换为对象
     *
     * @param data 字节数组
     * @return Parcel对象
     */
    public static Parcel bytesToParcel(byte[] data) {
        Parcel parcel = Parcel.obtain();
        parcel.unmarshall(data, 0, data.length);
        parcel.setDataPosition(0);
        return parcel;
    }
    //endregion
}

线程A

 private void sendToTarget(Handler handler, int action, Parcelable parcelable) throws IOException {
   Message msg = handler.obtainMessage();
   Bundle bundle = msg.getData();
   bundle.putByteArray(ASYNC_RESULT, SelfUtils.bytesFromParcel(parcelable));
   msg.what = action;
   msg.sendToTarget();
}
//使用方法:将对象SampleObject的副本传递给其它线程使用,SampleObject已经实现了Parcelable接口。
//其中ActionCode.System.CLEAR_CACHE为自定义的任务号,它是一个整数,你可以使用任何一个数字。
//SampleObject obj = new SampleObject();
this.sendToTarget(handler, ActionCode.System.CLEAR_CACHE, obj);

线程B

在线程B的消息循环处理中,接收来自线程A发送的消息(消息任务号为:ActionCode.System.CLEAR_CACHE),然后将SampleObject对象反序列化后得到SampleObject副本。

//region TODO 消息循环处理
Handler handlerMessage() {
   return new Handler(requireContext().getMainLooper()) {
       @Override
       public void handleMessage(@NonNull Message msg) {
       	   //在消息循环中获取Context对象不使用requireContext()
       	   //应该使用getContext(),然后检查它的返回值是否为null。
           Context context = getContext();
           if (null == context)
               return;
           try {
               switch (msg.what) {    
                   case ActionCode.System.CLEAR_CACHE:
                       do {
                           Bundle bundle = msg.getData();
                           byte[] data = bundle.getByteArray(ServiceAsync.ASYNC_RESULT);
                           //反序列化SampleObject
                           SampleObject obj = new SampleObject (SelfUtils.bytesToParcel(data));                   
                       } while (false);
                       break;                   
           } catch (Exception e) {
               Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
               e.printStackTrace();
           }
       }
   };
}
//endregion

总结

  • 在线程之间传递数据最好使用原始数据类型,例如int、long、bytes、String。尽量不要使用其它相对高级的数据类型。
  • 在消息循环中获取Context对象应该使用getContext()方法。因为当Fragment消息循环时,在消息到达时刚好Fragment从Activity中分离(onDetach方法执行完成),此时消息循环使用requireContext获取的Context 对象为null, requireContext在内部会检查Context是否为null,为null时会抛出IllegalStateException异常,导致应用崩溃。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐