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.