一、Activity
1.1、Activity介绍
Activity 是用户接口程序,原则上它会提供给用户一个交互式的接口功能。它是 android 应用程序的基本功能单元。Activity 本身是没有界面的。所以activity
类创建了一个窗口,开发人员可以通过setContentView(View)接口把UI放到activity创建的窗口上,当activity指向全屏窗口时,也可以用其他方式实现:作为漂浮
窗口(通过windowIsFloating的主题集合),或者嵌入到其他的activity(使用ActivityGroup)。activity是单独的,用于处理用户操作。
1.2、清单文件配置
<intent-filter> 代表的应用程序的入口界面 <action android:name="android.intent.action.MAIN" /> 应用程序在桌面上会产生一个快捷图标 <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
一个应用程序不但可以有多个界面外,还可以在桌面上有多个图标。
1.3、Intent(意图)
1.显示意图
显示意图只能调用当前应用程序的界面,也就是能获取到字节码的类。
Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent);
2.隐式意图
隐式意图不但可以调用当前应用程序的界面,还能调用其他应用程序的界面,比如系统自带的拨号,发送短信...
a)指定动作,可以有多个,且区分大小写。系统预定义部分action,当然动作是可以自定义的:intent.setAction("自定义")。
b)设置数据,intent.setData(前缀):需要在清单文件中进行声明。
c)在目标activity中使用getIntent()获取Intent实例,以及getData()来获取发送过来的数据。
注:在代码和清单文件中都可以配置过滤器,在清单文件配置data可以更精准的指定当前活动能响应什么类型的数据。
data的语法格式如下:
<intent-filter > <data android:scheme="string" android:host="string" android:port="string" android:path="string" android:pathPattern="string" android:pathPrefix="string" android:mimeType="string"/> </intent-filter>
data由两部分组成,mimeType和URI,其中mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic或video/*等,可以表示图片、文本、适配等
不同的媒体格式,而URI包含的数据则比较多,下面是它的结构:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
下面看看示例,结构就非常清晰:
content://com.example.project:200/folder/subfolder/etc
或
http://www.baidu.com:80/search/info
ath、PathPrefix、PathPattern:它们都表示路径信息,path和pathPattern都表示完整路径信息,其中pathPattern可以包含通配符“*”,
“*”表示0个或多个任意字符。由于正则表达式的规范,如果想表示真实的字符串,那么“*”要写成“\\*”,“\”写成“\\\\”;
pathPrefix则表示路径前缀信息。
a)过滤规则实例1
<intent-filter> <data android:mimeType="image/*"/> ... </intent-filter>
这种情况没有指定URI,但是URI默认值为content和file。那么匹配如下:
intent.setDataAndType(Uri.parse("file://abc"), "image/png");
如果要为Intent指定完整的data,必须调用setDataAndType方法,不能单一的调用setData和setType,它们会彼此清除对方。
b)过滤规则实例2
<intent-filter> <data android:mimeType="video/mpeg" android:scheme="http" .../> <data android:mimeType="audio/mpeg" android:scheme="http" .../> ... </intent-filter>
这种规则指定了两组data规则,且每个data都指定完整的属性值,匹配如下:
intent.setDataAndType(Uri.parse("http://abc"), "video/mpeg");
或
intent.setDataAndType(Uri.parse("http://abc"), "audio/mpeg");
c)两种特殊写法
<intent-filter ...> <data android:scheme="file" android:host="www.baidu.com"/> </intent-filter> 或 <intent-filter ...> <data android:scheme="file"/> <data android:host="www.baiud.com"/> </intent-filter>
这是和action不同的地方,其实作用是一样的。
1.当我们通过隐式意图启动Activity时,可以做一下判断,查看是否有Activity能够匹配我们隐式的Intent,判断有两种方式:
a) PackageManager的resolveActivity()方法。
b) Intent的resolveActivity()方法。
PackageManager manager = this.getPackageManager(); manager.resolveActivity(intent, flags);
它们如果找不到符合的Activity则会返回null。
2.其中PackageManager还提供有queryIntentActivities()方法,它返回的是所有成功匹配的Activity。
PackageManager manager = this.getPackageManager(); manager.queryIntentActivities(intent, flags);
resolveActivity方法和queryIntentActivities方法第一个参数是intent,第二个参数 MATCH_DEFAULT_ONLY
该标记位仅仅匹配在过滤器中配置默认类别的Activity:
<category android:name="android.intent.category.DEFAULT"/>
下面我们来可以看下面这个打开浏览器的例子:
Intent intent = new Intent(); // 设置Action intent.setAction("android.intent.action.VIEW"); // 设置category intent.addCategory("android.intent.category.BROWSABLE"); // 设置参数 intent.setData(Uri.parse("http://www.baidu.com")); startActivity(intent);
清单文件配置:
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAUT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android :scheme="http"/> <data android :scheme="https"/> <data android :scheme="about"/> <data android :scheme="javascript"/> </intent-filter>
注:
- 隐式调用时,代码中的action、category必须保持一致,否则无法正确匹配。
1.4、Intent通信
1.传递数据
Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivityForResult(intent, 1);
startActivityForResult()方法来启动 SecondActivity,请求码只要是一个唯一值即可。
2.接收数据
Intent intent = new Intent(); intent.putExtra("data_return", "Hello FirstActivity"); setResult(RESULT_OK, intent); finish();
setResult()方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用 RESULT_OK 或RESULT_CANCELED 这两个值
3.接收反馈
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case 1: if (resultCode == RESULT_OK) { String returnedData = data.getStringExtra("data_return"); Log.d("FirstActivity", returnedData); } break; } }
onActivityResult()方法带有三个参数:
第一个参数 requestCode,即我们在启动活动时传入的请求码。
第二个参数 resultCode, 即我们在返回数据时传入的处理结果。
第三个参数 data,即携带着返回数据的 Intent。
4.返回键的隐患
用户在数据达到的目标界面如果按下返回键,那么,数据就无法反馈给发送的界面,所以在目标界面重写返回键的方法。
@Override public void onBackPressed() { Intent intent = new Intent(); intent.putExtra("data_return", "Hello FirstActivity"); setResult(RESULT_OK, intent); finish(); }
1.5、生命周期
1.四种状态
(1)运行状态:活动位于返回栈的栈顶时,这时活动就处于运行状态。
(2)暂停状态:当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。
(3)停止状态:当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。
(4)销毁状态:当一个活动从返回栈中移除后就变成了销毁状态。
2.七个方法
以上七个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期:
(1)完整生存期:活动在 onCreate()方法和 onDestroy()方法之间所经历的,就是完整生存期。
(2)可见生存期:活动在 onStart()方法和 onStop()方法之间所经历的,就是可见生存期。
(3)前台生存期:活动在 onResume()方法和 onPause()方法之间所经历的,就是前台生存期。
Ps:当启动一个新的Activity时,旧Activity会先调用onPause()方法,才会执行新Activity的回调方法。
3.configChanges配置
它有很多属性,类似于屏幕尺寸改变,外接键盘接入...等一系列操作,这里不详细说明。
横竖屏切换时,默认情况会把activity先销毁再创建,这样就影响到正常的生命周期。
4.0之前:
android:configChanges="orientation|keyboardHidden"
4.0之后
android:configChanges="orientation|screenSize"
兼容所有
android:configChanges="orientation|keyboardHidden|screenSize"
当配置该属性后,横竖屏切换不会影响生命周期,但是会调用onConfigurationChanged方法:
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 做一些自己的操作 }
4、异常生命周期
a) 当旋转屏幕导致生命周期改变,会销毁当前活动,再创建一个新的活动。
b) 当程序位于后台,在资源不足的情况下被回收,活动也会重建。需要注意保存数据并回显。
Ps:我们可以通过onSaveInstance或onRestoreInstance()方法来保存一些临时数据,切忌保存Bundle(有时候取出为null)。
1.6、返回栈
Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,
在默认情况下,当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。每当我们按下 Back 键或调用 finish()方法去销毁一个活动时,处于栈顶
的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
1.7、启动模式
1、启动模式的配置:
a) 清单文件中为activity配置启动模式:
<activity android:name="cn.legend.toggleview.FirstActivity" android:launchMode="standard" > </activity>
b) 代码中使用Intent的标记位来动态设置activity启动模式:
Intent intent = new Intent(getApplicationContext(), SecondActivity.class); //setFlags方法是替换掉之前的标识位,addFlags是添加一个标识位。 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
c) 区别:
- 第二种方式比第一种方式的优先级高,当两种同时存在时以第二种为准。
- 第一种方式无法设定Intent.FLAG_ACTIVITY_CLEAR_TOP标记位,第二种无法指定singleInstance模式。
2、四种启动模式
3、Activity的Flags
除了在清单文件设置启动模式外,我们还可以通过Intent来设置Activity的启动模式
/**启动的Activity都在新的任务栈中,通常用于Service启动Activity的场景*/ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); /**和SingleTop启动模式的效果相同*/ intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); /**和SingleTask启动模式的效果相同*/ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); /**当该Activity启动其他Activity后,该Activity会移除任务栈中*/ intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
4、清空任务栈
通常情况下,可以在清单文件中的<activity>标签中使用以下属性来清理任务栈:
- clearTaskOnLaunch
每次返回该Activity时,都将该Activity之上所有Activity清除,可以让任务栈每次初始化时,都只有这一个Ativity。
- finishOnTaskLaunch
该属性和clearTaskOnLaunch类似,它的作用是针对自己的,当离开这个Activity所处的Task,那么用户返回时,该Activity会销毁。
- alwaysRetainTaskState
相当于免死金牌,如果将该属性设置为true,那么该Activity所在的Task将不接受任何清理命令。
5、ApplicationContext不具备任务栈:
当我们使用ApplicationContext去启动standard模式的Activity时,Activity会报错:
因为当ActivityA启动ActivityB(Standard模式)时,ActivityB会进到ActivityA的任务栈中,由于ApplicationContext是非Activity类型的Context,
并没有所谓的任务栈,所以就出现问题。解决方式是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动时就创建一个新的任务栈。
但是,此时待启动的Activity实际上是以singleTask模式启动。
Intent intent = new Intent(getApplicationContext(), SecondActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
6、onNewIntent的触发时机:
当一个应用程序加载singleTask或singleTop的Activity时,首先该Activity会检查是否存在相同的任务栈。
a)存在,那么检查是否实例化,如果已经实例化,那么销毁在该Activity以上的Activity并调用onNewIntent。如果没有实例化,那么该Activity实例化并入栈。
b)不存在,那么就重新创建任务栈,并入栈。
LaunchMode=""SingleTask" taskAffinity="com.tencent.mm" (com.tencent.mm是借助于工具找到的微信包名)
把自己的Activity放到微信默认的Task栈里面,回退时就会遵循“Task只要有Activity一定从本Task剩余Activity回退"的原则,不会回到自己的客户端。
7、TaskAffinity
TaskAffinity可以翻译成任务相关性,这个参数标识了Activity所需的任务栈名字,默认情况下,所有Activity所需的任务栈名字为应用的包名。
当然我们也可以指定TaskAffinity属性,但是必须不能和应用包名相同,否则就相当于没有指定。
它主要和SingleTask模式或allowTaskReparenting属性配置使用,且任务栈分为前台任务栈和后台任务栈:
这个涉及到跨进程的任务栈知识,等待后期慢慢理解。
1.8、保存临时数据
当Activity进入后台就有可能被系统未经许可就回收,如此一来我们的数据和状态都会丢失,Android为我们提供了恢复数据的机制。
1.触发时机
a)当某个Activity被系统销毁时,该Activity的onSaveInstanceState就会被执行,如果是用户主动销毁时则不会执行该方法。
@Override public void onSaveInstanceState(Bundle savedInstanceState){ super.onSaveInstanceState(savedInstanceState); savedInstanceState.putString("message", text.getText().toString()); }
b)当某个Activity被系统销毁并重新创建时会调用onRestoreInstanceState方法,它和onSaveInstance不一定成对出现。
@Override public void onRestoreInstanceState(Bundle savedInstanceState){ super.onRestoreInstanceState(savedInstanceState); message = savedInstanceState.getString("message"); }
c)onRestoreInstanceState的bundle参数也会传递到onCreate方法中,这里需要判断是否为空,以此判定是第一次创建活动还是被系统回收后回显数据的操作。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState != null){ message = savedInstanceState.getString("message"); } }
Ps:
目前除了SharePerences或Sqlite来存储全局变量外,其他任何方法都不靠谱,onSaveInstanceState和onRestoreInstanceState方法只能保存临时数据。
当Activity进入后台就会调用onSaveInstanceState,它在onStop前调用,可能在onPause前后,并且它不是Activity生命周期的一部分。
当Activity在未经允许的情况下被系统回收时,Activity会被重建,此时onRestoreInstanceState会被回调,它的调用时机在onStart后。
1.9、管理活动退出
a)我们创建一个ActivityController类用作管理Activity退出的逻辑类
public class ActivityController { // 活动管理器 public static List<Activity> mActivities = new ArrayList<Activity>(); // 添加活动的方法 public static void addActivity(Activity activity){ mActivities.add(activity); } // 移除活动的方法 public static void removeActivity(Activity activity){ mActivities.remove(activity); } // 移除所有活动 public static void finishAll(){ // 对集合进行迭代判断状态 for (Activity activity : mActivities) { if(!activity.isFinishing()){ activity.finish(); } } } }
b)如果我们想随时随地的退出应用的话,只需要调用ActivityController.finishAll()方法即可。
2.0、启动活动封装
我们在项目中会频繁的涉及到界面的跳转,所以我们可以将跳转的代码进行封装,这样优化了代码结构
public class StartIntentUtils { /** * 正常开启Activity * @param context 上下文 * @param clazz 要开启的活动的字节码 */ public static void startActivity(Context context,Class clazz){ Intent intent = new Intent(context,clazz); context.startActivity(intent); } /** * 开启Activity,并传递数据 * @param context 上下文 * @param clazz 要开启活动的字节码 * @param data1 传递的数据1 * @param data2 传递的数据2 */ public static void startActivity(Context context,Class clazz,String data1,String data2){ Intent intent = new Intent(context, clazz); intent.putExtra("param1", data1); intent.putExtra("param2", data2); context.startActivity(intent); } }
2.1、活动切换动画
a)Activity的过渡动画是通过overridePendingTransition实现的,该方法会在startActivity(intent)或finish()之后调用。
@Override public void overridePendingTransition(int enterAnim, int exitAnim) { super.overridePendingTransition(enterAnim, exitAnim); }
第一个参数:eAnim,是新的Activity的进入动画的resourceID。
第二个参数:exitAnim,是旧的Activity(当前Activity)离开动画的resourceID。
b)其中所需传入的动画定义在res\anim\目录下,下面看下其中动画示例
slide_in_bottom文件:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="2000"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="2000" /> </set>
slide_oit_bottom
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="0%p" android:toYDelta="100%p" android:duration="2000"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="2000" /> </set>
c)我们调用的时候只需要在跳转后调用该方法就可以
Intent intent = new Intent(TestActivities.this,TestActivityFirst.class); startActivity(intent); // transaction animation overridePendingTransition(R.anim.slide_in_bottom,R.anim.slide_out_bottom);
2.2、Activity对话框
使用Activity当做弹出对话框有以下好处:
- 显示位置的设置,直接就是layout.xml,可以实现任何效果以及出现在任意位置。
- 对话框内控件的事件处理都独立在一个类中,只需要startActivity()既可调用,代码结构更加清晰。
a) 编写对话框的布局效果
<LinearLayout android:onClick="tip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_marginTop="46dp" android:background="@drawable/title_function_bg" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:drawableLeft="@drawable/mm_title_btn_compose_normal" android:paddingLeft="10dp" android:paddingRight="10dp" android:text="发起聊天" android:textColor="#fff" android:textSize="20sp" /> </LinearLayout>
b)在style.xml中定义一个theme(背景透明,无标题,动画效果),一般Activity默认动画效果右进右出,我们可以覆盖。
<style name="MyDialogTopRight"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowNoTitle">true</item> <item name="android:windowAnimationStyle">@style/Anim_scale</item> </style>
动画效果:
<style name="Anim_scale" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/scale_in</item> <item name="android:activityOpenExitAnimation">@anim/scale_out</item> <item name="android:activityCloseEnterAnimation">@anim/scale_in</item> <item name="android:activityCloseExitAnimation">@anim/scale_out</item> </style>
在清单文件配置对话框样式的Activity
<activity android:name=".MainWeixinTitleRightActivity" android:theme="@style/MyDialogTopRight" > </activity>
c) 在需要使用的地方直接startActity()即可。