Activity与Fragment

Activity的启动模式(android:launchMode)

  1. standard 标准模式

    系统默认模式,每次启动ACtivity,不管该Activity的实例是否存在,都会创建一个新的实例。该Activity与启动它的Activity属于同一个任务栈

    注意:applicationContext不可启动standard模式的Activity。因为非Activity的context类型没有任务栈,那么如果要启动该模式的Activity,那该Activity无法进入任务栈,那么该Activity就不属于任何任务栈。

  2. singleTop

    栈顶复用模式。如果将要启动的Activity处于任务栈的顶部,那么该Activity会直接复用栈顶的实例。因为该Activity的实例已经存在,那么便不会执行onCreate和onStart,但会调用onNewIntent方法。在onNewIntent中可以取出这次请求的信息。但是如果该Activity不在任务栈的顶部,则会新建该Activity的实例。

  3. singleTask

    栈内复用模式。如果即将启动的Activity在某个任务栈中存在,则直接复用该Activity实例。再次过程中,只会执行onNewIntnent。具体而言,当存在该Activity所需要的任务栈,那么在该任务栈中查找该Activity的实例,如果查找到则直接复用,否则新建该Activity的实例并入栈。如果不存在该Activity的任务栈,则新建一个任务栈,然后新建Activity实例,并入栈。

    **注意:**当栈内复用时,那么将会把栈内该Activity之上的所有实例出栈。

  4. singleInstance

    栈内单例模式。如果某个Activity为该模式,那么该Activity只能单独的存在于某一个任务栈中,其他特征与singleTask相同。

注意: 上述任务栈是由taskAffinity属性来判定的,默认情况下所有的Activity的taskAffinity属性都是应用包名。如果想指定某个Activity所在的任务栈,修改该属性即可。该属性仅仅在于singleTask或allowTaskReparenting配合使用时才有意义。

附:FLAF vs android:launchMode

FLAG_ACTIVITY_NEW_TASK:singleTask

FLAG_ACTIVITY_CLEAR_TOP: 如果该Activity在当前任务栈中存在实体,那么销毁栈中在其之上的Activity实例,然后创建一个新的实例添加到栈顶。

FLAG_ACTIVITY_SINGLE_TOP: singleTop

总结:使用FLAG则不支持singleInstance,使用android:launchMode则不支持CLEAR_TOP

Task字段说明

  1. Android:allowTaskReparenting

    该属性用来标记当Activity退居后台之后,是否能从启动它的Activity所在的任务栈移动到与它有相同taskAffinity的任务栈。eg:在应用中页面A启动了浏览器,那么此时打开的浏览器页面B与A是属于同一个任务栈的,当B退居后台之后,B就会移动到浏览器所在的任务栈中,并且处于该栈的顶部,所以当再次打开浏览器的时候,显示的是页面B。

  2. android:allowRetainTaskState

    该属性用来标记是否能够保持原有的状态,但是该属性仅仅只对根Activity起作用(所谓根Activity一般指app的主页面)。

  3. android:clearTaskOnLaunch

    如果该属性为true,那么当启动该Activity时,便会清除该Activity所在任务栈的其他所有Activity。

  4. android:finishOnTaskLaunch

    如果设置该属性为true,那么点击home键回到屏幕之后,再次点击应用图标进入引用之后,系统自动销毁该Activity。

  5. android:alwaysRetainTaskState

    如果当前任务栈的根Activity的该属性设置为true,在该任务栈stop之后仍然保持该任务栈中所有Activity的状态。

intent和intent-filter

intent是一个消息传递对象,用来促进组件之间的通信,基本用例主要有:

  1. 启动Activity:
  2. 启动Service
  3. 传递Broadcast

intent主要分为两类:

  1. 显式intent:按类名来显示指定要启动的组件。一般情况下,我们仅在自己的应用程序中使用显式intent,因为仅在自己的应用程序中我们才知道要启动组件的完整类名。
  2. 隐式intent:不会指定特定的组件,而是说明要执行的操作,系统查找可以执行此操作的组件并启动它。通常情况下,应用程序中通过隐式intent来提供全局通用服务,比如阅读pdf文件。

警告:为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会抛出异常。

不管是显式intent还是隐式intent,intent中都包含了启动组建的信息,包括以下几种:

  • 组件名称:可选项,在显式intent中必须制定该项。换言之,如果在intent中没有说明目标组件名称,那么该intent就是隐式intent。如果是隐式intent,那么系统将根据intent的其他信息来判断启动哪个组件。
  • ACTION:字符串类型,说明目标组件所要执行的操作。系统提供了一些通用的操作: * ACTION_VIEW:目标组件会提供一些“查看服务”,例如查看图库、查看位置等。 * ACTION_SEND:也叫作“共享操作”,所以我们清楚地知道:如果目标组件可接收其他应用程序的共享数据,则将目标组件的ACTION设置为ACTION_SEND
  • Data:引用带操组的数据和/或该数据的MIME类型。提供的数据类型通常由ACTION来确定,例如ACTION为ACTION_EDIT,那么数据应该是待编辑的文档的uri。 * 创建 Intent 时,除了指定 URI 以外,指定数据类型(其 MIME 类型)往往也很重要。例如,能够显示图像的Activity可能无法播放音频文件,即便 URI 格式十分类似时也是如此。因此,指定数据的 MIME 类型有助于 Android 系统找到接收 Intent 的最佳组件。但有时,MIME 类型可以从 URI 中推断得出,特别当数据是 content: URI 时尤其如此。这表明数据位于设备中,且由 ContentProvider 控制,这使得数据 MIME 类型对系统可见。要仅设置数据 URI,请调用 setData()。要仅设置 MIME 类型,请调用 setType()。如有必要,您可以使用 setDataAndType() 同时显式设置二者。
  • 警告:若要同时设置 URI 和 MIME 类型,请勿调用 setData() 和 setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。
  • Category:指定intent的类别,一般情况下所有的Activity默认为CATEGORY_DEFAULT,不需要指定。如果某个组件要支持隐式intent,则必须添加一个category(如果不支持其他特殊的category,例如CATEGORY_BROWSABLE,那么必须添加CATEGORY_DEFAULT),否则不起作用。

以上四类intent属性表示了intent的既定特征,通过这些特征,系统就可以分标出要启动的目标组件。intent还有一些不影响其如何解析的属性,如下:

  • Extra:携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的附加数据。 您可以使用各种 putExtra() 方法添加附加数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有附加数据的 Bundle 对象,然后使用 putExtras() 将 Bundle 插入 Intent 中。 例如,使用 ACTION_SEND 创建用于发送电子邮件的 Intent 时,可以使用 EXTRA_EMAIL 键指定“目标”收件人,并使用 EXTRA_SUBJECT 键指定“主题”。Intent 类将为标准化的数据类型指定多个 EXTRA_* 常量。如需声明自己的附加数据 键(对于应用接收的 Intent ),请确保将应用的软件包名称作为前缀。例如:
```
	static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

```
复制代码
  • Flag:标志可以指示 Android 系统如何启动 Activity

显式intent示例

例如,如果在应用中构建了一个名为 DownloadService、旨在从 Web 中下载文件的服务,则可使用以下代码启动该服务:

	
	// Executed in an Activity, so 'this' is the Context
	// The fileUrl is a string URL, such as "http://www.example.com/image.png"
	Intent downloadIntent = new Intent(this, DownloadService.class);
	downloadIntent.setData(Uri.parse(fileUrl));
	startService(downloadIntent);

复制代码

隐式intent示例

警告:用户可能没有任何应用处理您发送到 startActivity() 的隐式 Intent。如果出现这种情况,则调用将会失败,且应用会崩溃。要验证 Activity 是否会接收 Intent,请对 Intent 对象调用 resolveActivity()。如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()。如果结果为空,则不应使用该 Intent。如有可能,您应禁用发出该 Intent 的功能。

PendingIntent

pendingIntent的目的是授权外部应用使用pendingIntent中包含的intent。 用例场景:通知、桌面小组件、Alarm

Activity的各个方法及调用时机

生命周期

onPause() 与 onResume()

在正常使用应用的过程中,前台Activity有时会被因为某些原因导致Activity暂停的可视组件阻挡。 例如,当半透明Activity打开时(比如对话框样式中的Activity),上一个Activity会暂停。 只要Activity仍然部分可见但目前又未处于焦点之中,它会一直暂停。但是,一旦Activity完全被阻挡并且不可见,它便停止,进入onStop()。

当Activity进入暂停状态时,系统会对Activity调用onPause()方法,通过该方法,您可以停止不应在暂停状态下继续进行的操作(比如视频)或保留任何应该永久保存的信息,以防用户离开应用。如果用户从暂停状态返回到Activity,系统会重新开始该Activity并调用 onResume() 方法。

当系统为Activity调用 onPause() 时,它从技术角度看意味着Activity仍然处于部分可见状态,但往往说明用户即将离开Activity并且它很快就要进入“停止”状态。 您通常应使用 onPause() 回调:

  • 停止动画或其他可能消耗 CPU 的进行之中的操作。
  • 提交未保存的更改,但仅当用户离开时希望永久性保存此类更改(比如电子邮件草稿)。
  • 释放系统资源,比如广播接收器、传感器手柄(比如 GPS) 或当您的Activity暂停且用户不需要它们时仍然可能影响电池寿命的任何其他资源。

例如,如果您的应用使用 Camera, onPause() 方法是释放它的好位置。但您应避免在 onPause() 期间执行 CPU 密集型工作,比如向数据库写入信息,因为这会拖慢向下一Activity过渡的过程(您应改为在 onStop()期间执行高负载关机操作。 在此说明一下,当从ActivityA切换到ActivityB时,两个Activity各个方法的执行过程为:

  1. Activity A 的 onPause() 方法执行。
  2. Activity B 的 onCreate()、onStart() 和 onResume() 方法依次执行。(Activity B 现在具有用户焦点。)
  3. 然后,如果 Activity A 在屏幕上不再可见,则其 onStop() 方法执行。

当用户从“暂停”状态继续您的Activity时,系统会调用 onResume() 方法。

请注意,每当您的Activity进入前台时系统便会调用此方法,包括它初次创建之时。 同样地,您应实现onResume() 初始化您在 onPause() 期间释放的组件并且执行每当Activity进入“继续”状态时必须进行的任何其他初始化操作(比如开始动画和初始化只在Activity具有用户焦点时使用的组件)。

onResume() 的以下示例对应于以上的 onPause() 示例,因此它初始化Activity暂停时释放的照相机。

onStop() 与 onStart() 与 onRestart();

正确停止和重新开始Activity是Activity生命周期中的重要过程,其可确保您的用户知晓应用始终保持Activity状态并且不会丢失进度。有几种Activity停止和重新开始的关键场景:

  • 用户打开“最近应用”窗口并从您的应用切换到另一个应用。当前位于前台的您的应用中的Activity将停止。 如果用户从主屏幕启动器图标或“最近应用”窗口返回到您的应用,Activity会重新开始。
  • 用户切换到新的Activity。当第二个Activity创建好后,当前Activity便停止。 如果用户之后按了返回按钮,第一个Activity会重新开始。
  • 用户在其手机上使用您的应用的同时接听来电。

当Activity调用到onStop()时,它不在可见,并且应该释放此时所有不需要的资源。一旦Activity处于停止状态,那么它可能会因为需要恢复系统内存还被销毁。 在极端情况下,系统可能会仅终止应用进程,而不会调用Activity的最终 onDestroy() 回调,因此您使用 onStop() 释放可能泄露内存的资源非常重要。

尽管 onPause() 方法在 onStop()之前调用,您应使用 onStop() 执行更大、占用更多 CPU 的关闭操作,比如向数据库写入信息。

Fragment的各个方法及调用时机

FragmentManager

FragmentPagerAdapter vs FragmentStatePagerAdapter

相信大家都使用过viewPager+PagerAdapter的方法来进行页面切换,其中PageAdapter提供page,viewPager显示page。当page的内容发生变化时,PagerAdapter将此变化通知给ViewPager,也就是说PagerAdapter负责创建Page、管理page,ViewPager负责显示page。但是因为各种各样的需求,要求PagerAdapter提供对page不同的管理方式,比如当page较少时,从page1切换到page2,再切换到page3时,要求不得将page1~page2销毁,这样当用户返回page1时,page1的内容会立马展示给用户;当page较多时,考虑到android系统对单一应用内存的限制,就不可能将所有的page保存起来,接下来我们详细了解一下系统为我们提供的各种PagerAdapter。

PagerAdapter

该类是page最简单的管理类,如果继承自该类,至少需要实现 instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject()。

  • getItemPosition() * 该函数用以返回给定对象的位置,给定对象是由 instantiateItem() 的返回值 在 ViewPager.dataSetChanged() 中将对该函数的返回值进行判断,以决定是否最终触发 PagerAdapter.instantiateItem() 函数。 在 PagerAdapter 中的实现是直接传回 POSITION_UNCHANGED。如果该函数不被重载,则会一直返回 POSITION_UNCHANGED,从而导致 ViewPager.dataSetChanged() 被调用时,认为不必触发 PagerAdapter.instantiateItem()。很多人因为没有重载该函数,而导致调用 PagerAdapter.notifyDataSetChanged() 后,什么都没有发生。
  • public Object instantiateItem(View container, int position) * 在每次 ViewPager 需要一个用以显示的 Object 的时候,该函数都会被 ViewPager.addNewItem() 调用。
  • public void notifyDataSetChanged() * 在数据集发生变化的时候,一般 Activity 会调用 PagerAdapter.notifyDataSetChanged(),以通知 PagerAdapter,而 PagerAdapter 则会通知在自己这里注册过的所有 DataSetObserver。其中之一就是在 ViewPager.setAdapter() 中注册过的 PageObserver。PageObserver 则进而调用 ViewPager.dataSetChanged(),从而导致 ViewPager 开始触发更新其内含 View 的操作。

FragmentPagerAdapter

FragmentPagerAdapter 继承自 PagerAdapter。相比通用的 PagerAdapter,该类更专注于每一页均为 Fragment 的情况。如文档所述,该类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的那种;如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter。FragmentPagerAdapter 重载实现了几个必须的函数,因此来自 PagerAdapter 的函数,我们只需要实现 getCount(),即可。且,由于 FragmentPagerAdapter.instantiateItem() 的实现中,调用了一个新增的虚函数 getItem(),因此,我们还至少需要实现一个 getItem()。因此,总体上来说,相对于继承自 PagerAdapter,更方便一些。

  • getItem() * 该类中新增的一个虚函数。函数的目的为生成新的 Fragment 对象。重载该函数时需要注意这一点。在需要时,该函数将被 instantiateItem() 所调用。如果需要向 Fragment 对象传递相对静态的数据时,我们一般通过 Fragment.setArguments() 来进行,这部分代码应当放到 getItem()。它们只会在新生成 Fragment 对象时执行一遍。如果需要在生成 Fragment 对象后,将数据集里面一些动态的数据传递给该 Fragment,那么,这部分代码不适合放到 getItem() 中。因为当数据集发生变化时,往往对应的 Fragment 已经生成,如果传递数据部分代码放到了 getItem() 中,这部分代码将不会被调用。这也是为什么很多人发现调用 PagerAdapter.notifyDataSetChanged() 后,getItem() 没有被调用的一个原因。
  • instantiateItem() * 函数中判断一下要生成的 Fragment 是否已经生成过了,如果生成过了,就使用旧的,旧的将被 Fragment.attach();如果没有,就调用 getItem() 生成一个新的,新的对象将被 FragmentTransation.add()。

FragmentPagerAdapter 会将所有生成的 Fragment 对象通过 FragmentManager 保存起来备用,以后需要该 Fragment 时,都会从 FragmentManager 读取,而不会再次调用 getItem() 方法。 如果需要在生成 Fragment 对象后,将数据集中的一些数据传递给该 Fragment,这部分代码应该放到这个函数的重载里。在我们继承的子类中,重载该函数,并调用 FragmentPagerAdapter.instantiateItem() 取得该函数返回 Fragment 对象,然后,我们该 Fragment 对象中对应的方法,将数据传递过去,然后返回该对象。 否则,如果将这部分传递数据的代码放到 getItem()中,在 PagerAdapter.notifyDataSetChanged() 后,这部分数据设置代码将不会被调用。

  • destroyItem() * 该函数被调用后,会对 Fragment 进行 FragmentTransaction.detach()。这里不是 remove(),只是 detach(),因此 Fragment 还在 FragmentManager 管理中,Fragment 所占用的资源不会被释放。

FragmentStatePagerAdapter

FragmentStatePagerAdapter 和前面的 FragmentPagerAdapter 一样,是继承子 PagerAdapter。但是,和 FragmentPagerAdapter 不一样的是,正如其类名中的 'State' 所表明的含义一样,该 PagerAdapter 的实现将只保留当前页面,当页面离开视线后,就会被消除,释放其资源;而在页面需要显示时,生成新的页面(就像 ListView 的实现一样)。这么实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。

  • getItem() * 一个该类中新增的虚函数。函数的目的为生成新的 Fragment 对象。Fragment.setArguments() 这种只会在新建 Fragment 时执行一次的参数传递代码,可以放在这里。由于 FragmentStatePagerAdapter.instantiateItem() 在大多数情况下,都将调用 getItem() 来生成新的对象,因此如果在该函数中放置与数据集相关的 setter 代码,基本上都可以在 instantiateItem() 被调用时执行,但这和设计意图不符。毕竟还有部分可能是不会调用 getItem() 的。因此这部分代码应该放到 instantiateItem() 中。
  • instantiateItem() * 除非碰到 FragmentManager 刚好从 SavedState 中恢复了对应的 Fragment 的情况外,该函数将会调用 getItem() 函数,生成新的 Fragment 对象。新的对象将被 FragmentTransaction.add()。FragmentStatePagerAdapter 就是通过这种方式,每次都创建一个新的 Fragment,而在不用后就立刻释放其资源,来达到节省内存占用的目的的。
  • destroyItem() * 将 Fragment 移除,即调用 FragmentTransaction.remove(),并释放其资源。

Activity, ViewPager, Fragment实现懒加载

系统配置变化导致的Activity变化

1. Device Configurations

Orientation, Keyboard, Language.

2. 原理简介及解决方案

一般情况下,当Device Configuration 在Application运行时发生变化,那么系统会自动重启该Activity(此时先onSaveInstance保存数据,然后执行onDestroy,最后执行onCreate)。

所以我们必须在Activity销毁之前使用onSaveInsance保存数据,在onCreate或者onRestoreInsanceState中回复数据,以此来提供良好的用户体验。但是有时,我们需要保存大量的数据,遇到这种情况一般有两种解决方案:

a. 引用对象

由于在回调onSaveInstanceState保存的数据不适合保存大批量的数据对象(例如bitmap),而且保存的数据对象必须是Serialized的。这种情况下,当系统配置发送变化时,我们通过引用Fragment来保存数据,在fragment中保存数据对象。具体实现如下:

//注意:当保存数据时,千万不要保存任何引用Activity实例的对象,否则会造成内存泄漏
复制代码

	public class RetainedFragment extends Fragment {
	
	    // data object we want to retain
	    private MyDataObject 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(MyDataObject data) {
	        this.data = data;
	    }
	
	    public MyDataObject getData() {
	        return data;
	    }
	}
复制代码

然后使用FragmentManager将fragment添加到Activity中。


public class MyActivity extends Activity {
	
	    private RetainedFragment dataFragment;
	
	    @Override
	    public void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.main);
	
	        // find the retained fragment on activity restarts
	        FragmentManager fm = getFragmentManager();
	        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
	
	        // create the fragment and data the first time
	        if (dataFragment == null) {
	            // add the fragment
	            dataFragment = new DataFragment();
	            fm.beginTransaction().add(dataFragment, “data”).commit();
	            // load the data from the web
	            dataFragment.setData(loadMyData());
	        }
	
	        // the data is available in dataFragment.getData()
	        ...
	    }
	
	    @Override
	    public void onDestroy() {
	        super.onDestroy();
	        // store the data in the fragment
	        dataFragment.setData(collectMyLoadedData());
	    }
	}
复制代码

b. 自己处理系统配置变化引起的改变

当系统配置发生变化时,如果Activity不需要更新数据或自动应用资源,那么可以声明自己处理该配置变化。

首先,在AndroidManifest中作如下声明:


	<activity android:name=".MyActivity"
			  <-- 在此处定义自己要处理的变化-->
	          android:configChanges="orientation|keyboardHidden"
	          android:label="@string/app_name">
	          
复制代码

当在xml中声明的任意配置发生变化时,系统不会自动重启该Activity,此时系统将回调onConfigurationChanged()。

注意: 当App的targetSdkVersion大于等于13,如果您想处理屏幕方向切换配置变化,那么你必须在android:configuration中包含screenSize属性。

Activity启动与加载过程

当用户启动Activity时,Instrumentation会接收该请求,然后instrumentation利用Binder向ActivityManagerService发请求。ActivityManagerService内部维护者Activity的调用堆栈(ActivityStack)及各个Activity的状态同步,ActivityManagerService通过ActivityThread去管理Activity的状态从而完成Activity的生命周期的管理。

转载于:https://juejin.im/post/5a3d2accf265da431d3ce107

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值