1.1 Activity(Android校招复习)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Leonidas_Li/article/details/84107725

1 Activity是什么?

Activity是Android中与用户直接进行交互(滑动、触摸、点击等)的组件,也就是Android系统提供给用户操作的UI组件。

2 Activity的生命周期

这里写图片描述

2.1 Activity的4种状态

  • running(运行状态)
    Activity运行于“前台任务栈”的栈顶,是与用户直接进行交互的Activity。
  • paused
    Activity失去焦点,就是有其他Activity以透明、非全屏的状态“覆盖”于此Activity之上时。
  • stoped
    Activity处于任务栈内,保存了所有运行时的数据,但无法与用户交互。
  • killed
    Activity被释放掉,不存在与内存之中。

2.2 Activity各生命周期方法的调用意义、作用

  • onCreate()
    表示Activity正在被创建。在这里可以做一些初始化的工作。
  • onStart()
    表示Activity正在被启动。已经可见,但不在前台,无法交互。
  • onResume()
    表示Activity已经可见,并且出现在前台可以交互。
  • onPause()
    表示Activity正在停止。在这里可以做一些储存数据、保存用户状态(必须在此处保存,因为onPause方法在Activity即将销毁或将到后台前必定执行)、停止动画等工作,但不能太耗时,因为必须onPause执行完成之后新的Activity才能Resume。
  • onStop()
    表示Activity即将停止。可以进行一些稍微重量级的回收工作,不能太耗时。在内存严重不足时将不会执行,因此保存用户状态等操作最好在onPause()方法中进行
  • onRestart()
    表示Activity正在重新启动。当前Activity从不可见重新变成可见状态。
  • onDestory()
    表示Activity即将被销毁。可以进行一些回收工作和最终的资源释放。

2.3 Activity在不同场景下经历的生命周期方法

常见情况

  • Activity正常启动:
    onCreate()-> onStart()-> onResume()
    在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期:
  • 当用户按下back键之后(Activity正常销毁):
    onPause()-> onStop()-> onDestory()
  • 当有一个透明、非全屏的Activity覆盖在此Activity之上时:
    onPause()
  • 打开了另外一个Activity时(此Activity不处于前台任务栈栈顶)
    onPause()-> onStop()
  • 用户按下Home键时
    onPause()-> onStop()

特殊情况

  • 调用finish()方法后:
    • 在Activity的onCreate()中调用finish(),则执行的生命周期方法顺序为:
      onCreate() -> onDestroy()
    • 在Activity的onStart()中调用finish(),则执行的生命周期方法顺序为:
      onCreate() -> onStart() -> onStop() -> onDestroy()
    • 在Activity的onResume()或onPostResume()中调用finish(),则执行的生命周期方法顺序为:
      onCreate() -> onStart() -> onResume() -> onPostResume() -> onPause() -> onStop() -> onDestroy()
    • 在Activity的onPause()、onStop()、onDestory()中调用finish(),则执行的什么周期方法顺序为:
      按照正常的生命周期顺序执行。
  • 横竖屏切换
    onPause()-> onSaveInstanceState()-> onStop()-> onDestroy()->onCreate()-> onStart()-> onRestoreInstanceState()-> onResume()
    PS:1.横竖屏切换过程中,Activity进行被销毁然后重建的过程,因此才有了生命周期的改变。
    2.在Activity因异常情况下终止时,系统会调用onSaveInstanceState()来保存当前Activity的状态。这个方法的调用是在onStop()之前,但它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。
    3.异常终止的Activity被重建后,系统会调用onRestoreInstanceState(),并把Activity销毁时onSaveInstanceState()方法所保存的Bundle对象参数同时传递给onRestoreInstanceState()和onCreate()方法,因此,可通过onRestoreInstanceState()方法来恢复Activity的状态,该方法的调用时机是在onStart之后
    4.onCreate()和onRestoreInstanceState()方法恢复Activity的状态的区别:onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断。onCreate()需要非空判断。建议使用onRestoreInstanceState()**。
    5.避免横竖屏切换时,Activity的销毁和重建,只需在AndroidManifest文件中指定如下属性即可:
    android:configChanges = “orientation| screenSize”
    避免重建后,Activity横竖屏切换后会调用如下方法
@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

2.4 Android进程优先级

前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程
前台进程(running状态):正在与用户交互的Activity或与前台Activity绑定的Service
可见进程(paused状态):当Activity可见但不是处于前台进程,也就是不可交互的进程。
服务进程:在后台开启的Service服务的进程
后台进程(stoped状态):如按下Home键后的当前Activity就处于后台进程,此时后台进程不会立即被kill掉,系统会根据当前的内存状况来进行判断是否kill。
空进程 :表示不活跃的组件,系统出于缓存的目的而保留。

3 Android任务栈

任务栈是什么?

  1. Task又称为Android任务栈,是一个栈结构,具有后进先出的特性,用于存放我们的Activity,Android中用任务栈与启动模式结合来达到节省内存资源的目的。

  2. 我们每次打开一个新Activity或退出当前Activity都会在任务栈中添加或减少一个Activity,因此一个任务栈包含了一个Activity的集合,只有栈顶Activity才能与用户交互,,Android系统通过Task有序地管理每个Activity,并决定哪个Activity与用户交互。

  3. 我们退出应用程序时,必须把任务栈中所有的Activity清除出栈时,任务栈才 会被销毁。当然任务栈也可以移动到后台,并且保留了每一个Activity的状态,可以有序的给用户列出它们的任务,同时也不会丢失Activity的状态信息。

  4. 需要注意的是,一个App中可能不止一个任务栈,某些特殊情况下,单独一个Actvity可以独享一个任务栈。还有一点就是一个Task中的Actvity可以来自不同的App,同一个App的Activity也可能不在一个Task中。

4 Activity四种启动模式

这里写图片描述

4.1 Standard (标准模式)

说明: 假设没有为Activity设置启动模式的话,默认标准模式。无论这个实例是否存在,每次启动一个Activity都会又一次创建一个新的实例入栈。

举例:此时Activity 栈中以此有A、B、C三个Activity,此时C处于栈顶,启动模式为Standard 模式。若在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是还有一个C Activity进入栈中,成为栈顶。
这里写图片描述

特殊情况:
如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。解决办法:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。

绝大多数Activity。如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。

4.2 SingleTop (栈顶复用模式)

说明:(分两种处理情况)
1.须要创建的Activity已经处于栈顶时,此时会直接复用栈顶的Activity,不会再创建新的Activity。
2.若须要创建的Activity不处于栈顶,此时会又一次创建一个新的Activity入栈,同Standard模式一样。
若情况一中栈顶的Activity被直接复用时,它的onCreate()、onStart()不会被系统调用,由于它并没有发生改变。可是一个新的方法 onNewIntent()会被回调(Activity被正常创建时不会回调此方法)。

举例:此时Activity 栈中以此有A、B、C三个Activity,此时C处于栈顶,启动模式为SingleTop 模式。

情况一:在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是直接复用栈顶的C Activity,并调用如下方法。

//由于不会重建一个Activity实例,则不会回调其他生命周期方法。
@Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
    }

情况二:在C Activity中加入点击事件,须要跳转到还有一个A Activity。结果是创建一个新的Activity入栈。成为栈顶。
这里写图片描述

应用场景:
在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用SingleTop,否则每次点击都会新建一个Activity。

当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 SingleTop模式也是可以避免这个Bug的。

同Standard模式,如果是外部程序启动SingleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中

4.3 SingleTask (栈内复用模式)

说明:若须要创建的Activity已经处于栈中时,此时不会创建新的Activity,而是将存在栈中的Activity上面的其他Activity所有销毁,使它成为栈顶,并调用onNewintent()方法。

PS:该模式是一种单例模式,即一个栈内只有一个该Activity实例。该模式,可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中,即SingleTask的Activity可以指定想要加载的目标栈。SingleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中。

<activity android:name=".Activity_A"
    android:launchMode="singleTask"
    android:taskAffinity="com.lvr.task"
    android:label="@string/app_name">
</activity>

PS:
1.关于taskAffinity的值:
每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。

2.执行逻辑:
在这种模式下,如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入栈内。如果Activity指定的栈存在,如果其中没有该Activity实例,则会创建Activity并压入栈顶,如果其中有该Activity实例,则把该Activity实例之上的Activity杀死清除出栈,重用并让该Activity实例处在栈顶,然后调用onNewIntent()方法。

**举例:**此时Activity 栈中依次有A、B、C三个Activity。此时C处于栈顶,启动模式为SingleTask 模式。

情况一:在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是直接用栈顶的C Activity。

情况二:在C Activity中加入点击事件,须要跳转到还有一个A Activity。结果是将A Activity上面的B、C所有销毁,使A Activity成为栈顶。

这里写图片描述

情况三:目标任务栈不存在,则创建一个任务栈,并压入栈顶。

这里写图片描述

应用场景:
应用于大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例。

通过这种方式能够保证退出应用时所有的Activity都能报销毁。在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。

特殊情景:

特殊情景一:
现在我们假设有如下两个Task栈,分别为前台任务栈和后台任务栈
这里写图片描述

从图中我们看出前台任务栈分别为AB两个Activity,后台任务栈分别为CD两个任务栈,而且其启动模式均为singleTask,此时我们先启动CD,然后再启动AB,再有B启动D,此时后台任务栈便会被切换到前台,而且这个时候整个***后退列表***就变成了ABCD,请注意我们这里强调的是后退列表,而非栈合并。因此当用户点击back键时,列表中的Activity会依次按DCBA顺序出栈,如下图所示:

这里写图片描述

这里我们通过两个应用ActivityTask和ActivityTask2来测试重现这个现象。因为两个是不同的应用所以启动时所在的栈也是不同。我们先启动ActivityTask2的应用,其ActivityC和ActivityD都是singleTask模式,然后再启动应用ActivityTask,此时ActivityC和ActivityD所在任务栈会被退居后台,而打开的ActivityA和ActivityB会在前台,而且都是默认模式。我们通过 adb shell dumpsys activity activities 命令查看此时栈的情况:

这里写图片描述

我们可以看到由两个栈,分别为id=222且栈名为“com.cmcm.activitytask”的任务栈其包含ActivityA和ActivityB(下面简称AB,栈名一般默认和包名相同),另外一个任务栈,id=221,栈名为“com.cmcm.activitytask2”,其包含ActivityC和ActivityD(下面检测CD)。现在我们通过ActivityB去启动ActivityD,然后按back键回退。B调用D代码如下:

public class ActivityB extends Activity {
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        btn= (Button) findViewById(R.id.main);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_MAIN);
                intent.addCategory(Intent.CATEGORY_LAUNCHER);
                ComponentName cn = new ComponentName("com.cmcm.activitytask2", "com.cmcm.activitytask2.ActivityD");
                intent.setComponent(cn);
                startActivity(intent);
            }
        });
    }
}

运行结果如下:
这里写图片描述

我们可以看到包含CD的任务栈被提前的,虽然CD隔开了,但是我们从id和栈名可以发现他们是同一个栈,而AB所在的栈则在CD所在栈的后面,所以此时我们按back回退时,退出顺序是这样的D->C->B->A,动态图如下:

这里写图片描述

到这里我们就应该更加清晰的了解情景一的现象了。了解这点有什么用呢,这可以使用我们更好地去管理我们的任务栈,而不会导致栈混乱是进入一些用户本来就不需要界面,影响用户体验。

特殊情景二:

如果上面B不是请求启动D而是请求启动C,那么又会是什么情况呢?其实这个时候任务栈退出列表变成C->B->A,其实原因很简单,singleTask模式的ActivityC切换到栈顶时会导致在他之上的栈内的Activity出栈。同样我们还是使用上面的代码,把B启动D改为B启动C,那么此时B未启动C时任务栈的情况如下:

这里写图片描述

我们仍然可以看到两个任务栈,分别为id=242,栈名“com.cmcm.activitytask”的Task,包含ActivityA和ActivityB;id=241,栈名“com.cmcm.activitytask2”的Task,包含ActivityC和ActivityD。此时我们通过B启动C后栈的情况变成如下情况 :

这里写图片描述

因此,栈的退出列表就变成了C->B->A了,如下图所示:
这里写图片描述

动态图如下:
这里写图片描述

4.4 SingleInstance (单例模式)

说明 SingleInstance比较特殊,具有此模式的Activity仅仅能单独位于一个任务栈中。这个经常使用于系统中的应用,比如Launch、锁屏键的应用等等,整个系统中仅仅有一个!

举例:比方 A Activity是该模式,启动A后。系统会为它创建一个单独的任务栈,由于栈内复用的特性,新的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁。

4.5 Activity启动模式的使用方式

1.通过AndroidMenifest.xml文件为Activity指定启动模式,代码如下:

<activity android:name=".ActivityC" 
          android:launchMode="singleTask" />

2.通过在Intent中设置标志位(addFlags方法)来为Activity指定启动模式,示例代码如下:

Intent intent = new Intent();
intent.setClass(ActivityB.this,ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

那么标志位是是什么呢?接下来我们就来了解一些常用的标志位

1.3 Activity的Flags

**说明:**这里我们主要介绍一下一些常用的Activity的Flag,因为Activity的Flag比较多,我们知道一些常用的就够了,遇到比较特殊的还是查查官网文档吧。

  • Intent.FLAG_ACTIVITY_NEW_TASK

该标志位表示使用一个新的Task来启动一个Activity,相当于在清单文件中给Activity指定“singleTask”启动模式。通常我们在Service启动Activity时,由于Service中并没有Activity任务栈,所以必须使用该Flag来创建一个新的Task。我们来重现一下这个错误,创建一个Service服务,并在onCreate方法中启动Activity,代码如下:

public class ServiceT extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent i =new Intent(getApplicationContext(),ActivityD.class);
        startActivity(i);
    }
}

启动应用并启动Service服务,后报错如下:

这里写图片描述

从异常信息我们可以看出,提示我们添加Intent.FLAG_ACTIVITY_NEW_TASK标志位,所以我们代码必须改成如下:

public class ServiceT extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public void onCreate() {
        super.onCreate();
        Intent i =new Intent(getApplicationContext(),ActivityD.class);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(i);
    }
}
  • Intent.FLAG_ACTIVITY_SINGLE_TOP

该标志位表示使用singleTop模式来启动一个Activity,与在清单文件指定android:launchMode="singleTop"效果相同。

  • Intent.FLAG_ACTIVITY_CLEAR_TOP

该标志位表示使用singleTask模式来启动一个Activity,与在清单文件指定android:launchMode="singleTask"效果相同。

  • Intent.FLAG_ACTIVITY_NO_HISTORY

使用该模式来启动Activity,当该Activity启动其他Activity后,该Activity就被销毁了,不会保留在任务栈中。如A-B,B中以这种模式启动C,C再启动D,则任务栈只有ABD。

  • Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

使用该标识位启动的Activity不添加到最近应用列表,也即我们从最近应用里面查看不到我们启动的这个activity。与属性android:excludeFromRecents="true"效果相同。

5 URL Scheme跳转协议

5.1 什么URL Scheme?

Scheme是Android中的一种页面跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。

5.2 URL Scheme的应用场景

客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。综上URL Scheme使用场景大致分以下几种:

  • 服务器下发跳转路径,客户端根据服务器下发跳转路 径跳转相应的页面
  • H5页面点击锚点,根据锚点具体跳转路径APP端跳转具体的页面
  • APP端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面
  • APP根据URL跳转到另外一个APP指定页面

5.3 URL Scheme协议格式

完整的URL Scheme协议格式:

xl://goods:8888/goodsDetail?goodsId=10011002
通过上面的路径 Scheme、Host、port、path、query全部包含,基本上平时使用路径就是这样子的。

  • xl代表该Scheme 协议名称
  • goods代表Scheme作用于哪个地址域
  • goodsDetail代表Scheme指定的页面
  • goodsId代表传递的参数
  • 8888代表该路径的端口号

5.4 URL Scheme如何使用?

  1. 在AndroidManifest.xml中对标签增加设置Scheme。
 <activity
            android:name=".GoodsDetailActivity"
            android:theme="@style/AppTheme">
            <!--要想在别的App上能成功调起App,必须添加intent过滤器-->
            <intent-filter>
                <!--协议部分,随便设置-->
                <data android:scheme="xl" android:host="goods" android:path="/goodsDetail" android:port="8888"/>
                <!--下面这几行也必须得设置-->
                <category android:name="android.intent.category.DEFAULT"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.BROWSABLE"/>
            </intent-filter>
        </activity>
  1. 获取Scheme跳转的参数
Uri uri = getIntent().getData();
if (uri != null) {
    // 完整的url信息
    String url = uri.toString();
    Log.e(TAG, "url: " + uri);
    // scheme部分
    String scheme = uri.getScheme();
    Log.e(TAG, "scheme: " + scheme);
    // host部分
    String host = uri.getHost();
    Log.e(TAG, "host: " + host);
    //port部分
    int port = uri.getPort();
    Log.e(TAG, "host: " + port);
    // 访问路劲
    String path = uri.getPath();
    Log.e(TAG, "path: " + path);
    List<String> pathSegments = uri.getPathSegments();
    // Query部分
    String query = uri.getQuery();
    Log.e(TAG, "query: " + query);
    //获取指定参数值
    String goodsId = uri.getQueryParameter("goodsId");
    Log.e(TAG, "goodsId: " + goodsId);
}
  1. 调用方式
    在网页中:
<a href="xl://goods:8888/goodsDetail?goodsId=10011002">打开商品详情</a>

在原生应用中:

Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
  startActivity(intent);
  1. 如何判断一个URL Scheme是否有效
PackageManager packageManager = getPackageManager();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isValid = !activities.isEmpty();
if (isValid) {
    startActivity(intent);
}

注:以上内容是由自己从互联网收集整理、自己写、及看书、看视频等总结出来的笔记,如果借鉴的内容需要标识出来请私信我。

猜你喜欢

转载自blog.csdn.net/Leonidas_Li/article/details/84107725
1.1
今日推荐