Desarrollo de complementos Flutter - (avanzado)

I. Resumen

Flutter también tiene su propio repositorio de paquetes Dart. El desarrollo y la reutilización de complementos pueden mejorar la eficiencia del desarrollo y reducir el grado de acoplamiento de los proyectos Módulos funcionales comunes como solicitudes de red (http), autorización de usuario (permission_handler) y otro desarrollo del lado del cliente, solo necesitamos introducir el complemento correspondiente -ins para integrar rápidamente para el proyecto Capacidades relevantes, a fin de centrarse en la realización de funciones comerciales específicas.

Además de usar componentes populares en el almacén, los desarrolladores aún necesitan desarrollar nuevos componentes cuando se enfrentan a la división de lógica comercial común o la necesidad de encapsular capacidades nativas durante el desarrollo de un proyecto de Flutter. Este artículo toma un complemento native_image_view específico como ejemplo y presentará la creación, el desarrollo, la prueba y el lanzamiento de componentes de Flutter desde múltiples aspectos, tratando de demostrar completamente el proceso de desarrollo y lanzamiento de todo el componente de Flutter.

2. Comunicación entre Flutter y Native

Durante el desarrollo de complementos de Flutter, casi siempre se requiere la interacción de datos entre Flutter y Native. Por lo tanto, antes de desarrollar complementos, comprendamos brevemente el mecanismo del canal de plataforma .


La comunicación entre Flutter y Native se realiza a través de Platform Channel, que es un modelo C/S, en el que Flutter actúa como Cliente y las plataformas iOS y Android actúan como Host, Flutter envía mensajes a Native a través de este mecanismo, y Native invoca el plataforma después de recibir el mensaje Implemente su propia API y luego devuelva el resultado del procesamiento a la página de Flutter.

El mecanismo de Platform Channel en Flutter proporciona tres métodos de interacción:

  • BasicMessageChannel: utilizado para pasar cadenas e información semiestructurada;

  • MethodChannel: se usa para pasar llamadas a métodos y manejar devoluciones de llamadas;

  • EventChannel: utilizado para monitorear y enviar flujos de datos.

Aunque estos tres canales tienen propósitos diferentes, todos contienen tres variables miembro importantes:

(1) Nombre de la cadena

Indica el nombre del canal. Puede haber muchos canales en un proyecto, y cada canal debe usar un nombre único, de lo contrario puede sobrescribirse. El método de denominación recomendado es el nombre de la organización más el nombre del complemento, por ejemplo: com.tencent.game/native_image_view, si un complemento contiene varios canales, se puede distinguir aún más según los módulos funcionales.

(2) mensajero BinaryMessager

Como portador de comunicación entre Native y Flutter, puede transferir los datos binarios convertidos por códec entre Native y Flutter. Cada canal debe generar o proporcionar un mensajero correspondiente cuando se inicializa.Si el canal registra un controlador correspondiente, el mensajero mantendrá una relación de mapeo entre el nombre y el controlador.

Después de que la plataforma nativa reciba el mensaje de la otra parte, meesager distribuirá el contenido del mensaje al controlador correspondiente para su procesamiento. Una vez que se completa el procesamiento, el resultado del procesamiento se puede devolver a Flutter a través del resultado del método de devolución de llamada.

 

(3) códec MessageCodec/MethodCodec

Se utiliza para codificar y decodificar durante la comunicación entre Native y Flutter. El remitente puede codificar el tipo básico de Flutter (o Native) en binario para la transmisión de datos, y el receptor Native (o Flutter) puede convertir el binario en el tipo básico. que el manipulador pueda reconocer.

Nota: El complemento native_image_share implementado en este artículo solo usa la comunicación MethodChannel más utilizada. Flutter pasa la dirección de imagen remota o el nombre del archivo de imagen local al lado nativo a través de MethodChannel. Después de que las plataformas iOS y Android obtengan la imagen, convierta en binario y devolverlo a través de resultado. Se pueden proporcionar más ejemplos de MessageChannel y EventChannel como referencia al final del artículo para una lectura ampliada.

3. Creación de complementos 

Los componentes de Flutter se pueden dividir en dos tipos según si contienen código nativo:

  • Flutter Package (paquete) : contiene solo código dart, generalmente una implementación de encapsulación de funciones específicas de flutter, como paquetes http para solicitudes de red.

  • Flutter Plugin (complemento) : además del código dart, también incluye la implementación del código de las plataformas Android e iOS. A menudo se usa para encapsular las capacidades nativas del cliente y proporcionarlas al proyecto flutter. Por ejemplo, el complemento flutter_keyboard_visibility que se usa para determinar el estado visible del teclado monitorea los eventos de apertura y cierre del teclado en los lados de iOS y Android respectivamente, y luego pasa los eventos correspondientes al proyecto Flutter a través del canal de la plataforma.

  • Los complementos de Flutter se pueden crear a través de Android Studio (los complementos de Dart y Flutter deben instalarse en Android Studio) o se pueden crear mediante la línea de comandos.

  • Crear un complemento de Flutter

flutter create --org com.qidian.image --template=plugin --platforms=android,ios -i objc -a java native_image_view 

  • El uso de la instrucción --template=plugin crea un complemento que contiene código de iOS y Android;

  • Use la opción --org para especificar la organización, generalmente usando notación de nombre de dominio inversa;

  • Use la opción -i para especificar el lenguaje de desarrollo de la plataforma iOS, objc o swift;

  • Use la opción -a para especificar el lenguaje de desarrollo de la plataforma Android, java o kotlin.

El directorio lib se usa para almacenar la implementación del código del paquete, y el andamiaje de Flutter generará automáticamente un archivo dart con el mismo nombre que el paquete.
El archivo pubspec.yaml debe ser muy familiar para los estudiantes que han realizado el desarrollo de Flutter. El paquete o complemento en el que confiamos para desarrollar el paquete debe declararse en este archivo.

4. Desarrollo de complementos 

El proceso de desarrollo y lanzamiento de Plugin y Package es básicamente el mismo. Por el contrario, Plugin también implica el desarrollo de iOS y Android, y su implementación es más complicada.

En el escenario donde Flutter está incrustado en un proyecto nativo, un problema común es que cuando se usa la misma imagen tanto en Flutter como en el proyecto nativo, ambos lados se almacenarán por separado, es decir, la imagen se almacenará dos veces. A diferencia de Weex, Hippy y otros marcos multiplataforma basados ​​en JS que se basan en la adquisición y visualización de imágenes nativas, Flutter administra las imágenes por sí mismo y las dibuja directamente a través del motor Skia.


En respuesta a este problema, este artículo desarrollará un complemento de Flutter (native_image_view) para entregar la descarga y el caché de imágenes de Flutter a Native, y el lado de Flutter solo es responsable de dibujar las imágenes. Además, también podemos definir un protocolo especial para manejar la llamada de imágenes locales y, al mismo tiempo, resolver el problema de que Flutter no puede reutilizar imágenes locales de proyectos nativos.

 Nota: El complemento desarrollado en este artículo solo se usa para presentar el proceso de desarrollo y lanzamiento del complemento. No se recomienda usarlo directamente en el entorno de producción. Para el problema del almacenamiento en caché secundario de imágenes, también puede consulte el artículo sobre Textura (textura externa) en la lectura ampliada.

1. Desarrollo del lado del aleteo

Primero declaramos el MethodChannel del complemento en el lado de Flutter, y luego iniciamos una llamada de método al lado nativo a través del método de invocación (nombre del método, parámetro) en el método initState. Llame a setState y use el método Image.memory para dibujar los datos binarios en una pantalla de imagen.

vista_de_imagen_nativa.dart:

class _NativeImageViewState extends State<NativeImageView> {
  Uint8List _data;
  static const MethodChannel _channel =
      const MethodChannel('com.tencent.game/native_image_view');
  @override
  void initState() {
    super.initState();
    loadImageData();
  }

  loadImageData() async {
    _data = await _channel.invokeMethod("getImage", {"url": widget.url});
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return _data == null
        ? Container(
            color: Colors.grey,
            width: widget.width,
            height: widget.height,
          )
        : Image.memory(
            _data,
            width: widget.width,
            height: widget.height,
            fit: BoxFit.cover,
          );

2. Desarrollo nativo

(1) desarrollo iOS

La plataforma iOS del complemento utiliza el componente SDWebImage para descargar y almacenar en caché imágenes de red, por lo que debe declarar las dependencias en el archivo native_image_view.podspec.


s.dependency 'Flutter'
s.dependency 'SDWebImage'
s.platform = :ios, '8.0'

El scaffolding de Flutter genera automáticamente el archivo NativeImageViewPlugin.m y el método registerWithRegistrar para nosotros. Este método es el punto de entrada para la ejecución del componente y el administrador de complementos de Flutter llamará automáticamente.

En este método, creamos un MethodChannel con el mismo nombre que el lado de Flutter y creamos una instancia del objeto de complemento para manejar la llamada al método en el lado de Flutter. El método handleMethodCall se activará después de que MethodChannel reciba una llamada de método desde el lado de Flutter. Los desarrolladores pueden obtener el nombre del método y los parámetros a través de FlutterMethodCall y devolver el contenido de la imagen a través de FlutterResult.

NativeImageViewPlugin.m:

#import "NativeImageViewPlugin.h"
#import <SDWebImage/SDWebImage.h>

@implementation NativeImageViewPlugin
//组件注册接口,Flutter自动调用
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel
      methodChannelWithName:@"com.tencent.game/native_image_view"
            binaryMessenger:[registrar messenger]];
  NativeImageViewPlugin* instance = [[NativeImageViewPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"getImage" isEqualToString:call.method]) {
      [self getImageHandler:call result:result];
  } else {
      result(FlutterMethodNotImplemented);
  }
}

- (void)getImageHandler:(FlutterMethodCall*)call result:(FlutterResult)result{
  if(call.arguments != nil && call.arguments[@"url"] != nil){
      NSString *url = call.arguments[@"url"];
      if([url hasPrefix:@"localImage://"]){
        //获取本地图片
        NSString *imageName = [url stringByReplacingOccurrencesOfString:@"localImage://" withString:@""];
        UIImage *image = [UIImage imageNamed:imageName];
        if(image != nil){
            NSData *imgData = UIImageJPEGRepresentation(image,1.0);
            result(imgData);
        }else{
            result(nil);
        }
      }else {
        //获取网络图片
        UIImage *image = [[SDImageCache sharedImageCache] imageFromCacheForKey:url];
        if(!image){
          //本地无缓存,下载后返回图片
          [[SDWebImageDownloader sharedDownloader]
            downloadImageWithURL:[[NSURL alloc] initWithString:url]
            completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
              if(finished){
                result(data);
                [[SDImageCache sharedImageCache] storeImage:image forKey:url completion:nil];
              }
            }];
        }else{
          //本地有缓存,直接返回图片
          NSData *imgData = UIImageJPEGRepresentation(image,1.0);
          result(imgData);
        }
      }
  }
}
@end

Al procesar una llamada de imagen iniciada por Flutter, primero determine si Flutter solicita una imagen local o de red. Si es una imagen local, lea los datos binarios de la imagen directamente desde el objeto UIImage y devuélvalos; si es una imagen de red, primero determine si hay un caché local. Si hay un caché, regresará directamente. Si no hay caché, primero debe descargar la imagen y luego devolver los datos. 

(2) desarrollo de Android

La plataforma Android del complemento utiliza el componente Glide para descargar y almacenar en caché imágenes de red, y las dependencias deben declararse en el archivo build.gradle.

dependencies { implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'}

Para ser compatible con la versión histórica, el complemento del lado de Android debe implementar la misma lógica de registro y monitoreo de MethodChannel en los métodos onAttachedToEngine y registerWith. onMethodCall se usa para procesar llamadas a métodos en Flutter, y también proporciona MethodCall y Result objetos similares a la plataforma iOS.

android/src/main/xxxx/NativeImageViewPlugin.java:

//新的插件注册接口
@Override
public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
  channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "com.tencent.game/native_image_view");
  channel.setMethodCallHandler(this);
  setContext(flutterPluginBinding.getApplicationContext());
}

@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
  channel.setMethodCallHandler(null);
}

// Flutter-1.12之前的插件注册接口,功能与onAttachedToEngine一样
public static void registerWith(Registrar registrar) {
  NativeImageViewPlugin plugin = new NativeImageViewPlugin();
  plugin.setContext(registrar.context());
  final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.tencent.game/native_image_view");
  channel.setMethodCallHandler(plugin);
}

@Override
public void onMethodCall(final MethodCall call,final Result result) {
  if (call.method.equals("getImage")) {
    getImageHandler(call,result);
  } else {
    result.notImplemented();
  }
}

La lógica de implementación del código en el lado de Android es consistente con la de iOS. Primero, se juzga si Flutter está llamando a una imagen local o de red. Para una imagen local, el mapa de bits de la imagen se obtiene primero de acuerdo con el nombre del archivo, y luego se convierte en una matriz de bytes y se devuelve; la memoria caché y la descarga de la imagen de red se basan en Glide El componente se da cuenta de que después de obtener la memoria caché del archivo o la ruta de descarga, lee el archivo como una matriz de bytes y la devuelve.

public void getImageHandler(final MethodCall call,final Result result){
  HashMap map = (HashMap) call.arguments;
  String urlStr = map.get("url").toString();
  Uri uri = Uri.parse(urlStr);
  if("localImage".equals(uri.getScheme())){
    String imageName = uri.getHost();
    int lastIndex = imageName.lastIndexOf(".");
    if(lastIndex > 0){
      imageName = imageName.substring(0,lastIndex);
    }
    String imageUri = "@drawable/"+imageName;
    int imageResource = context.getResources().getIdentifier(imageUri, null, context.getPackageName());
    if(imageResource > 0){
      Bitmap bmp = BitmapFactory.decodeResource(context.getResources(),imageResource);
      ByteArrayOutputStream stream = new ByteArrayOutputStream();
      bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
      byte[] byteArray = stream.toByteArray();
      result.success(byteArray);
    }else{
      result.error("NOT_FOUND","file not found",call.arguments);
    }
  }else {
    Glide.with(context).download(urlStr).into(new CustomTarget<File>() {
      @Override
      public void onResourceReady(@NonNull File resource, @Nullable Transition<? super File> transition) {
        byte[] bytesArray = new byte[(int) resource.length()];
        try {
          FileInputStream fis = new FileInputStream(resource);
          fis.read(bytesArray);
          fis.close();
          result.success(bytesArray);
        } catch (IOException e) {
          e.printStackTrace();
          result.error("READ_FAIL",e.toString(),call.arguments);
        }
      }
      @Override
      public void onLoadFailed(@Nullable Drawable errorDrawable) {
        super.onLoadFailed(errorDrawable);
        result.error("LOAD_FAIL","image download fail",call.arguments);
      }
      @Override
      public void onLoadCleared(@Nullable Drawable placeholder) {
        result.error("LOAD_CLEARED","image load clear",call.arguments);
      }
    });
  }
}

5. Prueba de complemento

El scaffolding de Flutter genera automáticamente un proyecto de ejemplo al crear un complemento. Este proyecto hace referencia a los componentes que estamos desarrollando al especificar la ruta del complemento, lo que nos permite probar completamente el complemento antes de lanzarlo.

native_image_view:

path: ../

Además del desarrollo y la depuración, el proyecto de ejemplo también es un buen ejemplo del uso de complementos. En comparación con la documentación, muchos desarrolladores prefieren mirar directamente la implementación del código del ejemplo del complemento. Demostramos el uso de imágenes de red en main.dart Las imágenes locales deben tener los archivos correspondientes en el proyecto nativo.

dardo principal:


String url = "";
//String url = "localImage://xxx.jpeg";
@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: Text('example'),
      ),
      body: Center(
      child: NativeImageView(
        url: url,
        width: 300,
        height: 200,
      ),
    ),
  ));
}

 6. Liberación del complemento

Una vez que se completa el desarrollo del complemento, ingresa al enlace de lanzamiento. Para facilitar el mantenimiento posterior y los comentarios de los usuarios, mantenemos el complemento en github y completamos la dirección del almacén en el archivo pubspec.yaml del complemento. -en

name: native_image_view

description: 该组件提供了一种方式,可以让flutter通过methodChannel调用原生的本地和网络图片的加载

version: 0.0.1

repository: 

Antes de comprometerse con el almacén, debemos ejecutar el comando de ejecución en seco para verificar si el componente cumple actualmente con los requisitos de lanzamiento.


flutter pub publish --dry-run

 El archivo de LICENCIA creado para nosotros por Flutter scaffolding está vacío y los desarrolladores deben completar el acuerdo de código abierto del complemento por sí mismos. Si no lo completa, la ejecución en seco no aparecerá, pero seguirá informando un error en el paso de publicación en el almacén.

El artículo anterior enseñó cómo crear un archivo de LICENCIA, por lo que no entraré en detalles.

Supongo que te gusta

Origin blog.csdn.net/RreamigOfGirls/article/details/130224297
Recomendado
Clasificación