Lembre-se, não defina SingleTask / SingleInstance na interface de inicialização do seu aplicativo

Autor: intestino de porco triste

fundo

Recentemente, estou fazendo a otimização de inicialização do aplicativo. Para obter o efeito de inicialização rápida, removemos a página da tela inicial de nosso aplicativo (SplashActivity exibe uma imagem fixa), substituímos pelo fundo MainActivity (windowBackground) e, finalmente, substituímos pelo tema do aplicativo. Ofereça aos usuários uma experiência de resposta rápida.

<style name="AppWelcomeTheme" parent="BaseAppTheme">
        <item name="android:windowBackground">@drawable/flash_bg</item>
</style>
//flash_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="#fff" />
        </shape>
    </item>
    <!--底层使用蓝色填充色-->
    <item
        android:gravity="center"
        android:top="60dp">
        <bitmap
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:src="@drawable/ic_splash_logo" />
    </item>
</layer-list>

AndroidManifest.xml

<activity
            android:name=".ui.main.MainActivity"
            android:theme="@style/AppWelcomeTheme"

Quando uma MainActivity é iniciada, ela primeiro exibe uma janela de visualização para dar aos usuários uma experiência de resposta rápida. Quando a atividade deseja restaurar o tema original, você pode chamar setTheme (R.style.AppTheme) antes de chamar super.onCreate () e setContentView (), da seguinte maneira:

public class MyMainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Make sure this is before calling super.onCreate
    setTheme(R.style.AppTheme);
    super.onCreate(savedInstanceState);
    // ...
  }
}

No entanto, houve um problema com a otimização . O modo de inicialização usado por nossa MainActivity era SingleTask. Depois de remover a tela inicial, independentemente de quantas páginas eu abrisse, coloquei o aplicativo em segundo plano e o iniciei de volta à página principal (MainActivity) . Esse é um problema sério. Felizmente, o problema foi descoberto em tempo hábil.


Solução de problemas

Ao solucionar problemas, primeiro verifique se a versão anterior tem o problema (e nenhum problema foi encontrado) e, em seguida, verifique meu registro de envio de código, descobri que a modificação principal que fiz em AndroidManifest.xml removeu a interface da tela inicial e cliquei em App A atividade principal é iniciada diretamente, mas a armadilha é que achei que era um problema causado pela introdução do link dinâmico e pensei que o link dinâmico precisa ser passado sequencialmente da interface de inicialização. Depois de remover todos os links dinâmicos, descobri que o problema ainda existe. Resolva o problema.

Como resultado, caí na dúvida. Tenho feito desenvolvimento para Android há vários anos. Quando (MainActivity) está definido como SingleTask, haverá essa mudança, por que não percebi?

Poderia ser a modificação provocada pelas alterações da versão mais recente da API, por que a nova modificação é tão trapaceira? Então comecei a testar com diferentes versões da máquina virtual, ou definir targetSdkVersion diferente para teste, os resultados foram os mesmos, cada vez que era MainActiivy.

Voltei a pensar profundamente, é uma ilusão que a SingleTask que a MainActiviy tem usado por tantos anos? Mas por que os aplicativos anteriores não tiveram esse problema. (Na verdade, havia uma página SplashActivity antes, mas não há mais SplashActivity)

Mais tarde, determinei cuidadosamente o conteúdo do registro de envio e descobri que removi a interface da tela inicial que pode afetá-lo. O efeito de restaurar a página da tela inicial não teve esse problema. Após determinar o problema, foi o problema de a página da tela inicial ter sido fotografada ou não. O problema é causado pela configuração da interface de inicialização como SingleTask.

Posteriormente, vi algumas soluções na Internet, principalmente definindo o modo de inicialização como standard ou SingleTop e, em seguida, adicionando Flag a Intent.FLAG_ACTIVITY_CLEAR_TOP para resolver ou para obter o efeito de limpeza de pilha semelhante a este SingleTask sem causar todas as inicializações É MainActivity.


Análise detalhada do código-fonte relacionado à SingleTask

No entanto, todos os artigos na Internet não analisaram cuidadosamente por que o problema foi causado. Eu também li algumas análises de código-fonte do processo de inicialização do Activity. Eu apenas peguei um determinado nome de método e não analisei o processo. Não tive escolha a não ser fazer isso sozinho.


Fluxograma inicial

Você pode ver o módulo de inicialização em Activity.startActivity na figura e, em seguida, dar uma olhada no processo. É fácil ver onde o método geral aparece. Esta é a vantagem de estar familiarizado com o processo de inicialização e desenho.

startActivityUnchecked

Deixe-me falar sobre a lógica geral do código relacionado startActivityUnchecked. Obtenha uma reusedActivity em getReusableIntentActivity. Como este é um início a quente, nossa Activity foi criada antes e não há nenhuma nova Activity a ser inserida na pilha, portanto, o retorno não está vazio;

Insira if (reusedActivity! = Null) {lógica de julgamento, o seguinte isLaunchModeOneOf (LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK) condição também é estabelecida e, em seguida, insira o próximo julgamento lógico e, em seguida, julgue se é a atividade raiz, defina a atividade iniciada para nossa mStartActivity (MainActivity), então Quando a atividade de inicialização do APP for MainActivity, defina o modo de inicialização para SingleTask ou SingleInstance ao mesmo tempo, e a interface que você vê toda vez que clicar no ícone do aplicativo é MainActivity.

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity, boolean restrictedBgActivity) {
  ···
  //从getReusableIntentActivity中获取  
  ActivityRecord reusedActivity = getReusableIntentActivity();  
  ···
//不为空时进入该循环   
if (reusedActivity != null) {
            // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
            // still needs to be a lock task mode violation since the task gets cleared out and
            // the device would otherwise leave the locked task.
 ···
 ···  

// This code path leads to delivering a new intent, we want to make sure we schedule it
// as the first operation, in case the activity will be resumed as a result of later
// operations.
//isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)表示启动模式为或者SingleInstance或者SingleTask时,进入该判断   
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
        || isDocumentLaunchesIntoExisting(mLaunchFlags)
        || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
    final TaskRecord task = reusedActivity.getTaskRecord();

    // In this situation we want to remove all activities from the task up to the one
    // being started. In most cases this means we are resetting the task to its initial
    // state.
   //大多数情况下我们可能准备清空当前task或者回到task的初始状态
    final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
            mLaunchFlags);

    // The above code can remove {@code reusedActivity} from the task, leading to the
    // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
    // task reference is needed in the call below to
    // {@link setTargetStackAndMoveToFrontIfNeeded}.
    if (reusedActivity.getTaskRecord() == null) {
        reusedActivity.setTask(task);
    }

    if (top != null) {
      	//是否为根activity  
      	//boolean frontOfTask; // is this the root activity of its task?
        if (top.frontOfTask) {
            // Activity aliases may mean we use different intents for the top activity,
            // so make sure the task now has the identity of the new intent.		 //设置启动Activity为根Activity
            top.getTaskRecord().setIntent(mStartActivity);
        }
    		//将会调用该Activity的onNewIntent,一旦调用了mStartActivity,因为我们也设置了SingleTask或者SingleInstance,所以我们每次看到的都是mStartActivity
        deliverNewIntent(top);
    }
}
} 
···
···
}

Vamos dar uma olhada no método getReusableIntentActivity primeiro, olhar as anotações do método e logo entender a função, para que o retorno não seja nulo, então ele entrará na lógica de julgamento acima

/**
 * Decide whether the new activity should be inserted into an existing task. Returns null
 * if not or an ActivityRecord with the task into which the new activity should be added.
 */
private ActivityRecord getReusableIntentActivity() {
    // We may want to try to place the new activity in to an existing task.  We always
    // do this if the target activity is singleTask or singleInstance; we will also do
    // this if NEW_TASK has been requested, and there is not an additional qualifier telling
    // us to still place it in a new task: multi task, always doc mode, or being asked to
    // launch this as a new task behind the current one.

Vamos dar uma olhada em DeliverNewIntentLocked que é chamado em DeliverNewIntent e, finalmente, decidir qual Activity's onNewIntent será chamada, que é nossa mStartActivity

/**
 * Deliver a new Intent to an existing activity, so that its onNewIntent()
 * method will be called at the proper time.
 */
final void deliverNewIntentLocked(int callingUid, Intent intent, String referrer) {
    // The activity now gets access to the data associated with this Intent.

Problemas que podem surgir desde a primeira instalação

Durante o processo de desenvolvimento, quando um aplicativo é instalado, clique para abri-lo diretamente na interface de instalação. Entramos na página inicial do aplicativo. Nesse momento, pressionamos o botão inicial para retornar à área de trabalho e, em seguida, clicamos no ícone do aplicativo. Descobriremos que, em vez de entrar diretamente na página inicial, primeiro entramos na página da tela inicial do aplicativo e, em seguida, entramos na página inicial. Repita essa etapa para sempre. Nesse momento, pressionamos a tecla Voltar para retornar e descobrimos que não voltamos para a área de trabalho diretamente, mas voltamos para as várias páginas iniciais que abrimos antes. Mas se não abrirmos diretamente no início da instalação, mas clicarmos no aplicativo para entrar no desktop.


solução

Em sua interface de tela inicial, ou se não houver interface de tela inicial, se a interface de inicialização acima for MainActivity diretamente, você poderá adicionar diretamente o seguinte código no método onCreate da interface. A análise específica pode ser vista neste artigo abaixo

if (!this.isTaskRoot()) { // 当前类不是该Task的根部,那么之前启动
            Intent intent = getIntent();
            if (intent != null) {
                String action = intent.getAction();
                if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) { // 当前类是从桌面启动的
                    finish(); // finish掉该类,直接打开该Task中现存的Activity
                    return;
                }
            }
        }

Resumindo

Tenha cuidado para não definir o modo de inicialização para SingleTask ou SingleInstance em sua interface de inicialização (se você quiser definir o fundo da janela de MainActivity como uma interface de tela inicial, remova a página da tela inicial e inicie MainActivity diretamente para dar aos usuários uma sensação de inicialização rápida), Depois de definido, independentemente do início suave ou do início a quente, inicie o aplicativo a partir da interface de início, a menos que você tenha requisitos especiais, não defina dessa forma.

Se você deseja obter um efeito de eliminação de pilha semelhante ao SingleTask, você pode usar singleTop em combinação com o Flag correspondente para alcançar (observe que o padrão irá recriar uma nova instância, não importa qual flag for correspondido).

Finalmente, vamos falar sobre outra situação. Se você definitivamente deseja definir SingleTask / SingleInstance na página de inicialização (você não pode viver sem configuração), há também uma maneira de adicionar uma interface de tela inicial (SplashActivity é definido como SingleTask / SingleInstance), e então Inicie MainActivity, preste atenção aqui, a interface da tela inicial (SplashActivity) deve ser fechada a tempo e, ao mesmo tempo, adicione o seguinte código ao método onCreate da página da tela inicial, caso contrário, toda vez que você clicar no ícone do aplicativo, a página da tela inicial começará a ser exibida . Mas a otimização acima de remover a página da tela inicial e o início rápido não faz sentido.

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: =========");
        //关键代码
        if (!isTaskRoot()) {
            Intent intent = getIntent();
            if (intent != null) {
                if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
                    finish();
                    return;
                }
            }
        }
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        finish();
    }

Por fim, também compartilho aqui um artigo seco. O PDF de aprendizado do Android + vídeo de arquitetura + notas de origem coletadas por grandes caras , bem como mapas cerebrais avançados de tecnologia de arquitetura avançada, materiais especiais de entrevista de desenvolvimento Android, materiais de arquitetura avançada para ajudar todos a aprender e melhorar Avançado, também economiza o tempo de todos para pesquisar informações na Internet para aprender, e você também pode compartilhar com amigos ao seu redor para aprenderem juntos

Se precisar, você pode clicar para obtê-lo

Acho que você gosta

Origin blog.csdn.net/ajsliu1233/article/details/110482267
Recomendado
Clasificación