Android四大组件之Activity

没有难点,没有不会的,只是匆匆的留个笔记。。。。。
简介

Activity是Android一个非常重要的用户接口(四大组件之一),是可见的,主要是用户和应用程序之间进行交互的接口。在每个Activity中都可以放很多控件,所以也可以把Activity看作控件的容器,一般作为App的入口

生命周期
public class MainActivity extends AppCompatActivity {
    private final String TAG = "===>>>" + this.getClass().toString() + "==";

    /**
     * Activity创建时被调用
     *
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");
    }

    /**
     * Activity从后台重新回到前台时被调用
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

    /**
     * Activity系将可见时调用
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    /**
     * Activity处于交互状态,可见
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }

    /**
     * Activity即将不可见的时候,其他Activity获得用户焦点,(Activity快要暂停了)
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }

    /**
     * Activity不可见的时候,已经跳转到了新Activity,旧的Activity不再可见,处于停止状态
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }

    /**
     * 退出当前Activity时被调用,Activity快要被销毁了,调用之后Activity就结束了
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    /**
     * Activity窗口获得或失去焦点时被调用,在onResume之后或onPause之后
     *
     * @param hasFocus
     */
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        Log.d(TAG, "onWindowFocusChanged");
    }

    /**
     * Activity被系统杀死时被调用.
     * 例如:屏幕方向改变时,Activity被销毁再重建;当前Activity处于后台,系统资源紧张将其杀死.
     * 另外,当跳转到其他Activity或者按Home键回到主屏时该方法也会被调用,系统是为了保存当前View组件的状态.
     * 在onPause之前被调用.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, "onSaveInstanceState");
    }

    /**
     * Activity被系统杀死后再重建时被调用.
     * 例如:屏幕方向改变时,Activity被销毁再重建;当前Activity处于后台,系统资源紧张将其杀死,用户又启动该Activity.
     * 这两种情况下onRestoreInstanceState都会被调用,在onStart之后.
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState");
    }

}
注意点:
  • 当用户打开新的Activity或切换回到桌面的时候,回调为onPause->onStop,但是若Activity采用的为透明主题,则不会回调onStop。
  • 一般情况下,Activity有不可见变为可见,onRestart才会调用。
  • onStart和onStop控制Activity在可见和不可见的状态之间转换,onResume和onPause控制Activity在前台或非前台之间转换。
  • 当由Activity A ->Activity B时,回调的顺序为onPause(A)->onCreate(B)->onStart(B)->onStop(A),因此不可以在onPause上做重量级操作。
  • 如果应用长时间处于stopped状态并且此时系统内存极为紧张的时候,系统就会回收Activity,此时系统在回收之前会回调onSaveInstanceState方法来保存应用的数据Bundle。当该Activity重新创建的时候,保存的Bundle数据就会传递到onRestoreSaveInstanceState方法和onCreate方法中,这就是onCreate方法中Bundle savedInstanceState参数的来源(onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原)。

任务栈和四种启动模式

应用内的Activity是被任务栈Task来管理的,一个Task中的Activity可以来自不同的应用,同一个应用的Activity也可能不在同一个Task中。默认情况下,任务栈依据栈的后进先出原则管理Activity,但是Activity可以设置一些“特权”打破默认的规则,主要是通过在AndroidManifest文件中的属性android:launchMode或者通过Intent的flag来设置。

  • standard

standard模式是默认的启动模式,不用为<activity>配置android:launchMode属性即可,当然也可以指定值为standard。
standard模式是所启动的Activity都是在同一个task容器栈下,不会重新创建新的task容器栈。先压入栈的Activity实例按顺序入栈底,后入栈在栈顶,处于栈的顶部Activity实例处于活动状态,其他处于非活动状态。按物理返回键,退出当前所处活动状态Activity窗口,这样就会从task容器栈中弹出,显示在手机主屏幕上,从而,有非活动状态转换成活动的状态。其次,standard容器栈可能会存在着相同的Activity实例,只有没调用一次startActivity方法,就会创建目标Activity实例对象压入task容器栈。

  • singleTop

AndroidManifest.xml文件中<activity>launchmode属性配置singletop,那么启动实例化Activity,如果task容器栈顶存在已经激活的Activity实例,就会重用当前栈顶的Activity实例,不会再重新去实例化Activity对象。善于思考的朋友可能会问,如果要启动的目标Activity已经有实例化对象存在task容器栈里面,只是现在不处于栈顶,这样情况下,singletop启动模式会创建目标Activity实例吗?答案是肯定的。要启动的目标Activity实例正好处于栈顶,才能重用该实例,其他情况必须创建新实例。

  • singleTask

singletask模式,特别需要注意了。启动的目标Activity实例如果已经存在task容器栈中,不管当前实例处于栈的任何位置,是栈顶也好,栈底也好,还是处于栈中间,只要目标Activity实例处于task容器栈中,都可以重用该Activity实例对象,然后,把处于该Activity实例对象上面全部Activity实例清除掉,并且,task容器栈中永远只有唯一实例对象,不会存在两个相同的实例对象。所以,如果你想你的应用不管怎么启动目标Activity,都只有唯一一个实例对象,就使用这种启动模式。

  • singleInstance

singleInstance启动模式,简单说就是可以共享某个Activity。比如,应用1的任务容器栈中创建了MainActivity实例,应用2也要激活MainActivity,则不需要创建MainActivity实例,直接可以公用MainActivity实例。
尤其值得注意:应用1启动MainActivity,按home键;打开应用2启动应用1的MainActivity实例。在按home键,打开应用1,这时候应用1的界面是应该是处于MainActivity界面实例。

  • Intent Flag启动模式

(1) Intent.FLAG_ACTIVITY_NEW_TASK:使用一个新的task来启动Activity,一般用在service中启动Activity的场景,因为service中并不存在Activity栈。
(2) Intent.FLAG_ACTIVITY_SINGLE_TOP:类似andoid:launchMode="singleTop"
(3) Intent.FLAG_ACTIVITY_CLEAR_TOP:类似andoid:launchMode="singleTask"
(4) Intent.FLAG_ACTIVITY_NO_HISTORY:使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在task栈中。例如A B,在B中以这种模式启动C,C再启动D,则当前的task栈变成A B D。

Intent类型
  • 显式调用startActivity(new Intent(Activity_A.this, Activity_B.class));
    • 显式Intent,可以通过类名来找到相应的组件,在应用中用显式Intent去启动一个组件,通常是因为我们知道这个组件(Activity或者Service)的名字。如下代码,我们知道具体的Activity的名字,要启动一个新的Activity,(这里就是Activity_B),当调用了startActivity(itent)后,我们就只会很明确的知道,这次的任务是启动Activity_B,而没有其它的过程。
  • 隐式调用
 Intent intent=new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+"14790666666"));  
 startActivity(intent);  


  - 隐式Intent,不指定具体的组件,但是它会声明将要执行的操作,从而匹配到相应的组件。最简单的Android中调用系统 调用拨号功能的操作,就是隐式Intent。
IntentFilter的匹配规则
  • IntentFilter的意思就是意图过滤器,当我们隐式的启动系统组件的时候,就会根据IntentFilter来筛选出合适的进行启动。
  • action的匹配规则
    action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action,acgtion区分大小写。action的匹配规则是Intent中的action必须能够和过滤规则中的action字符串完全一样。一个过滤中可以有多个action,Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。
  • category的匹配规则
    category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category要求Intent中如果含有category,不管有多少个,都必须和过滤规则中的其中有定义的category相同。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。原因是系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上”android.intent.category.DEFAULT”这个category,所以这个category就可以匹配前面的过滤规则中的第三个category。同时,为了我们的activity能够接收隐式调用,就必须在intent-filter中指定”android.intent.category.DEFAULT”这个category。即是说,在Intent中可以没有category,但有的情况下,哪怕是其中一个都不能无中生有。
  • data的匹配规则
    data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。
<data android:scheme="string"  
android:host="string"  
android:port="string"  
android:path="string"  
android:pathPattern="string"  
android:pathPrefix="string"  
android:mimeType="string"/>  
Uri的格式

Uri uri = Uri.parse("scheme://host:port/pathPrefix /pathPattern")<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]两者是相同的,为了方便看清楚

  • scheme:URI的模式,比如http、file、content等。如果URI中没有指定scheme,那么整个URI的其它参数无效,这也意味着URI是无效的。
  • host: URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。
  • port:URI中的端口号,比如80,仅当URI中指定了schemehost参数的时候port参数才是有意义的。
  • path、pathPatternpathPrefix: 这三个参数表述路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“*”“*”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“*”要写成 “\\*”,”\”要写成”\\\\”pathPrefix表示路径的前缀信息。
  • mineType:告诉Android系统本Activity可以处理的文件的类型。如设置为“text/plain”表示可以处理 “.txt”文件。
data的匹配规则多种情况
<!--过滤-->
<intent-filter>  
   <data android:mimeType="image/*"/>  
</intent-filter>  

//匹配实例
intent.setDataAndType(Uri.parse("file://abc"),"image/png")  
这种规则mimeType属性必须为“image/*”才能匹配,虽然过滤规则没有指定URI,但是却有默认值,URI的默认值为contentfile
//过滤规则
<intent-filter>  
    <dataandroid:mimeTypedataandroid:mimeType="video/mpeg" android:scheme="http" .../>  
    <dataandroid:mimeTypedataandroid:mimeType="audio/mpeg" android:scheme="http" .../>  
 </intent-filter>  

//匹配实列
intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg")  
//或者如下也可以
intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg")  
  • 特殊写法(如下两种特殊写法,它们的作作是一样的: )
<intent-filter …>  
   <data android:scheme="file" android:host=”www.baidu.com”/>  
   …  
</intent-filter>  

//和
<intent-filter …>  
   <data android:scheme="file" />  
   <data android:host=”www.baidu.com”/>  
   …  
</intent-filter> 
错误规避
当我们通过隐式方式启动一个Activity时,如果没有匹配Activity则会出现错误,所以我们在匹配前可以使用PackageManagerresolveActivity方法或者IntentresolveActivity方法。如果它们找不到匹配的Activity则返回nullPackageManager还提供了queryIntentActivities方法,这个方法不是返回最佳匹配的Activity信息,而是返回所有成功匹配的Activity信息。
//可以在startActivity前,先判断Intent是否有效,可以这样
private static boolean isIntentAvailable(Context context, Intent intent) {  
    if (intent == null) {  
        return false;  
    }  
    boolean isAvailable = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;  
    return isAvailable;  
}  
自定义Activity,并隐式方式启动
  • 我们都知道了,如果要想自定义隐式启动别的activity,需要给该Activity添加“意图过滤器<intent-filter>
<intent-filter>
     <action android:name="com.android.test" />
     <category android:name="android.intent.category.DEFAULT" />
     <data
       android:host="www.jianshu.com"
       android:mimeType="text/plain"
       android:path="/u/af49bd713636"
       android:scheme="https" />
</intent-filter>
  • <action>标签中我们指明了当前活动可以响应"com.android.test" 这个 action,
    <data>中指定了URL的schemehttps,并指定主机名URL为www.jianshu.com,这里没有指定端口所以端口出无效果,下来指定了路径信息path/u/af49bd713636,为了表达真实的串分别用"/"表示路径的前缀,也指定了数据的处理类型mimeTypetext/plain
  • 下来看看匹配实例
 Intent intent = new Intent();
intent.setAction("com.android.test");
//因为data和type分开设置后面的会覆盖前面的所以在一起写
intent.setDataAndType(Uri.parse("https://www.jianshu.com/u/af49bd713636"),"text/plain");
//可以在startActivity前,先判断Intent是否有效
if (isIntentAvailable(MainActivity.this, intent)){
       startActivity(intent);
 }else {
       Log.d("===>>>","Intent无效");
 }

通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的 action
和 category,打开 AndroidManifest.xml,添加如下代码:

入口Activity
二者共同作用是用来标明这是一个入口Activity并且会出现在系统的应用列表中,少了任何一个都没有实际意义。
<actionandroid:nameactionandroid:name=”android.intent.action.MAIN” />  
<categoryandroid:namecategoryandroid:name=”android.intent.category.LAUNCHER” />