Reutilización de recursos de grupo de motor para múltiples instancias de FlutterView en Android

Al integrar módulos Flutter en una aplicación Android nativa, es común enfrentar desafíos de rendimiento cuando se requiere mostrar múltiples vistas de Flutter. La solución estándar, donde cada FlutterView crea su propio motor Flutter, provoca una carga repetida de recursos esenciales como el código Dart, el motor gráfico Skia y las fuentes. Esto se traduce en tiempos de inicio elevaods y un consumo de memoria significativo.

Para resolver esto, Flutter proporcioan el concepto de FlutterEngineGroup. Este actúa como un administrador centralizado para un conjunto de recursos compartidos (el grupo del motor). Múltiples instancias de FlutterEngine creadas a partir del mismo FlutterEngineGroup pueden reutilizar estos recursos, eliminando la necesidad de cargarlos de forma independiente.

Implementación en una Actividad con Vistas Flutter

Primero, se debe inicializar una única instancia de FlutterEngineGroup en la clase Application para garantizar su disponibilidad global.

public class AppPrincipal extends Application {
    private FlutterEngineGroup gestorDeMotores;

    @Override
    public void onCreate() {
        super.onCreate();
        gestorDeMotores = new FlutterEngineGroup(this);
        // Precalentamiento opcional del primer motor
        gestorDeMotores.createAndRunDefaultEngine(this);
    }

    public FlutterEngineGroup obtenerGestorDeMotores() {
        return gestorDeMotores;
    }
}

En una Activity, se crean múltiples FlutterEngine a partir de este gestor y se asocian a sus correspondientes FlutterView.

public class ActividadPrincipal extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.actividad_principal);
        LinearLayout contenedor = findViewById(R.id.contenedor_raiz);

        FlutterEngineGroup grupo = ((AppPrincipal) getApplication()).obtenerGestorDeMotores();

        // Motor y vista para la sección uno
        FlutterEngine motor1 = grupo.createAndRunDefaultEngine(this);
        FlutterView vista1 = new FlutterView(this);
        vista1.attachToFlutterEngine(motor1);

        // Motor y vista para la sección dos
        FlutterEngine motor2 = grupo.createAndRunDefaultEngine(this);
        FlutterView vista2 = new FlutterView(this);
        vista2.attachToFlutterEngine(motor2);

        // Configurar parámetros de layout
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f);
        vista1.setLayoutParams(params);
        vista2.setLayoutParams(params);

        contenedor.addView(vista1);
        contenedor.addView(vista2);
    }
}

Uso con Fragmentos de Flutter

En el paradigma de Fragmentos, el proceso es análogo. Se crea el motor a partir del grupo compartido y se inyecta en el Fragmento.

public class FragmentoFlutter extends FlutterFragment {
    public static FragmentoFlutter nuevaInstancia(FlutterEngine motor) {
        FragmentoFlutter frag = new FragmentoFlutter();
        frag.setFlutterEngine(motor);
        return frag;
    }
}

Luego, en la Activity host:

public class ActividadConFragmentos extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.actividad_fragmentos);

        FlutterEngineGrupo grupo = ((AppPrincipal) getApplication()).obtenerGestorDeMotores();

        FlutterEngine motorFrag1 = grupo.createAndRunDefaultEngine(this);
        FlutterEngine motorFrag2 = grupo.createAndRunDefaultEngine(this);

        FragmentoFlutter frag1 = FragmentoFlutter.nuevaInstancia(motorFrag1);
        FragmentoFlutter frag2 = FragmentoFlutter.nuevaInstancia(motorFrag2);

        FragmentTransaction txn = getSupportFragmentManager().beginTransaction();
        txn.add(R.id.contenedor_frag1, frag1);
        txn.add(R.id.contenedor_frag2, frag2);
        txn.commit();
    }
}

Estrategia de Precalentamiento

Para minimizar aún más la latencia inicial, se puede invocar la creación del primer motor dentro de onCreate() de la Application. Esto carga el grupo de recursos en segundo plano antes de que el usuario navegue a una pantalla que lo requiera.

Impacto en el Rendimiento

Las métricas típicas muestran mejoras sustanciales al adoptar este patrón:

Métrica Sin Agrupación de Motores Con Agrupación de Motores Mejora
Tiempo de carga inicial (ms) ~1500 ~800 ~47%
Uso de memoria (MB) ~200 ~120 ~40%

Es crucial garantizar que todas las vistas y fragmentos Flutter dentro de una aplicación utilicen el mismo FlutterEngineGroup. Los plugins de Flutter deben registrarse en cada FlutterEngine individual si su configuración lo requiere. Este modelo de recursos compartidos es fundamental para construir aplicaciones Android con múltiples islas Flutter eficientes y responsivas.

Etiquetas: Android Flutter FlutterEngineGroup Fragment Motor

Publicado el 7-28 03:25