Android中launcherMode="singleTask"详解【android源码解析六】

2023-05-16

            android中launcherMode有4中属性:standard(默认), singleTop,singleTask和 singleInstance;网上有好多例子讲解这四种关系的:下面我列举几个链接:

       大明原创---->转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/7304191

        http://www.cnblogs.com/xiaoQLu/archive/2011/09/29/2195742.html

        http://marshal.easymorse.com/archives/2950

        http://blog.csdn.net/infsafe/article/details/5666964

        http://www.j2megame.com/html/xwzx/ty/2027.html

        等等。我不一一列举;

       下面是我对singleTask的一点拙见,希望分享一下给大家:

        最近研究android浏览器browser,这个BrowserActivity的launcherMode="singleTask",因为browser不断地启动自己,所以要求这个栈中保持只能有一个自己的实例,就像别人总结的这样“注意singleTask模式的Activity不管是位于栈顶还是栈底,再次运行这个Activity时,都会destory掉它上面的Activity来保证整个栈中只有一个自己,切记切记”这点是毋庸置疑的。

       问题:browser的launcherMode=“singleTask”,browser上网的时候,遇到播放视频的链接,就会通过隐式intent方式跳转找Gallery3D中的MovieView这个类来播放视频,这时候如果你点击home键,再点击browser,你会发现MovieView这个类已经销毁不存在了,而不会像保存这个MovieView的类对象,给客户带来的用户体验特别的不好。用户还得从新下载刚才的视频。不支持断点播放。为了验证这一问题,我专门写了个小例子来说明activity之间的生命周期:

       (1)先建一个App1的工程。包名为:com.cn.daming.app1,包里面有一个类:App1Activity。里面的代码如下:

package com.cn.daming.app1;

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

public class App1Activity extends Activity {

	private Button mButton = null;
	
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.v("daming", "App1Activity--11-->onCreate()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
        mButton = (Button)findViewById(R.id.button1);
        mButton.setOnClickListener(new OnClickListener(){

			public void onClick(View arg0) {
				Intent intent = new Intent();
				intent.setClassName("com.cn.daing.app2", "com.cn.daing.app2.App2Activity");
				startActivity(intent);
			}
        });
    }

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.v("daming", "App1Activity--11-->onDestroy()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onPause() {
		super.onPause();
		Log.v("daming", "App1Activity--11-->onPause()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Log.v("daming", "App1Activity--11-->onRestart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onResume() {
		super.onResume();
		Log.v("daming", "App1Activity--11-->onResume()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStart() {
		super.onStart();
		Log.v("daming", "App1Activity--11-->onStart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStop() {
		super.onStop();
		Log.v("daming", "App1Activity--11-->onStop()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		Log.v("daming", "App1Activity--11-->onNewIntent()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		Log.v("daming", "App1Activity--11-->onSaveInstanceState()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}
}

       manifest的属性为:android:launchMode="singleTask"     android:alwaysRetainTaskState="true"

 

      (2)在建一个App2的工程,包名:com.cn.daing.app2  ;类名为:App2Activity;代码为:

package com.cn.daing.app2;

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

public class App2Activity extends Activity {
	
	private Button mButton = null;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.v("daming", "App2Activity--22-->onCreate()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
        mButton = (Button)findViewById(R.id.button1);
        mButton.setOnClickListener(new OnClickListener(){

			public void onClick(View arg0) {
				Intent intent = new Intent();
				intent.setClassName("com.cn.daming.app1", "com.cn.daming.app1.App1Activity");
				startActivity(intent);
			}
        });
    }

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.v("daming", "App2Activity--22-->onDestroy()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		Log.v("daming", "App2Activity--22-->onNewIntent()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onPause() {
		super.onPause();
		Log.v("daming", "App2Activity--22-->onPause()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Log.v("daming", "App2Activity--22-->onRestart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onResume() {
		super.onResume();
		Log.v("daming", "App2Activity--22-->onResume()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		Log.v("daming", "App2Activity--22-->onSaveInstanceState()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStart() {
		super.onStart();
		Log.v("daming", "App2Activity--22-->onStart()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}

	@Override
	protected void onStop() {
		super.onStop();
		Log.v("daming", "App2Activity--22-->onStop()...."+" /n getTaskId() == "+
        		getTaskId()+"   isTaskRoot() == "+isTaskRoot());
	}
    
}

     manifest的属性为默认值;


     (3)然后启动App1---->点击调用第二个App2button按钮的log: 

                       

      从log中可以看出:从App1到App2是在一个task栈中,值都是33,并且App1Activity是位于栈底。根节点的。点击按钮的时候,调用App1Activity的onSaveInstanceState()----->onPause()----->App2Activity的onCreate()----->onStart()---->onResume()---->App1Activity的onStop()方法。

 

     (4)点击调用第一个App1---->button按钮;

               

   

        看一下log:

 还在一个栈中,数字变了因为我退出后从新操作的,把以前的log清空了,这时候发现了一个奇怪的现象,启动第一个App1的时候,App2从onStop()---->onDestroy()了,彻底销毁了;这个对象就不存在了。这也就验证了browser点击视频播放,点击home键,再点击browser,视频播放停止的现象的,android的singleTask就是这个设计的。挺独特的。

      (5) 要想解决这一个问题,也是有办法的,我经过两天的钻研,终于找到了方法,现分享给大家:就是在App1中启动一个activity,这个activity的launcherMode=“standard”,然后由它来启动App1Activity,这样就避免了这一现象;

      启动一个默认的activity:LoginApp1Activity代码:

package com.cn.daming.app1;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class LoginApp1Activity extends Activity{

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Intent intent = (Intent)getIntent().clone();
		intent.setClass(LoginApp1Activity.this, App1Activity.class);
		startActivity(intent);
		finish();
	}
}

      

         manifest.xml中的代码:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cn.daming.app1"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:launchMode="standard"
            android:alwaysRetainTaskState="true"
            android:name=".LoginApp1Activity" 
            >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:alwaysRetainTaskState="true"
            android:name=".App1Activity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.DELETE"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

     

 

      (6)从启动App1---->点击按钮启动App2---->点击home键---->在点击App1---->进入App2界面的log如下:


       说明:从mainmenu见面点击App1的时候,调用的是App2Activiy的onRestart()方法---->onStart()---->onResume()方法,而不是调用的onDestroy()方法。

 

        原因梳理:

 在Task范围内只产生一个实例。且这个实例启动完成后是在栈顶。

如果在一个apk应用里,singleTask的A启动了一个B,B中启动了A,这样的情况怎么办呢?如果A是在原Task中启  动,系统会将B结束,然后保持原A在栈顶。

设置了"singleTask"启动模式的Activity的特点:

1. 设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动(此后在根据第2条检查),否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。

2. 如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的Stack顶端中(官方文档说同时也在root of Stack,Stack底端,存疑1)。

   真正的原因是singleTask的模式有个FLAG_ACTIVITY_BROUGHT_TO_FRONT,它才是最后的罪魁祸首!

   官方文档上的解释如下:

    If, when starting the activity, there is already a task running that starts with this activity, then instead of starting a new instance the current task is brought to the front. The existing instance will receive a call to Activity.onNewIntent() with the new Intent that is being started, and with the Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set. This is a superset of the singleTop mode, where if there is already an instance of the activity being started at the top of the stack, it will receive the Intent as described there (without the FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set). See the Tasks and Back Stack document for more details about tasks.

    大致意思为:当启动一个activity的时候,如果这个task中有这个activity的实例,这个实例就会被放到当前的task的前面,这个存在的实例是通过onNewIntent()调用实现的。伴随着这个activity的flag---->(FLAG_ACTIVITY_BROUGHT_TO_FRONT )会被设置,这是singleTop模式的超集,如果当这儿已经有个实例的activity在这个栈的顶部,这时候它不会设置这个flag(FLAG_ACTIVITY_BROUGHT_TO_FRONT )的值,详解看tasks和栈说明,更清楚的了解这方面的知识!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android中launcherMode="singleTask"详解【android源码解析六】 的相关文章

  • Android:如何将自定义字段/操作添加到联系人详细信息

    我会将自定义操作添加到 Android 的系统联系方式中 例如 Viber 请参阅随附的屏幕截图 和其他消息应用程序 当用户单击此类自定义操作时 我的应用程序将进行处理 有什么帮助吗 我怀疑这是否像听起来那么容易 据我所知 您需要在设备上创
  • Android ListView数组索引过滤后越界

    我认为这是专家的问题 我接到电话getView with positon 出界来自ListView数据列表 当我使用适配器过滤器时会发生这种情况 过滤器publishResults 方法使用小于原始列表的过滤列表填充数据 当新的过滤列表时似
  • Android x86_64 和 x86 模拟器在 AlertDialog.onClick 事件上崩溃

    我正在 x86 64 和 x86 模拟器中测试 Android 应用程序 在从显示列表的 AlertDialog 中选择一个项目后 它崩溃了 Android 模拟器意外关闭 AlertDialog 是从 AppCompatActivity
  • Android 6 getAccountName() 缺少 android.permission.GET_ACCOUNTS

    在 Android 6 设备上运行时出现以下异常 java lang SecurityException Missing android permission GET ACCOUNTS 这看起来像是一个相当简单的例外 但对我来说并非如此 我
  • InflateException:二进制 XML 文件行 #8:膨胀类 ImageView 时出错

    我知道还有其他问题也问同样的事情 我已经看过其中的大部分了 大多数答案都涉及内存错误 我看不出如何ImageView只加载一个小图标 24x24 像素 可以做出类似这样的东西 但我想在这里发布堆栈跟踪 也许其他人可以识别我无法识别的东西并帮
  • Android 中的 JDBC 连接

    有没有人在 android 中尝试过 JDBC 连接 因为在 Android 2 3 中支持 JDBC 我必须在没有 Web 服务的情况下连接 Mysql 我已经提出申请 但它给了我错误 public class MysqlConnect
  • Orientation改变时如何处理Activity?

    我正在编写一个活动 它从服务器加载数据并使用 ArrayAdapter 将其显示为列表 为此 我显示了一个进度对话框 即加载 同时它从服务器加载所有数据 然后我在处理程序中关闭该对话框 我的问题是 当我更改方向时 会再次显示进度对话框 这是
  • 从 DecorView@2da7146[MyActivity] 中找不到 ViewTreeLifecycleOwner

    从撰写更新后alpha 11 to alpha 12 or beta 01 每当我打开具有撰写视图的活动或片段时 我都会遇到此崩溃 我在用AppCompatActivity它实现了LifecycleOwner 所以这非常奇怪 java la
  • java.lang.RuntimeException:release()后调用的方法

    If i am 不使用 相机 release in 表面被破坏 then 无法从另一个 Activity 再次启动 CameraActivity 简而言之 得到不幸的是应用程序已停止 错误 即使不释放相机 但如果我确实点击了 主页 按钮 来
  • 此版本不符合 Google Play 64 位要求,添加库后仍然出现错误

    我正在 Play 商店上传一个视频编辑器应用程序 其中包含带有一些本机代码的库 所以我通过将其添加到 gradle 来使其兼容 64 位 ndk abiFilters armeabi v7a arm64 v8a x86 x86 64 添加了
  • 自定义 ListView 具有不同的行布局,根据 Web 服务响应进行扩展

    我有列表视图 我有3组观点 图像视图 视频查看 文本视图 我有一个返回图像 视频和文本的网络服务器 因此 根据服务器的响应 我想膨胀列表视图中行的视图 我知道使用自定义适配器来扩展自定义布局 我想知道如何使用自定义适配器来扩充视图类型 例如
  • 将原生 Twilio Android SDK 与 Flutter 集成

    我正在尝试使用 flutter 创建 IP 语音 VOIP 移动应用程序 我还没有看到 twilio 语音 api 的 flutter 插件的实现 所以我使用 MethodChannel 将我的应用程序与本机 android 语音 api
  • Android React-Native 中 MainActivity 不存在错误

    所以我收到 MainActivity 不存在错误 这可能是因为我将包和应用程序重命名为bomber to Bomber并将 appId 更改为com bomber to cool bomber android 我检查了 AndroidMan
  • ViewPager 的 Android Viewholder

    您好 我正在使用 ViewPager 但我现在想保留状态 而不是重新生成单元格 我注意到 viewpager 与 listviews 的自定义适配器非常相似 它们的功能类似 listview 在单元格不再出现在屏幕上后重新生成单元格中的数据
  • 更改弹出对话框的背景颜色

    我编写了显示弹出对话框的 android 代码 但我想将背景颜色从黑色更改为白色 然后更改文字颜色 这是对话框的代码 mPrefs PreferenceManager getDefaultSharedPreferences this Boo
  • 在Android中绘制圆角矩形

    我已经发现这个问题 https stackoverflow com questions 5618402 how to draw rounded rectangle in android ui解决方案是这段代码
  • 如何在Android data/data/project文件系统中创建文件目录和文件夹

    我正在开发一个视频编辑器程序 并且对 android 和 java 相当陌生 我希望发生的是 当用户按下 创建新项目 按钮时 会弹出一个对话框 询问用户项目的名称 我已经把那部分写下来了 但是我想要的是 当用户在该对话框上按 确定 时 我的
  • Listview里面只有一个Element

    您好 我正在尝试将列表视图放入列表视图中的列表视图中 唯一的问题是只有第一个列表视图正确显示所有元素 此后的每个列表视图仅包含一个元素 UPDATE 创建我自己的不可滚动列表视图解决了这个问题 https stackoverflow com
  • 在运行时用Dagger添加Retrofit RequestInterceptor

    我正在使用匕首和改装 我用 Dagger 注入我的 Retrofit 服务 现在我想做一个授权请求来获取 accessToken 之后 我想使用请求拦截器来增强我的 api 模块 以便将此访问令牌用于将来的请求 我的想法是在收到访问令牌后使
  • 首选项和操作栏中的开/关切换按钮 - 冰淇淋三明治风格

    我指的是 ICS 手机上默认 Android 设置应用程序中看到的蓝色开 关样式 也可以在这里看到 http android developers blogspot com 2012 02 android design v2 now wit

随机推荐