区分Activity的四种加载模式 及理解

在多Activity开发中,有可能是自己应用之间的Activity跳转,或者夹带其他应用的可复用Activity。可能会希望跳转到原来某个Activity实例,而不是产生大量重复的Activity。

这需要为Activity配置特定的加载模式,而不是使用默认的加载模式。

加载模式分类及在哪里配置

Activity有四种加载模式:

  • standard
  • singleTop
  • singleTask
  • singleInstance

设置的位置在AndroidManifest.xml文件中activity元素的android:launchMode属性:

<activity android:name="ActB" android:launchMode ="singleTask"></activity>

也可以在Eclipse ADT中图形界面中编辑:

image

 

区分Activity的加载模式,通过示例一目了然。这里编写了一个Activity A(ActA)和Activity B(ActB)循环跳转的例子。对加载模式修改和代码做稍微改动,就可以说明四种模式的区别。

standard

首先说standard模式,也就是默认模式,不需要配置launchMode。先只写一个名为ActA的Activity:

package com.easymorse.activities;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ActA extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);
        textView.setText(this + "");
        Button button = new Button(this);
        button.setText("go actA");
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(ActA.this, ActA.class);
                startActivity(intent);
            }
        });
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(textView);
        layout.addView(button);
        this.setContentView(layout);
    }
}

例子中都没有用layout,免得看着罗嗦。可见是ActA –> ActA的例子。在界面中打印出对象的toString值可以根据hash code识别是否创建新ActA实例。

第一个界面:

image

点击按钮后:

image

可以多点几次。发现每次都创建了该Activity的新实例。standard的加载模式就是这样的,intent将发送给新的实例。

现在点Android设备的回退键,可以看到是按照刚才创建Activity实例的倒序依次出现,类似退栈的操作,而刚才操作跳转按钮的过程是压栈的操作。如下图:

image

singleTop

singleTop和standard模式,都会将intent发送新的实例(后两种模式不发送到新的实例,如果已经有了的话)。不过,singleTop要求如果创建intent的时候栈顶已经有要创建的Activity的实例,则将intent发送给该实例(这时onNewIntent 被回调),而不发送给新的实例。

还是用刚才的示例,只需将launchMode改为singleTop,就能看到区别。

运行的时候会发现,按多少遍按钮,都是相同的ActiA实例,因为该实例在栈顶,因此不会创建新的实例。如果回退,将退出应用。

image

singleTop模式,可用来解决栈顶多个重复相同的Activity的问题。

如果是A Activity跳转到B Activity,再跳转到A Activity,行为就和standard一样了,会在B Activity跳转到A Activity的时候创建A Activity的新实例,因为当时的栈顶不是A Activity实例。

ActA类稍作改动:

package com.easymorse.activities;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ActA extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);
        textView.setText(this + "");
        Button button = new Button(this);
        button.setText("go actB");
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(ActA.this, ActB.class);
                startActivity(intent);
            }
        });
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(textView);
        layout.addView(button);
        this.setContentView(layout);
    }
}

 

ActB类:

package com.easymorse.activities;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;

public class ActB extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         Button button=new Button(this);
            button.setText("go actA");
            button.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent=new Intent();
                    intent.setClass(ActB.this, ActA.class);
                    startActivity(intent);
                }
            });
            LinearLayout layout=new LinearLayout(this);
            layout.addView(button);
            this.setContentView(layout);
    }
}

 

ActB类使用默认(standard)加载,ActA使用singleTop加载。结果类似下图:

image

如果把ActA的加载模式改为standard,情况一样。

singleTask

singleTask模式和后面的singleInstance模式都是只创建一个实例的。


当intent到来,需要创建singleTask模式Activity的时候,系统会检查栈里面是否已经有该Activity的实例。如果有直接将intent发送给它。

把上面singleTop的实例中的ActA的launchMode改为singleTask,ActB的改为standard。那么会发现在ActA界面中按一次按钮:

image

然后在ActB1界面中按按钮,因为ActA是singleTask,会使用原来的ActA1实例。这时候栈内的情况:----如果此时显示的是singleTask模式的activity那么此时Task 就只有此activity。

image

如果多次按按钮跳转,会发现始终只有ActA1这一个ActA类的实例。


再次些上自己认为最佳的总结(如有误,请留言,不胜感激!):通过观察发现--------- singleTask模式并不会创建新的Task,由此代码验证 : this.getTaskId()。。。


       运行程序中,无论何时出现了Act_With_SingleTaskA (一个launchmode 为singletask类型的Activity) ,第一次出现时,设定在任务的 B点。继续启动别的活动,当再次要启动Act_With_SingleTakA 时,ActivityManagerService将清除从B点到此任务栈顶的所有活动(无论这里面是否包括别的 “ launchmode 为singletask类型的 ”Activity)。


       由此可知singleTask启动模式对任务有重大的影响:使用这种模式需要谨慎对待你如何与系统其他部分进行交互,因为这影响到这个活动中的每一个路径。它应当仅在活动处于应用程序前台时使用(也就是支持 MAIN动作和LAUNCHER类别)。

singleInstance

解释singleInstance模式比较麻烦。

首先要说一下Task(任务)的概念。

如果是Swing或者Windows程序,可能有多个窗口可以切换,但是你无法在自己程序中复用人家的窗口。注意是直接复用人家的二进制代码,不是你拿到人家api后的源代码级调用。

Android可以做到,让别人的程序直接复用你的Activity(类似桌面程序的窗口)。

Android为提供这种机制,就引入了Task的概念。Task可以认为是一个栈,可放入多个Activity。比如启动一个应用,那么 Android就创建了一个Task,然后启动这个应用的入口Activity,就是intent-filter中配置为main和launch的那个(见一个APK文件部署产生多个应用安装的效果 )。这个Activity是根(Root)Activity,可能会在它的界面调用其他Activity,这些Activity如果按照上面那三个模式,也会在这个栈(Task)中,只是实例化的策略不同而已。

验证的办法是调用和打印Activity的taskId:

TextView textView2 = new TextView(this);
textView2.setText("task id: "+this.getTaskId());

会发现,无论切换Activity,taskId是相同的。

当然也可以在这个单一的Task栈中,放入别人的Activity,比如google地图,这样用户看过地图按回退键的时候,会退栈回到调用地图的Activity。对用户来说,并不觉得在操作多个应用。这就是Task的作用。

但是,有这样的需求,多个Task共享一个Activity(singleTask是在一个task中共享一个Activity)。

现成的例子是google地图。比如我有一个应用是导游方面的,其中调用的google地图Activity。那么现在我比如按home键,然后到应用列表中打开google地图,你会发现显示的就是刚才的地图,实际上是同一个Activity。

如果使用上面三种模式,是无法实现这个需求的。google地图应用中有多个上下文Activity,比如路线查询等的,导游应用也有一些上下文Activity。在各自应用中回退要回退到各自的上下文Activity中。

singleInstance模式解决了这个问题(绕了这么半天才说到正题)。让这个模式下的Activity单独在一个task栈中。这个栈只有一个Activity。导游应用和google地图应用发送的intent都由这个Activity接收和展示。

这里又有两个问题:

  • 如果是这种情况,多个task栈也可以看作一个应用。比如导游应用启动地图Activity,实际上是在导游应用task栈之上 singleInstance模式创建的(如果还没有的话,如果有就是直接显示它)一个新栈,当这个栈里面的唯一Activity,地图Activity 回退的时候,只是把这个栈移开了,这样就看到导游应用刚才的Activity了;
  • 多个应用(Task)共享一个Activity要求这些应用都没有退出,比如刚才强调要用home键从导游应用切换到地图应用。因为,如果退出导游应用,而这时也地图应用并未运行的话,那个单独的地图Activity(task)也会退出了。

如果还是拿刚才的ActA和ActB的示例,可以把ActB的模式改为singleInstance,ActA为standard,如果按一次按钮切换到ActB,看到现象用示意图类似这样:

image

如果是第一次按钮切换到ActB,在ActB在按按钮切换到ActA,然后再回退,示意图是:

image

另外,可以看到两个Activity的taskId是不同的。

 

任务A(ActA1)---任务B (ActB1) ---任务A (ActA2) ,此时连续press “ back ” key。流程是:ActA2-- ActA1任务A 结束;到任务B ,ActB1 再按 “ back ” key,任务B结束,到home screen。

 

按 返回键 是从任务中清除 activity ,当任务中没有 activity 时,任务也被撤销(这些都是在ActivityManagerSercie类)-- 流程转到最近一个任务,此时显示的是此任务栈顶的Activity。

 

现在讲解如下代码:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.title"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name" >
        <activity android:name=".ActA"
                  android:label="@string/app_name"
                  android:theme = "@style/test"                 
                  >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
         <activity android:name=".ActB"
           android:taskAffinity="com.sony.liu"
           >
        </activity>
         <activity android:name=".ActC">
        </activity>
        <activity android:name=".ActD"
        android:taskAffinity="com.sony.liu">
        </activity>
             <activity android:name=".ActE">
        </activity>
    </application>
    <uses-sdk android:minSdkVersion="4" />
</manifest>

 

       由A启动B (Intent  flag 为Intent.FLAG_ACTIVITY_NEW_TASK,会先启动一个新的和B为root Activity的任务);B启动C;C启动D(Intent  flag 为Intent.FLAG_ACTIVITY_NEW_TASK,因为D的taskAffinity 为com.sony.liu,所以不会启动新的Activity,并将此活动作为任务的顶层活动);D启动E。而E中执行如下代码:

                    Intent intent=new Intent();                      
                    intent.setClass(ActE.this, ActB.class);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);

此时并不会启动Activity,因为Activity B 为此任务的root。如果去掉intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);会启动ActB新的实例并放在栈顶。

 

改E中的代码:

 

                    Intent intent=new Intent();                      
                    intent.setClass(ActE.this, ActD.class);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);

此时会启动活动ActD新的实例,放在栈顶。 

 

 

文中大部分内容来自:http://marshal.easymorse.com/archives/2950

 

      大多数操作系统,在应用程序所寄存的可执行程序映像(如Windows系统里的.exe)、它所运行的进程以及和用户交互的图标和应用之间有一种严格的1对1关系。在Android系统里,这些关联要松散得多。并且重要的是要理解各种概念怎么样组成整体。由于Android应用固有的灵活性,当实现这些不同方面的时候有一些基本术语需要加以理解:

一个Android包 (.apk)文件,其中包含一个应用程序的代码和资源。这是应用程序分发和下载的文件,用户用来安装该应用程序在他们的设备上。

  • 一个任务一般而言是指用户视为的一个可启动应用程序:通常任务在桌面(home screen)有一个可访问的图标,且可以被切换到前台。
  • 一个进程是一个运行着应用程序代码的底层核心过程。通常所有.apk里的代码运行在一个专有的进程里。不过,进程标记也可以用来限定代码运行位置,或者为整个.apk或者为个别的活动activity,接收者receiver,服务或提供者provider,组件。


任务

这里的一个关键点是:当用户看到一个“应用”时,他们实际上在和任务打交道。如果您刚刚创建一个包含若干活动的.apk,其中之一是顶层入口点(通过动作android.intent.action.MAIN的意图过滤器intent-filter和类别 android.intent.category.LAUNCHER),那么这事实上将为您的.apk创建一个任务,并且您从那儿起动的任何活动都将作为那个任务的一部分运行。

一个任务,那么,从用户的角度来看是您的应用程序;而从应用程序开发者的角度来看,它是一个或多个用户在那个任务中已经经历过且未关闭的活动,或者说是一个活动栈。一个新的任务通过以Intent.FLAG_ACTIVITY_NEW_TASK标志起动一个活动意图来创建;这一意图将被用来作为任务的根意图,定义任务是什么。任何不以这个标志起动的活动将和起动它的活动在相同的任务中运行(除非该活动已请求特别启动模式,稍后会讨论)。任务可以被重新安排:如果您使用FLAG_ACTIVITY_NEW_TASK标志但已经有一个任务以这个意图运行,则当前任务的活动栈将被切换到前台而不是开始一个新的任务。

FLAG_ACTIVITY_NEW_TASK必须谨慎使用:使用它意味着,在用户看来,一个新的应用程序由此起动。如果这不是你所期望的行为,你就不该去创建一个新的任务。另外,仅在用户可以从桌面返回到他原来的地方和以一个新任务启动相同意图的情况下,你才应该使用新的任务标记。否则,如果用户在你已经启动的任务里按桌面(HOME)键,而不是返回(BACK)键,你的任务及其活动将被放置到桌面后面,没有办法再切换回去。


任务共用性Affinity

在某些情况下,Android需要知道一个活动属于哪个任务即使它没有被启动到一个具体的任务里。这是通过任务共用性(Affinities)完成的。任务共用性(Affinities)为这个运行一个或多个活动的任务提供了一个独特的静态名称,默认的一个活动的任务共用性(Affinity)是实现了该活动的.apk包的名字。这提供了预期的标准特性,即所有在一个特定的.apk包里的活动是单个用户应用程序的一部分。

当开始一个没有Intent.FLAG_ACTIVITY_NEW_TASK标志的活动时,任务共用性affinities不会影响将会运行该新活动的任务:它总是运行在启动它的任务里。但是,如果使用了NEW_TASK标志,那么共用性(affinity)将被用来判断是否已经存在一个有相同共用性(affinity)的任务。如果是这样,这项任务将被切换到前面而新的活动会启动于这个任务的顶层。



这种特性在您必须使用NEW_TASK标志的情况下最有用,尤其是从状态栏通知或桌面快捷方式启动活动时。结果是,当用户用这种方式启动您的应用程序时,它的当前任务将被切换到前台,而且想要查看的活动被放在最上面。

     你可以在程序清单(Manifest)文件的应用程序application标签中为.apk包中所有的活动分配你自己的任务共用性 Affinites,或者在活动标记中为各个活动进行分配。一些说明其如何使用的例子如下:如果您的.apk包含多个用户可以启动的高层应用程序,那么您可能需要对用户看到的每个活动指定不同的affinities。一个不错的命名惯例是以附加一个以冒号分隔的字符串来扩展您的.apk包名。例如,“com.android.contacts ”.apk可以有affinities:“com.android.contacts:Dialer”和“ com.android.contacts:ContactsList”。
如果您正在替换一个通知,快捷方式,或其他可以从外部发起的应用程序的“内部”活动,你可能需要明确设定您替代活动的taskAffinity和您准备替代的应用程序一样。例如,如果您想替换contacts详细信息视图(用户可以创建并调用快捷方式),你得把taskAffinity设置成“com.android.contacts”。
启动模式和启动标志

注意:当用flag 为 Intent.FLAG_ACTIVITY_NEW_TASK的Intent启动活动时,在Manfiest中Activity的taskAffinity 的值需要进行设置。才可以为此活动启动一个新的任务(没有相同的任务已经存在)。结束执行程序后,你长按Home键打开任务管理器,会发现有两个相同名称的任务(这两个是应用程序的两个任务,当然都是因为这两个任务都是最近被启动过,而且都有静态标志)。

 

   启动一个Activity 如果同时也启动了一个新的任务,Screen会直接进入新启动的Activity;如果不启动新的任务,则只要当此Activity创建完毕Screen才会进入此Activity。。


您控制活动和任务交互的主要途径是通过活动的launchMode 属性和意图相关的标志flags。这两个参数可以以各种方式合作来控制活动启动的结果,正如它们相关文档中描述的那样。在这里,我们将看看一些常见的用例和参数组合。

       你将使用的最常见的启动模式(除了默认的standard模式)是singleTop。这并不影响任务;它只是避免多次在一个堆栈顶部起动同一活动。singleTask启动模式对任务有重大的影响:它使活动始终是开始于一项新的任务(或其现有的任务被带到前台) 。使用这种模式需要谨慎对待你如何与系统其他部分进行交互,因为这影响到这个活动中的每一个路径。它应当仅在活动处于应用程序前台时使用(也就是支持 MAIN动作和LAUNCHER类别)。singleInstance启动模式更是专业,并应仅用于整个就是被实现为一个活动的应用程序中。

       有一种你会经常遇到的情况是当另一个实体(如SearchManager 或NotificationManager)开始您的一个活动。在这种情况下,必须使用Intent.FLAG_ACTIVITY_NEW_TASK 标签,因为该项活动是在任务之外起动的(而且应用/任务可能根本不存在)。正如前面所述,这种情况下的标准行为是把匹配新活动affinity的任务带到前台和在此之上起动新的活动。不过,也有其他您可以实施的行为类型。

       其中一种常见的做法是,还可以使用Intent.FLAG_ACTIVITY_CLEAR_TOP 国旗与NEW_TASK 。通过这样做,如果你的任务已经运行,那么将提请前景,所有的活动,其堆栈清除除根系活力和根系活力的onNewIntent (意图) 所谓的意图正在开始。请注意,该活动还常常使用singleTop 或singleTask 发射模式时,使用这种方法,因此,目前的情况是由于新的意图而不需要将它摧毁,一个新的实例开始。


       一种通常的办法是和NEW_TASK联合起来使用Intent.FLAG_ACTIVITY_CLEAR_TOP标志。这样,如果您的任务已经运行,那么它将会被带到前台,除根活动外其它所有堆栈中的活动都被清除,而且这个根活动的方法onNewIntent(Intent)会在该意图起动时被调用。注意这个活动使用这个方法时经常使用singleTop或者singleTask起动模式,这样当前实例被赋予新的意图而不是需要销毁它然后重新起动一个新的实例。



您能采取的另外的方法是设置通知活动的任务affinity为空字符串“”(表示没有affinity),并设置 finishOnBackground属性。这种方法是有用的如果你希望这个通知把用户带到一个单独的描述它的活动中,而不是返回到应用程序的任务。通过指定这个属性,该活动将被结束不管用户通过BACK还是HOME离开它;如果这个属性没有指定,按首页将导致这个活动及其任务仍保留在系统里,且可能没有办法返回它。

 

进程



在Android里,进程完全是应用的实现细节,而不是用户通常了解的那样。其主要用途就是:
(1).通过安置不受信任的或不稳定的代码到另一个进程来提高稳定性或安全性。
(2).通过在同一进程里运行多个.apks的代码来减少开销。
(3).通过把重量级代码放在单独的进程中来帮助系统管理资源,该进程可以在不影响应用程序其他部分的情况下被终止。


       正如前面所述,这个进程属性用来控制运行着特定应用程序组件的进程,注意,此属性不能用于违反系统安全性:如果有两个不共享相同用户ID的.apks尝试运行在同一进程中,这将不会被允许,相反会为它们每一个创建不同的进程。

 http://club.topsage.com/thread-1848297-1-1.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值