Activity

先讲actitivity的生命周期:

生命周期:onCreate() -> onStart() - > onResume() -> onPause() -> onStop() -> onDestroy()

在这里插入图片描述

  • 启动activity:系统先调用onCreate(),然后调用onStart(),最后调用onResume()方法,activity进入运行状态。

  • activity被覆盖(DialogActivity或者Menu)或者锁屏(注意,不是普通的Dialog,只有DialogActivity才会调用onPause,并且,覆盖的意思是当前activity还是可见的,只是变灰暗了。):系统会调用onPause()方法,暂停当前activity的执行。

  • 当前activity由被覆盖状态回到前台或者解锁屏:系统会调用onResume()方法,再次进入运行状态。

  • 当前Activity转到新的Activity界面或按Home键回到主屏,自身退居后台:系统会先调用onPause方法,然后调用onStop方法,进入停滞状态。

  • 用户后退回到此Activity:系统会先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,再次进入运行状态。

  • 当前Activity处于被覆盖状态或者后台不可见状态,即第2步和第4步,系统内存不足,杀死当前Activity,而后用户退回当前Activity:再次调用onCreate方法、onStart方法、onResume方法,进入运行状态。

  • onPause():表示activity正在停止,此时可以做一些存储数据,停止动画等工作,注意不能太耗时,因为这会影响到新activity的显示,onPause必须先执行完,新的activity的onResume才会执行。旧activity先onPause,然后新activity在启动

在此处特别声明,,我们知道不管是活动被覆盖还是跳转其他界面还是销毁等所有情况,都会调用 onPause(),如果你需要希望保证activity不管是什么情况不见的时候想要保存activity的数据,应该再 onPause()上,(例如用户编辑)而不是在onStop()和onDestroy()中. 不是这个保存不能太久,不然影响下一个活动的显示。


活动发生异常


当活动屏幕发生改变,或者发生异常被系统杀死重新,活动就会重新创建新实例。这样原来的数据就会消失,这里分为几种情况。

a

如果是少量数据,可以通过onSaveInstanceState()和onRestoreInstanceState()进行保存与恢复。

activity销毁,依次调用onPause,onSaveInstanceState,onStop,onDestory,然后再重依次调用, onCreate,onStart,onRestoreInstanceState, onResume。

这种情况就重写onSaveInstance方法,

 @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }

在onSaveInstance方法里,利用个Bundle对象,诸如putString()和putInt()之类的方法将关于Activity的状态信息保存为名称 - 值的键值对.

然后,重写onRestoreInstanceState

@Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    }

或者不用,因为onCreate也可以。

在onRestoreInstanceState或者onCreate,利用已保存的状态Bundle并恢复活动状态。如果没有要恢复的状态信息,则Bundle传递给您的是null(这是第一次创建活动时的情况)。

不过,有些状态系统会帮我们做如:文本框(EditeText)中用户输入的数据,ListView滚动的位置等,这些view相关的状态系统都能够默认为我们恢复。可以查看view源码,和activity一样,每个view都有onSaveInstanceState方法和onRestoreInstanceState方法。

b

如果是大量数据,使用Fragment保持需要恢复的对象。

如果重新启动你的Activity需要恢复大量的数据,重新建立网络连接,或者执行其他的密集型操作,这样因为配置发生变化而完全重新启动可能会是一个慢的用户体验。并且,使用系统提供的onSaveIntanceState()的回调中,使用Bundle来完全恢复你Activity的状态是可能是不现实的(Bundle不是设计用来携带大量数据的(例如bitmap),并且Bundle中的数据必须能够被序列化和反序列化),这样会消耗大量的内存和导致配置变化缓慢。在这样的情况下,当你的Activity因为配置发生改变而重启,你可以通过保持一个Fragment来缓解重新启动带来的负担。这个Fragment可以包含你想要保持的有状态的对象的引用。

使用:在Fragment中调用setRetainInstance(true);保证Fragment的实例不会被销毁。熟悉Fragment的开发人员都知道,Fragment是依附于Activity的。当Activity销毁时,Fragment会随之销毁。而当Activity配置发生改变(如屏幕旋转)时候,旧的Activity会被销毁,然后重新生成一个新屏幕旋转状态下的Activity,自然而然的Fragment也会随之销毁后重新生成,而新生成的Fragment中的各个对象也与之前的那个Fragment不一样,伴随着他们的动作、事件也都不一样。所以,这时候如果想保持原来的Fragment中的一些对象,或者想保持他们的动作不被中断的话,就迫切的需要设置setRetainInstance(true)。进行了这样的操作后,一旦发生Activity重组现象,Fragment会跳过onDestroy直接进行onDetach(界面消失、对象还在),而Framgnet重组时候也会跳过onCreate,而onAttach和onActivityCreated还是会被调用。需要注意的是,要使用这种操作的Fragment不能加入backstack后退栈中。并且,被保存的Fragment实例不会保持太久,若长时间没有容器承载它,也会被系统回收掉的。

下面以一个加载图片为例子:

首先是Fragment:

这个fragment就是用来装数据的

package com.example.zhy_handle_runtime_change;
 
import android.app.Fragment;
import android.graphics.Bitmap;
import android.os.Bundle;
 
public class RetainedFragment extends Fragment
{
	
	private Bitmap data;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
	
		setRetainInstance(true);//要设置,保证实例不被销毁,那么数据就还在。
	}
 
	public void setData(Bitmap data)
	{
		this.data = data;
	}
 
	public Bitmap getData()
	{
		return data;
	}
}

注意,一定要在onCreate调用setRetainInstance(true);

然后是活动:

package com.example.zhy_handle_runtime_change;
 
import android.app.Activity;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
 
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.Volley;
 
public class FragmentRetainDataActivity extends Activity
{
 
	private static final String TAG = "FragmentRetainDataActivity";
	private RetainedFragment dataFragment;
	private Bitmap mBitmap;
 
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		FragmentManager fm = getFragmentManager();
		dataFragment = (RetainedFragment) fm.findFragmentByTag("data");

		if (dataFragment == null)
		{
			// add the fragment
			dataFragment = new RetainedFragment();
			fm.beginTransaction().add(dataFragment, "data").commit();
		}
		mBitmap = dataFragment.getData()
	}
 
	}
 
	@Override
	public void onPause()
	{
		super.onPause();
		dataFragment.setData(mBitmap);
	}
 
}

原理就是,fragment 只有一个,且不会被销毁,然后avtivity销毁的时候利用fragment来保存数据,重建的时候利用fragment得到数据。

下一是一个经典的场景:

假设当前Activity在onCreate中启动一个异步线程去夹在数据,当然为了给用户一个很好的体验,会有一个ProgressDialog,当数据加载完成,ProgressDialog消失,设置数据。

这里,如果在异步数据完成加载之后,旋转屏幕,使用上述a、b两种方法都不会很难,无非是保存数据和恢复数据。

但是,如果正在线程加载的时候,进行旋转,会存在以下问题:

a)此时数据没有完成加载,onCreate重新启动时,会再次启动线程;而上个线程可能还在运行,并且可能会更新已经不存在的控件,造成错误。

b)关闭ProgressDialog的代码在线程的onPostExecutez中,但是上个线程如果已经杀死,无法关闭之前ProgressDialog。

首先说一下探索过程:

起初,我认为此时旋转无非是再启动一次线程,并不会造成异常,我只要即使的在onDestroy里面关闭上一个异步任务就可以了。事实上,如果我关闭了,上一次的对话框会一直存在;如果我不关闭,但是activity是一定会被销毁的,对话框的dismiss也会出异常。真心很蛋疼,并且即使对话框关闭了,任务关闭了;用户旋转还是会造成重新创建任务,从头开始加载数据。

下面我们希望有一种解决方案:在加载数据时旋转屏幕,不会对加载任务进行中断,且对用户而言,等待框在加载完成之前都正常显示:

当然我们还使用Fragment进行数据保存,毕竟这是官方推荐的:

OtherRetainedFragment

package com.example.administrator.module;

import android.os.Bundle;
import android.support.v4.app.Fragment;

public class OtherRetainedFragment extends Fragment
{

    // data object we want to retain
    // 保存一个异步的任务
    private MyAsyncTask data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyAsyncTask data)
    {
        this.data = data;
    }

    public MyAsyncTask getData()
    {
        return data;
    }

}

MyAsyncTask

package com.example.administrator.module;

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.AsyncTask;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MyAsyncTask extends AsyncTask<Void, Void, Void>
{
    private MainActivity activity;
    /**
     * 是否完成
     */
    private boolean isCompleted;
    /**
     * 进度框
     */
    private LoadingDialog mLoadingDialog;
    private List<String> items;

    public MyAsyncTask(MainActivity activity)
    {
        this.activity = activity;
    }

    /**
     * 开始时,显示加载框
     */
    @Override
    protected void onPreExecute()
    {
        mLoadingDialog = new LoadingDialog();
        mLoadingDialog.show(activity.getSupportFragmentManager(), "LOADING");
    }

    /**
     * 加载数据
     */
    @Override
    protected Void doInBackground(Void... params)
    {
        items = loadingData();
        return null;
    }

    /**
     * 加载完成回调当前的Activity
     */
    @Override
    protected void onPostExecute(Void unused)
    {
        isCompleted = true;
        notifyActivityTaskCompleted();
        if (mLoadingDialog != null)
            mLoadingDialog.dismiss();
    }

    public List<String> getItems()
    {
        return items;
    }

    private List<String> loadingData()
    {
        try
        {
            Thread.sleep(5000);
        } catch (InterruptedException e)
        {
        }
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
                "onSaveInstanceState保存数据",
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
                "Spark"));
    }

    /**
     * 设置Activity,因为Activity会一直变化
     *
     * @param activity
     */
    public void setActivity(MainActivity activity)
    {
        // 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁
        if (activity == null)
        {
            mLoadingDialog.dismiss();
        }
        // 设置为当前的Activity
        this.activity = activity;
        // 开启一个与当前Activity绑定的等待框
        if (activity != null && !isCompleted)
        {
            mLoadingDialog = new LoadingDialog();
            mLoadingDialog.show(activity.getSupportFragmentManager(), "LOADING");
        }
        // 如果完成,通知Activity
        if (isCompleted)
        {
            notifyActivityTaskCompleted();
        }
    }

    private void notifyActivityTaskCompleted()
    {
        if (null != activity)
        {
            activity.onTaskCompleted();
        }
    }

}

LoadingDialog

package com.example.administrator.module;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;

import java.util.Objects;

/**
 * 等待弹框
 */
public class LoadingDialog extends DialogFragment {

    private String msg = "正在加载";
    private boolean onTouchOutside = true;
    private TextView textView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        getDialog().setCanceledOnTouchOutside(onTouchOutside);
        View loadingView = inflater.inflate(R.layout.loading, container);
        textView= loadingView.findViewById(R.id.textView);
        textView.setText(msg);
        return loadingView;
    }

}

LoadingDialog的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:background="@color/colorPrimary"
    android:gravity="center">

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:indeterminateTintMode="src_atop"
        android:indeterminateTint="@color/white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView"
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@color/white"
        tools:text="正在加载" />
</LinearLayout>

**

主Activity:

package com.example.administrator.module;


import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.util.List;


public class MainActivity extends AppCompatActivity {



    private static final String TAG = "zjs";

    private List<String> mDatas;
    private OtherRetainedFragment dataFragment;
    private MyAsyncTask mMyTask;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");

        // find the retained fragment on activity restarts
       FragmentManager fm= getSupportFragmentManager();
        dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");

        // create the fragment and data the first time
        if (dataFragment == null)
        {
            // add the fragment
            dataFragment = new OtherRetainedFragment();
            fm.beginTransaction().add(dataFragment, "data").commit();
        }
        mMyTask = dataFragment.getData();
        if (mMyTask != null)
        {
            mMyTask.setActivity(this);
        } else
        {
            mMyTask = new MyAsyncTask(this);
            dataFragment.setData(mMyTask);
            mMyTask.execute();
        }
        // the data is available in dataFragment.getData()
    }


    @Override
    protected void onPause() {
        super.onPause();
        mMyTask.setActivity(null);//把上个activity销毁防止内存泄漏
        Log.d("zjs", "onPause: ");
    }


    /**
     * 回调
     */
    public void onTaskCompleted()
    {
        mDatas = mMyTask.getItems();
        Log.d("zjs", "onTaskCompleted: "+mDatas);
    }

}



异步任务中,管理一个对话框,当开始下载前,进度框显示,下载结束进度框消失,并为Activity提供回调。当然了,运行过程中Activity不断的重启,我们也提供了setActivity方法,onPause时,会setActivity(null)防止内存泄漏,同时我们也会关闭与其绑定的加载框;当onCreate传入新的Activity时,我们会在再次打开一个加载框,当然了因为屏幕的旋转并不影响加载的数据,所有后台的数据一直继续在加载。

讲Bundle

**
Bundle只是一个信息的载体,内部其实就是维护了一个Map<String,Object>。
Bundle经常使用在Activity之间或者线程间传递数据,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。
当Bundle传递的是对象或对象数组时,必须实现Serializable或Parcelable接口。
Bundle提供了各种常用类型的putXxx()/getXxx()方法,用于读写基本类型的数据。(各种方法可以查看API)

在activity间传递信息

Bundle bundle = new Bundle();        //得到bundle对象  
bundle.putString("sff", "value值");  //key-"sff",通过key得到value-"value值"(String型)  
bundle.putInt("iff", 175);           //key-"iff",value-175  
intent.putExtras(bundle);            //通过intent将bundle传到另个Activity  
startActivity(intent);

在跳转活动种:

Bundle bundle = this.getIntent().getExtras(); //读取intent的数据给bundle对象     
String str1 = bundle.getString("sff"); //通过key得到value     
int int1 = bundle.getInt("iff"); 

**

启动Activity

**

开启活动有两种

第一种 startActivity:如果A启动B活动并需要传数据到B活动

Intent intent = new Intent(this, SignInActivity.class);
Bundle bundle = new Bundle();        //得到bundle对象  
bundle.putString("sff", "value值");  //key-"sff",通过key得到value-"value值"(String型)  
bundle.putInt("iff", 175);           //key-"iff",value-175  
intent.putExtras(bundle);            //通过intent将bundle传到另个Activity  
startActivity(intent);

也可以不要用bundle ,直接利用intent放数据。

 intent.putExtra("key","data");
 intent.getStringExtra("key");

第二种startActivityForResult
如果A启动B活动并需要传数据到B活动,并且B活动销毁的时候想传数据给A。

A活动中的方法:

(1)startActivityForResult(Intent intent, int requestCode);

第一个参数:一个Intent对象,A跳到B要传给B的数据。

第二个参数:请求号码,也就是标识A的号码

(2)onActivityResult(int requestCode, int resultCode, Intent data)

第一个参数:A活动的请求号码。
  第二个参数:这整数resultCode是由子Activity通过其setResult()方法返回。也就是B活动设置的标识B活动的号码,适用于多个activity都返回数据时,来标识到底是哪一个activity返回的值。

第三个参数:B活动退出后返回给A的数据

B活动中的方法:

setResult(int resultCode, Intent data)

第一个参数:B活动的号码

第二个参数:B要传给A的数据

=======================================================================================

下边这个例子看一看:

1.页面跳转的时候不采用startActivity(intent) ,而采用startActivityForResult(intent, REQUEST_CODE)。

public static int REQUEST_CODE = 1;
Intent intent = new Intent(this, OtherActivity.class);
intent.putExtra("text", editText.getText().toString());//添加数据 key为 text,value 为edittext中输入的String
startActivityForResult(intent, REQUEST_CODE);//这里采用startActivityForResult来做跳转,启动activity ,并传入requestCode请求码 请求码可以自己定义 我这边声明的是1(大于等于0即可),你也可以直接写成startActivityForResult(intent, 1)

2.重写onActivityResult方法,用来接收OtherActivity回传的数据,当OtherActivity执行finish()方法后调用。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
 
    if(requestCode == REQUEST_CODE && resultCode == OtherActivity.RESULT_CODE) {//resultCode是返回的结果码 在Other中通过setResult()方法返回给第一个activity
        editText.setText(data.getStringExtra("text"));//获取返回的数据,转成String类型 显示到edittext控件上
    }
}

3.在OtherActivity中回传数据时采用setResult方法,并且之后要调用finish方法。

public static int RESULT_CODE =2;
Intent intent = new Intent();
intent.putExtra("text", editText.getText().toString());//根据key “text” 把获取OtherActivity中的edittext中的String回传给第一个activity
setResult(RESULT_CODE, intent);//回传结果码,大于等于0即可,值随意
finish();//setResult之后必须调用finish方法

4.这里finish执行完 OtherActivity就关闭 开始执行步骤2中的onActivityResult方法

温馨提示:

调用setResult()方法必须在finish()之前。

**

Activity的启动模式

**

众所周知当我们多次启动同一个Activity时,系统会创建多个实例,并把它们按照先进后出的原则一一放入任务栈中,当我们按back键时,就会有一个activity从任务栈顶移除,重复下去,直到任务栈为空,系统就会回收这个任务栈。

一个应用他默认是所有的activity都放在同一个任务栈的,每个任务栈的栈id 是同一个id
并且任务栈的 taskAffinity 默认是当前应用的包名。

注意,判断activity是否处于同一个任务栈,是通过activity 所属的 任务栈的id,来判断,
并不是通过 activity的 taskAffinity ,不同的 taskAffinity 他也是会处于同一个任务栈

taskAffinity并不是区分不同任务栈的标准,taskId 才是

可通过 adb shell dumpsys activity 查看当前设备的 有什么任务栈,以及什么acitivity
在这里插入图片描述
从上面的图就可以看出,

MainActivity1 ,MainActivity2,MainActivity3
各自的 taskAffinity 是

com.android.myapplication.MainActivity
com.android.myapplication.MainActivity2
com.android.myapplication.MainActivity3
但是他们都属于同一个任务栈 id 1027 .

可以通过代码 查看对应的activity所处的任务栈和 taskAffinity

class MainActivity : AppCompatActivity() {

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityMainBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)


        activityMainBinding .text.setOnClickListener {

            val intent = Intent(this, MainActivity2::class.java)

            startActivity(intent)
        }


        val taskId = this.taskId
        Log.d("zjs", "MainActivity  is in task with id: $taskId")

        
        val taskInfo = this.packageManager.getActivityInfo(this.componentName, PackageManager.GET_META_DATA)
        val taskAffinity = taskInfo.taskAffinity
        Log.d("zjs", "MainActivity  belongs to task stack: $taskAffinity")
        

    }

Android提供四种启动模式

standard、singleTop、singleTask、singleInstance

standard-默认模式

这个模式是默认的启动模式,即标准模式,在不指定启动模式的前提下,系统默认使用该模式启动Activity,每次启动一个Activity都会重写创建一个新的实例,不管这个实例存不存在,这个Activity它的onCreate(),onStart(),onResume()方法都会被调用。这种模式下,谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中,也就是说,如果A的任务栈号码是1,那么在A启动B的时候,B的任务栈也会跟A一样是1,也就是同一组,而多次创建就会多个实例。 谁启动了我,我就处于谁的任务栈,就算是其他应用的一个任务栈启动了我,我也会处于其他应用的一个任务栈

<activity android:name=".standard.StandardActivity" android:launchMode="standard" > 

对于standard模式,android:launchMode可以不进行声明,因为默认就是standard。

singleTop-栈顶复用模式

这个模式下,如果新的activity已经位于栈顶,那么这个Activity不会被重写创建,Activity它的onCreate(),onStart()方法不会被调用,因为它并没有发生改变。,不过它的onNewIntent方法会被调用,通过此方法的参数我们可以去除当前请求的信息。如果栈顶不存在该Activity的实例,则情况与standard模式相同。也就是如果是在栈顶,那么多次开启也只会是调用同一个的onNewIntent方法,如果不是栈顶,而是栈内其他位置,那么会重新创建个新的实例活动开启。 也是跟 standard一样, 谁启动了我,我就处于谁的任务栈,就算是其他应用的一个任务栈启动了我,我也会处于其他应用的一个任务栈。

配置形式:

<activity android:name=".singletop.SingleTopActivity" android:launchMode="singleTop">

注意 在 standard和singleTop的activity 去指定 taskAffinity 是没有任何意义的
他只是把 activity 的taskAffinity,变了,但是这个activity属于哪个任务栈,完全取决于,谁启动了它。
不过如果是 singleInstance的 activity 启动了他,那么会新创建一个任务栈,这个点下面会说。
也就是哪个activity的任务栈启用了它,它就跟随哪一组任务栈,
虽然你为 avtivity 指定了taskAffinity, taskAffinity改了,但是activity的任务栈还是原来启动它的activity
判断是否相同任务栈也是看的是 taskId,而不是taskAffinity,
所以对于 standard和singleTop的activity,taskAffinity属性是没有任何意义的。

singleTask-栈内复用模式
taskAffinity只有在跟 singleTask才有意义,其余模式都是没有意义的。

singleTask启动模式启动Activity时,
首先会根据taskAffinity去寻找当前是否存在一个对应名字的任务栈,

如果你没有指定这个taskAffinity,那么这个acitivity的任务栈就是当前应用默认的任务栈
首先会判断栈中有没activity 实例,如果没有那么新建实例放入栈顶
如果有实例,那么直接调用这个实例,不会新建一个,只会调用Activity实例的onNewIntent方法 并且把该activity上的其他acitivity全部移除出栈
然后把改activity放栈顶

如果你指定了一个taskAffinity,那么
如果taskAffinity 指定的任务栈 不存在,则会创建一个新的任务栈
,并创建新的Activity实例入栈到新创建的Task中去
如果taskAffinity 指定的任务栈 已经存在,查找该任务栈中是否存在该Activity实例,
如果存在activity实例,则 不会再次创建新的activity实例,只会调用Activity实例的onNewIntent方法
并且会 将它上面的Activity实例都出栈,让这个activity 置于栈顶,
如果不存在该activiytr实例,那么新建实例放入栈顶

<activity android:name=".ActivitySingleTask" android:launchMode="singleTask" android:taskAffinity="com.castiel.demo.singletask"/>

如下3个activity指定都是同一个 android:taskAffinity。
所以启动他们的时候,并不会创建新的任务栈,他们都是属于1045,任务栈
在这里插入图片描述
而如下, 如果他们指定的 不同的 android:taskAffinity,那么他们开启他们的时候,就会创建一个个新的任务栈
在这里插入图片描述

singleInstance-全局唯一模式
singleInstance跟taskAffinity也没有意义,
不管你指定不指定,或者指定是否同一个。他都会为你的activity 重新创建一个新的任务栈

指定不同 taskAffinity,开启会新建任务栈
在这里插入图片描述
指定相同taskAffinity,开启会新建任务栈
在这里插入图片描述

不指定taskAffinity,开启会新建任务栈
在这里插入图片描述
根据这3附图也可以知道

singleInstance跟taskAffinity也没有意义,也没有关系,
不管你指定不指定,或者指定是否同一个。他都会为你的activity 重新创建一个新的任务栈,
只不过你指定了 taskAffinity,activity的taskAffinity名字跟着换了而已

所以综上就是,taskAffinity值只作用在singleTask才有意义,他代表,如果存在指定的 taskAffinity的任务栈,那么不会创建新的任务栈,不存在指定的 taskAffinity的任务栈,才会去创建新的任务栈

而对于singleInstance, 跟taskAffinity没有关系,创建不创建新的任务栈,取决于要开启的任务栈是否存在,
不存在或者被系统销毁了,开启他肯定会创建一个新的任务栈,并且任务栈只可以有它 自己,
而由于站内复用,如果这个任务栈还没有被系统销毁,任务栈还在,那么就重复用刚创建的实例,也就是会调用它 的onNewIntent

<activity android:name=".singleinstance.SingleInstanceActivity" android:launchMode="singleInstance" >


我们可以做个实例,3个 singleInstance 的acitivity,A 开启B,B开启C,然后再继续C开启A.

看下,是不是会重用一开始的A的任务栈,只调用它的onNewIntent,而不是再重新创建新任务栈新实例

如图 A会复用,调用onNewIntent
在这里插入图片描述

也就是singleInstance 的acitivity的任务栈还在的时候,开启它就还是原来的它,如果任务栈被销毁了,会重新建一个任务栈。

前面说了standard 和 singTop,都是谁开启我,我就属于谁的任务栈,但是,如果A activity 是 singleInstance
B activity 是 standard 和 singTop, 这个时候如果 A 启动 B,

A 说它 的任务栈只有自己没有其他人,而B 又说规则是谁启动我,我属于谁,
这个时候从实例可以看出,

在这里插入图片描述

当开启A的时候,他的任务栈是1063.但是它开启B的时候,B不会属于A的1063,而是新建一个任务栈1064,
而因为C是singleInstance,所以又新开一个1065.

而如果C是 standard 和 singTop,,那么不会开启新的,还是会是1064.

而如果是 A 是 standard, B 是 singleInstance,C是 standard,
那么C会跟A是同一个任务栈,如下日志可看出

在这里插入图片描述

**

那么 acitivity的任务栈什么时候会被系统销毁

**。

当3个Activity MainActivity1 MainActivity2 MainActivity3 都是 standard模式,依次跳转
在MainActivity3上去回到桌面,你不会发现有哪个Activity被销毁,但是
当你换其他的启动模式,或者,添不添加 taskAffinity,就会出现不同的效果

有下面一种情况,

当一个应用,进入第一个页面,MainActivity1是 singleInstance ,MainActivity2 standard, 在MainActivity2页面回退到桌面,会出现 MainActivity1 被系统销毁,包括任务栈也被销毁的情况,接着再点到应用,进入第一个页面还是MainActivity1,并且它是重新创建了的,任务栈也是重新创建
但是,MainActivity2没有销毁,他的实例还在,所以当你从桌面跳转到 MainActivity1,MainActivity1重建,
在MainActivity1跳转到 MainActivity2的,MainActivity2还是原来的实例,不会重新 MainActivity2

在这里插入图片描述

但是如果你为 MainActivity2 添加 android:taskAffinity=“com.android.myapplication.MainActivity2”

当在MainActivity2页面回退到桌面,MainActivity1 就不会销毁。

反过来,下面一种情况,

当一个应用,进入第一个页面,MainActivity1是 standard,然后MainActivity1 调转到MainActivity2 singleInstance, 这个时候,在MainActivity2页面回退到桌面,你会发现 MainActivity1 也会被销毁

而当出现3个acitivity的情况:
MainActivity1是 standard, MainActivity2 singleInstance,MainActivity3是 standard
然后在 MainActivity3回到桌面,你会发现 MainActivity1跟MainActivity3是同一个任务栈,回到桌面销毁的是
MainActivity2

出现的情况会非常多种,上面只是一点点皮毛,目前并不能有个好的总结,也不知道有哪方面的知识有说明这一块的原理。
只是不过有一点,当你的应用所有Activity 里面的某个Activity 出现了singleInstance的 ,你就应该注意下,
会有回到桌面,某个Activity被销毁,而有的就不会的情况。

**

Activity的 属性android:allowTaskReparenting=“true”

**

当为一个Activity添加 allowTaskReparenting属性的作用是这样的

当个比方,A应用 调用了B 应用 一个不是B应用桌面点击进入的activity b,而这个 b activity 添加了

android:allowTaskReparenting=“true” 属性

那么当 在 A应用 调用启动了B 应用 activity b,然后回到桌面,然后在桌面点击 B应用,你会发现,进入
B应用的第一个页面并不是 B应用 的主activity,而是刚刚 A应用 调用启动了B 应用的 activity b。
这就是为 B 应用的 activity b添加 android:allowTaskReparenting=“true” 的作用

可以这么理解

在 A应用 调用启动了B 应用 activity b, 这个时候 activity b 还是属于 A应用 的任务栈的,
而当你打开 B 应用的时候, activity b 就会转移到 B 应用的任务栈中,所以就放在了栈顶显示。

**`

非activity的context 去startactivity`

**
注意在,在android中,开启activity可以用context.startactivity(intent)
但是如果这个context,他并不是一个activity,有可能是applicationContext,或者server等
那么当你context.startactivity(intent) 的时候一定会报错

也就是如下的代码

class MainActivity : AppCompatActivity() {

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityMainBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)


        activityMainBinding .text.setOnClickListener {

            val intent = Intent(this, MainActivity2::class.java)
            //用applicationContext去开启,而不是this activity,报错
            applicationContext.startActivity(intent)
        }

报错如下

在这里插入图片描述

原因就是
你的context,并不是activity。

修改就是 为这个intent 另外起一个task,不然就会报错崩溃,
也就是

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startactivity(intent)

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 就是在AndroidManifest.xml 标记 singTask的动态代码

你会说,我不写这个 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
而是在 AndroidManifest.xml 为这个待启动的acitivity申明singTask或者 singInstance,
也是不可以依旧会报错,
所以就是不管你的acitivity 在 在 AndroidManifest.xml 是什么模式,都要加上 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
并且优先级是 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);,生效,
而 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 就是 标记 singTask的动态代码

flags 还有 Intent.FLAG_ACTIVITY_SINGLE_TOP ,这个就是对应,singTop,

而对于standard 和singInstace ,并没有对应动态代码对应。只有上面两个 singTop singTask

并且如果你的代码 , 为一个acitivity在 AndroidManifest.xml标记了 哪个模式,并且动态代码也写了哪个模式,
这种情况,是 动态代码优先级高,以动态代码生效。

Activity与Fragment生命周期关系

创建过程:
在这里插入图片描述
销毁过程:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值