Keep a `ByteData` around in `CodedBufferReader` to avoid repeated `ByteData` allocs (#890)

In Dart, the only way to convert a list of bytes to an `int` or `double` is by using a `ByteData`.

Currently `CodedBufferReader` allocates a new `ByteData` for every fixed-size `int` or `float` decoding.

With this change we now allocate a `ByteData` once initialization and reuse it.

Benchmark results, using the same benchmark reported in #888:

|                              | Before     | After      | Diff                |
|------------------------------|------------|------------|---------------------|
| AOT                          |  26,763 us |  25,305 us | - 1,458 us, - 5.4%  |
| JIT                          |  24,573 us |  25,521 us | +   948 us, + 3.8%  |
| dart2js -O4                  | 212,000 us | 199,300 us | -12,700 us, - 5.9%  |
| dart2wasm --omit-type-checks |  89,750 us |  78,958 us | -10,792 us, -12.0%  |
diff --git a/protobuf/lib/src/protobuf/coded_buffer_reader.dart b/protobuf/lib/src/protobuf/coded_buffer_reader.dart
index 3c2165c..cc7e3dd 100644
--- a/protobuf/lib/src/protobuf/coded_buffer_reader.dart
+++ b/protobuf/lib/src/protobuf/coded_buffer_reader.dart
@@ -13,6 +13,11 @@
   static const int DEFAULT_SIZE_LIMIT = 64 << 20;
 
   final Uint8List _buffer;
+
+  /// [ByteData] of [_buffer], created once to be able to decode fixed-size
+  /// integers and floats without having to allocate a [ByteData] every time.
+  late final ByteData _byteData = ByteData.sublistView(_buffer);
+
   int _bufferPos = 0;
   int _currentLimit = -1;
   int _lastTag = 0;
@@ -123,9 +128,20 @@
   Int64 readUint64() => _readRawVarint64();
   int readSint32() => _decodeZigZag32(readUint32());
   Int64 readSint64() => _decodeZigZag64(readUint64());
-  int readFixed32() => _readByteData(4).getUint32(0, Endian.little);
+
+  int readFixed32() {
+    final pos = _bufferPos;
+    _checkLimit(4);
+    return _byteData.getUint32(pos, Endian.little);
+  }
+
   Int64 readFixed64() => readSfixed64();
-  int readSfixed32() => _readByteData(4).getInt32(0, Endian.little);
+
+  int readSfixed32() {
+    final pos = _bufferPos;
+    _checkLimit(4);
+    return _byteData.getInt32(pos, Endian.little);
+  }
 
   Int64 readSfixed64() {
     final pos = _bufferPos;
@@ -158,8 +174,17 @@
         .convert(_buffer, stringPos, stringPos + length);
   }
 
-  double readFloat() => _readByteData(4).getFloat32(0, Endian.little);
-  double readDouble() => _readByteData(8).getFloat64(0, Endian.little);
+  double readFloat() {
+    final pos = _bufferPos;
+    _checkLimit(4);
+    return _byteData.getFloat32(pos, Endian.little);
+  }
+
+  double readDouble() {
+    final pos = _bufferPos;
+    _checkLimit(8);
+    return _byteData.getFloat64(pos, Endian.little);
+  }
 
   int readTag() {
     if (isAtEnd()) {
@@ -270,10 +295,4 @@
     }
     throw InvalidProtocolBufferException.malformedVarint();
   }
-
-  ByteData _readByteData(int sizeInBytes) {
-    _checkLimit(sizeInBytes);
-    return ByteData.view(_buffer.buffer,
-        _buffer.offsetInBytes + _bufferPos - sizeInBytes, sizeInBytes);
-  }
 }