Ele foi conectado ao aplicativo há algum tempo Flutter
e a Multiple Flutter
solução oficial de gerenciamento do mecanismo é usada. Atualmente, está funcionando bem online. Aqui está um resumo dos problemas encontrados.
Não há problema em integrar o Flutter ao aplicativo existente como um todo, basta seguir as instruções do documento e combinar a operação de demonstração. Depois de acessar vários idiomas, o modo escuro também pode ser executado normalmente como a parte nativa. Mas ainda encontrou alguns detalhes no desenvolvimento real.
Acima da otimização da dobra
Conforme mencionado na documentação oficial, mesmo com pré-aquecido , ainda demora algum tempo para exibir o conteúdo
FlutterEngine
pela primeira vez .Flutter
Para melhorar ainda mais a experiência do usuário,Flutter
há suporte para exibir a página da tela inicial antes que o primeiro quadro seja renderizado.
O problema que encontrei aqui é que existem quatro guias na página inicial e a terceira guia é a página do Flutter. Portanto, ao mudar para ele, o primeiro carregamento será uma tela branca.
Eu forneço dois métodos de otimização aqui. O primeiro método pode ser usado CachedEngineFragmentBuilder
em arquivos shouldDelayFirstAndroidViewDraw()
. Ele indica se deve atrasar o processo de desenho do Android até que a IU do Flutter seja exibida.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldDelayFirstAndroidViewDraw(true)
.build();
Há dois pontos a serem observados ao usar esse método:
- O modo de renderização deve ser usado
RenderMode.surface
. - Este atraso não desaparece, apenas mostra uma estratégia diferente. Se for a primeira vez que você clica na guia para alternar, após clicar, ele aguardará o término da exibição do Flutter antes de alternar. Em máquinas de baixo custo, haverá gagueira perceptível aqui.
O segundo método pode ser usado SplashScreen
para exibir uma página inicial. Deixamos a FlutterFragment
Activity hospedada implementar o método SplashScreenProvider
da interface provideSplashScreen
.
@Override
public SplashScreen provideSplashScreen() {
return new DrawableSplashScreen(this.getResources().getDrawable(R.drawable.xxx),
ImageView.ScaleType.CENTER_CROP, 300);
}
- Os parâmetros acima são para definir a imagem da tela inicial, o método de recorte da imagem e o tempo de animação de transição quando a interface do usuário do Flutter aparecer.
- O modo de renderização deve ser usado para que a animação de transição tenha efeito
RenderMode.texture
.
Esses dois métodos têm seus próprios cenários aplicáveis, e o primeiro pode ser usado na maioria dos casos. Como o plano de fundo da nossa página é uma imagem, podemos usar o segundo método. Exiba a imagem primeiro e, em seguida, use a transição de animação para exibir o conteúdo da página. O tempo de espera ao trocar pela primeira vez pode ser evitado usando o primeiro método.
Além disso, RenderMode.surface
atualmente existe um bug no modo de renderização (Flutter 3.10), que é ao carregar a página onResume
. FlutterFragment
aparecerá repentinamente porque SurfaceView
está no topo da hierarquia de visualização. Portanto, cobre outras páginas do Fragment. Não existe tal problema no uso atual RenderMode.texture
.
Problemas de acompanhamento podem ser encontrados aqui .
Activity é basicamente o mesmo que Fragment, e usa RenderMode.surface
o modo de renderização por padrão. Portanto, a atividade não será aberta até que o primeiro quadro da página seja renderizado. Se o fundo da página flutuante for uma imagem, quando você entrar na página pela primeira vez, ela piscará por um tempo porque a imagem carrega por um determinado período de tempo (a liberação é relativamente melhor). Portanto, você também pode considerar a solução de tela inicial.
As perguntas acima correspondem ao FlutterActivityAndFragmentDelegate
local do código-fonte onCreateView
:
View onCreateView(
LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState,
int flutterViewId,
boolean shouldDelayFirstAndroidViewDraw) {
Log.v(TAG, "Creating FlutterView.");
...
SplashScreen splashScreen = host.provideSplashScreen();
if (splashScreen != null) {
FlutterSplashView flutterSplashView = new FlutterSplashView(host.getContext());
flutterSplashView.setId(ViewUtils.generateViewId(FLUTTER_SPLASH_VIEW_FALLBACK_ID));
flutterSplashView.displayFlutterViewWithSplash(flutterView, splashScreen);
return flutterSplashView;
}
if (shouldDelayFirstAndroidViewDraw) {
delayFirstAndroidViewDraw(flutterView);
}
return flutterView;
}
private void delayFirstAndroidViewDraw(FlutterView flutterView) {
if (host.getRenderMode() != RenderMode.surface) {
throw new IllegalArgumentException(
"Cannot delay the first Android view draw when the render mode is not set to"
+ " `RenderMode.surface`.");
}
if (activePreDrawListener != null) {
flutterView.getViewTreeObserver().removeOnPreDrawListener(activePreDrawListener);
}
activePreDrawListener =
new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (isFlutterUiDisplayed && activePreDrawListener != null) {
flutterView.getViewTreeObserver().removeOnPreDrawListener(this);
activePreDrawListener = null;
}
return isFlutterUiDisplayed;
}
};
flutterView.getViewTreeObserver().addOnPreDrawListener(activePreDrawListener);
}
manipulação de exceção
FlutterFragment, IllegalStateException
IllegalStateException: The requested cached FlutterEngine did not exist in the FlutterEngineCache: 'my_engine_id'
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.setupFlutterEngine (FlutterActivityAndFragmentDelegate.java:280)
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onAttach (FlutterActivityAndFragmentDelegate.java:189)
at io.flutter.embedding.android.FlutterFragment.onAttach (FlutterFragment.java:1046)
at androidx.fragment.app.Fragment.performAttach (Fragment.java:2922)
at androidx.fragment.app.FragmentStateManager.attach (FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:275)
at androidx.fragment.app.FragmentStore.moveToExpectedState (FragmentStore.java:112)
at androidx.fragment.app.FragmentManager.moveToState (FragmentManager.java:1647)
at androidx.fragment.app.FragmentManager.dispatchStateChange (FragmentManager.java:3128)
at androidx.fragment.app.FragmentManager.dispatchCreate (FragmentManager.java:3061)
at androidx.fragment.app.FragmentController.dispatchCreate (FragmentController.java)
at androidx.fragment.app.FragmentActivity.onCreate (FragmentActivity.java:276)
Localização anormal:
void setupFlutterEngine() {
Log.v(TAG, "Setting up FlutterEngine.");
// First, check if the host wants to use a cached FlutterEngine.
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException(
"The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
+ cachedEngineId
+ "'");
}
return;
}
...
}
O motivo da análise deve ser que, após a página ser reciclada em segundo plano, quando a página é reaberta, FlutterEngine
descobre-se que ela não existe FlutterEngineCache
em . Por cachedEngineId
ter sido obtido por meio de getArguments()
aquisição, FlutterEngine
foi onDetach
removido.
@Override
public String getCachedEngineId() {
return getArguments().getString(ARG_CACHED_ENGINE_ID, null);
}
Portanto, o método de processamento é julgar se existe FragmentActivity.onCreate
antes FlutterEngine
e criá-lo quando não existir.
@Override
protected void onCreate(Bundle savedInstanceState) {
if (!FlutterEngineCache.getInstance().contains("my_engine_id")) {
FlutterEngine flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
}
super.onCreate(savedInstanceState);
...
}
- Dispositivos individuais aparecem
UnsatisfiedLinkError
Caused by java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: No implementation found for
void io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(float) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate and Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate__F)
at java.util.concurrent.FutureTask.report(FutureTask.java:123)
at java.util.concurrent.FutureTask.get(FutureTask.java:193)
at io.flutter.embedding.engine.loader.FlutterLoader.ensureInitializationComplete(FlutterLoader.java:221)
Caused by java.lang.UnsatisfiedLinkError: No implementation found for void io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(float) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate and Java_io_flutter_embedding_engine_FlutterJNI_nativeUpdateRefreshRate__F)
at io.flutter.embedding.engine.FlutterJNI.nativeUpdateRefreshRate(FlutterJNI.java)
at io.flutter.embedding.engine.FlutterJNI.updateRefreshRate(FlutterJNI.java:7)
at io.flutter.embedding.engine.loader.FlutterLoader$1.call(FlutterLoader.java:27)
at io.flutter.embedding.engine.loader.FlutterLoader$1.call(FlutterLoader.java)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
ou:
Caused by java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xx/base.apk"],nativeLibraryDirectories=[/data/app/xx/lib/x86, /system/lib, /vendor/lib]]] couldn't find "libflutter.so"
Geralmente ocorre durante a criação FlutterEngine
ou FlutterEngineGroup
, e não há método melhor no momento, mas você pode capturar essas exceções e fazer algumas operações de baixo para cima. Evite travamentos diretos que afetam o uso de outras funções pelos usuários. Esse tipo de problema é responsável por uma pequena proporção. Atualmente, apenas dois usuários relataram essa exceção.
O acompanhamento deste problema pode ser encontrado aqui .
recarga quente
Ao depurar o módulo de desenvolvimento misto (Flutter versão 3.10.x), descobriu-se que, quando há várias páginas Flutter (usando FlutterEngineGroup
Create), o recarregamento dinâmico fará com que o aplicativo congele. Encontrei um problema relacionado , tentei a versão beta 3.13.0 e descobri que isso foi corrigido. Aguardando o lançamento do estável.
Pacote
Todos nós sabemos que o desempenho do modo de depuração do Flutter é mediano, portanto, ao entregá-lo ao teste, para evitar alguns problemas de experiência. Podemos empacotar o módulo Flutter em uma versão.
Se você usar o método de integração baseado no Android Archive, poderá usar o pacote diretamente flutter_release
. Se você depende diretamente do código-fonte do módulo, pode modificar diretamente flutter/packages/flutter_tools/gradle/flutter.gradle
o código-fonte:
/**
* Returns a Flutter build mode suitable for the specified Android buildType.
*
* The BuildType DSL type is not public, and is therefore omitted from the signature.
*
* @return "debug", "profile", or "release" (fall-back).
*/
private static String buildModeFor(buildType) {
if (buildType.name == "profile") {
return "profile"
} else if (buildType.debuggable) {
return "debug"
}
return "release"
}
Basta alterar o "debug" acima para "release". iOS flutter/packages/flutter_tools/bin/xcode_backend.dart
é modificado em .
Obviamente, a modificação direta não é muito elegante, então você pode escrever um script de empacotamento para lidar com essa operação. Por exemplo, usando o Dart para obter o seguinte:
// 读取文件内容
File file = File('xxx\flutter\packages\flutter_tools\gradle\flutter.gradle');
String content = file.readAsStringSync();
// 修改文件内容
String newContent = content.replaceAll('return "debug"', 'return "release"// weilu');
// 将修改后的内容写回文件
file.writeAsStringSync(newContent);
Após a execução, restaure-o.
outro
- BUG em [v3.10.0] Vazamento de memória do FlutterViewController ao adicionar um aplicativo existente, várias instâncias do Flutter
- [Android, SystemChrome, FlutterActivity] A barra de status fica semitranslúcida ao usar um mecanismo Flutter pré-aquecido
Se houver novos problemas encontrados posteriormente, eles também serão registrados aqui de forma síncrona.