android菜鸟学习笔记8----Activity(一)
Activity是android应用程序中重要的组件之一,常听到的android四大组件是Activity、Service、BroadcastReceiver和ContentProvider。它间接继承自android.content.Context,因此,有些时候都直接把Activity实例当做Context的实例来使用。
如前面所提到的要在应用程序中使用Activity,必须在Android Manifest.xml中配置它。
新建一个Android工程,新建过程中勾选create activity,让系统自动帮我们创建一个Activity并在Android Manifest.xml中配置它。
AndroidManifest.xml代码如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 4 5 package="cn.csc.activity" 6 7 android:versionCode="1" 8 9 android:versionName="1.0" > 10 11 <uses-sdk 12 13 android:minSdkVersion="8" 14 15 android:targetSdkVersion="14" /> 16 17 <application 18 19 android:allowBackup="true" 20 21 android:icon="@drawable/ic_launcher" 22 23 android:label="@string/app_name" 24 25 android:theme="@style/AppTheme" > 26 27 <activity 28 29 android:name=".FirstActivity" 30 31 android:label="@string/app_name" > 32 33 <intent-filter> 34 35 <action android:name="android.intent.action.MAIN" /> 36 37 <category android:name="android.intent.category.LAUNCHER" /> 38 39 </intent-filter> 40 41 </activity> 42 43 </application> 44 45 </manifest>
配置中,Activity节点比较重要的属性有:
android:name属性:指明该Activity节点对应的Activity定义所在的类名,这里默认简写为.FisrtActivity,完整的类名需要与Manifest节点的package属性进行拼接,也可以直接写完整的类名,这里即为cn.csc.activity.FirstActivity。注意,name属性是必须配置的,否则报错,毕竟不配置name属性本身就没有任何意义了。
android:label属性:指明该Activity的标题栏显示的内容。
此外,比较重要的还有一个:
android:launchMode属性:指明该Activity的加载模式。取值可以是standard、singleTop、singleTask和singleInstance。这个属性在提到Activity生命周期时会用到。
回到FirstActivity.java的代码来看:
1 public class FirstActivity extends Activity { 2 3 @Override 4 5 protected void onCreate(Bundle savedInstanceState) { 6 7 super.onCreate(savedInstanceState); 8 9 setContentView(R.layout.first_layout); 10 11 } 12 13 }
Activity中常用的方法:
void onCreate(Bundle savedInstanceState) :这个方法在该Activity被创建时回调,进行相关的初始化工作。如,我们最常做的setContentView();设置一个UI界面。
void setContentView(int layoutResID)
void setContentView(View view) :这两个方法用于给Activity设置UI界面,只是传入的参数类型不同,一个是传入一个layout资源id,一个是直接在代码中编写UI界面。
View findViewById(int id) :根据控件的id找到该id,返回值是一个View实例,通常需要进行向下转型到具体类型,如Button、TextView等,以便对控件进行操作,如设置控件属性值,进行事件绑定等。
在first_layout.xml中添加一个按钮,需要设置按钮的id属性,在FirstActivity中的onCreate()方法中根据id获取该按钮,然后设置它的单击响应,弹出一个Toast信息。
代码如下:
first_layout.xml:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 3 xmlns:tools="http://schemas.android.com/tools" 4 5 android:layout_width="match_parent" 6 7 android:layout_height="match_parent" > 8 9 <Button 10 11 android:id="@+id/btn" 12 13 android:layout_width="wrap_content" 14 15 android:layout_height="wrap_content" 16 17 android:text="@string/btnText" 18 19 /> 20 21 </RelativeLayout>
FirstActivity.java:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 Button btn = (Button) findViewById(R.id.btn); 8 9 btn.setOnClickListener(new OnClickListener() { 10 11 @Override 12 13 public void onClick(View v) { 14 15 // TODO Auto-generated method stub 16 17 Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); 18 19 } 20 21 }); 22 23 }
void startActivity(Intent intent) :用于启动一个新的Activity,参数intent中指定了要启动Activity的相关信息。
void finish() :用于结束,并销毁当前Activity。
新建一个Activity,名为SecondActivity;新建一个layout文件,名为second_layout.xml
修改first_layout中按钮的点击事件,使它启动SecondActivity。
代码如下:
second_layout.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 4 5 android:layout_width="match_parent" 6 7 android:layout_height="match_parent" 8 9 android:orientation="vertical" > 10 11 <Button android:id="@+id/btn2" 12 13 android:layout_width="wrap_content" 14 15 android:layout_height="wrap_content" 16 17 android:text="@string/close"/> 18 19 </LinearLayout>
SecondActivity.java:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 // TODO Auto-generated method stub 4 5 super.onCreate(savedInstanceState); 6 7 setContentView(R.layout.second_layout); 8 9 Button btn2 = (Button) findViewById(R.id.btn2); 10 11 btn2.setOnClickListener(new OnClickListener() { 12 13 @Override 14 15 public void onClick(View v) { 16 17 // TODO Auto-generated method stub 18 19 finish(); 20 21 } 22 23 }); 24 25 }
FirstActivity.java:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 Button btn = (Button) findViewById(R.id.btn); 8 9 btn.setOnClickListener(new OnClickListener() { 10 11 @Override 12 13 public void onClick(View v) { 14 15 // TODO Auto-generated method stub 16 17 // Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); 18 19 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 20 21 startActivity(intent); 22 23 } 24 25 }); 26 27 }
注意:使用SecondActivity一定要在Manifest.xml中配置
<activity android:name=".SecondActivity" android:label="@string/second">
</activity>
否则会出现如下错误:
点击FirstActivity中的按钮,会启动SecondActivity,点击SecondActivity中的按钮会销毁SecondActivity,然后又回到FirstActivity中。跟点击模拟器上的返回键效果一样。
点击I am a button
点击Close或者返回键:
关于Intent的使用,将在之后详细说明。
void startActivityForResult(Intent intent, int requestCode) :用于启动一个新的Activity,并期望在这个新的Activity结束时返回数据。
void onActivityResult(int requestCode, int resultCode, Intent data) :用于接收处理启动的新的Activity结束时返回的数据。
这两个函数在Intent在Android之间传递数据时会用到。
Intent getIntent() :获取启动该Activity的意图实例,该方法可以实现获取该Activity的启动者所要传递给自己的存放在Intent中的数据。
关于管理Activity的任务栈:
Activity中有一个int getTaskId()方法 :用于获取当前Activity所处的栈的id。
修改FirstActivity中的onCreate():
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 Button btn = (Button) findViewById(R.id.btn); 8 9 Log.i("TaskId","First:"+getTaskId()); 10 11 btn.setOnClickListener(new OnClickListener() { 12 13 @Override 14 15 public void onClick(View v) { 16 17 // TODO Auto-generated method stub 18 19 // Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); 20 21 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 22 23 startActivity(intent); 24 25 } 26 27 }); 28 29 }
及SecondActivity中的onCreate():
1 protected void onCreate(Bundle savedInstanceState) { 2 3 // TODO Auto-generated method stub 4 5 super.onCreate(savedInstanceState); 6 7 setContentView(R.layout.second_layout); 8 9 Button btn2 = (Button) findViewById(R.id.btn2); 10 11 Log.i("TaskId","Second:"+getTaskId()); 12 13 btn2.setOnClickListener(new OnClickListener() { 14 15 16 17 @Override 18 19 public void onClick(View v) { 20 21 // TODO Auto-generated method stub 22 23 finish(); 24 25 } 26 27 }); 28 29 }
注意:Log.i("TaskId","First:"+getTaskId());用于在LogCat中输出程序运行信息,第一个为Tag参数,第二个要输出的字符串信息。
打开LogCat界面:window ----> show view ----> other ---->即可找到LogCat。
由于显示的运行信息比较多,可以添加一个信息过滤器,只显示我们所关心的信息。
点击绿色的加号
由于我们在之前的Log.i()中设定了Tag参数为TaskId,这时选择by Log Tag,然后填入我们所设置的TaskId即可,随便给过滤器取个名字,然后OK。
启动应用程序,若发现仍有很多运行信息:
此时,单击下TaskId会发现只剩下一条了
此时运行的是FirstActivity,显示的是FirstActivity所在的任务栈的id为15。然后点击程序中的按钮,启动SecondActivity,发现又多出一条信息:
发现SecondActivity所在的任务栈id同样为15。
一个android应用中,不可能只有一个Activity,如上面启动了两个Activity。Android中使用任务栈来管理多个Activity。
当一个Activity被创建启动时,就会把它放入一个任务栈的栈顶。当该Activity结束被销毁时,就会从所在任务栈弹出,其下的Activity变为栈顶,切换到活动状态。当有新的Activity被启动时,它会入栈称为新的栈顶,之前活动的Activity被强制切换到暂停或者停止状态。
任意时刻,只有栈顶的Activity处于活动状态,可以与用户进行交互。栈中其他Activity若是仍然可见或部分可见,即没有被当前活动Activity完全遮盖时,则处于暂停状态。若完全不可见,则处于停止状态。
一般来说,一个android应用中所有的Activity都会被放到同一个任务栈中进行统一管理,但是也有例外,如上面提到的在Manifest.xml中配置launchMode时,配置不同的值就会有所差别。
关于Activity的状态:
任意时刻,一个Activity都处于下面四个状态之一:
运行状态:位于任务栈的栈顶,此时能够与用户进行交互。
暂停状态:不再处于任务栈的栈顶,不能与用户交互,但是仍然有部分可见。
停止状态:不再处于任务栈的栈顶,不能与用户交互,而且完全不可见。
销毁状态:已从任务栈中弹出。
当系统内存不足时,会优先回收处于销毁状态的Activity所占用的资源;仍然不足时,会回收处于停止状态的Activity所占用的资源;仍然不足时,会回收处于暂停状态的Activity的资源;最不愿意回收的是运行状态的Activity资源。
关于Activity的加载模式与任务栈的关联:
前面提到android:launchMode属性:指明该Activity的启动模式。取值可以是standard、singleTop、singleTask和singleInstance。
standard模式:是默认的启动模式,若没有明确指定启动模式,则为standard模式。如上面的FirstActivity和SecondActivity都没有设置launchMode属性,即为standard模式。
在该模式下,每当新启动一个Activity时,都会为之创建一个新的实例,然后放入栈顶,而不在乎任务栈中是否已然存在该Activity的实例。
修改FirstActivity代码:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 Button btn = (Button) findViewById(R.id.btn); 8 9 Log.i("TaskId","First:"+this+" "+getTaskId()); 10 11 btn.setOnClickListener(new OnClickListener() { 12 13 @Override 14 15 public void onClick(View v) { 16 17 // TODO Auto-generated method stub 18 19 // Toast.makeText(FirstActivity.this, "I'm clicked", Toast.LENGTH_SHORT).show(); 20 21 Intent intent = new Intent(FirstActivity.this, FirstActivity.class); 22 23 startActivity(intent); 24 25 } 26 27 }); 28 29 }
Log.i("TaskId","First:"+this+" "+getTaskId());此处,加上this,打印出当前实例引用this的值。
按钮的点击响应改为启动FirstActivity自身。
运行信息:
发现每次this的值都不同,可见每次都新建了一个FirstActivity实例,即便当前FirstActivity实例已然位于任务栈中,且位于任务栈的栈顶。
singleTop模式:standard模式很多时候明显不太合理,当前Activity已然有实例位于任务栈的栈顶,直接使用不就得了,干嘛非要再创建一个实例浪费资源呢。singleTop模式就是当要启动的Activity已然有实例位于任务栈的栈顶就直接使用当前栈顶,而不重新创建实例。
修改Manifest.xml:
<activity
android:name=".FirstActivity"
android:label="@string/first"
android:launchMode="singleTop" >
修改FirstActivity代码:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 Button btn = (Button) findViewById(R.id.btn); 8 9 Log.i("TaskId","Create:"+this+" "+getTaskId()); 10 11 btn.setOnClickListener(new OnClickListener() { 12 13 @Override 14 15 public void onClick(View v) { 16 17 18 19 Intent intent = new Intent(FirstActivity.this, FirstActivity.class); 20 21 Log.i("TaskId","Click:"+FirstActivity.this+" "+getTaskId()); 22 23 startActivity(intent); 24 25 } 26 27 }); 28 29 }
Log.i("TaskId","Create:"+this+" "+getTaskId());表示是onCreate()中调用
在button的onclick中Log.i("TaskId","Click:"+FirstActivity.this+" "+getTaskId());表明是点击按钮调用。
运行信息:
发现只有一个Create,即onCreate()方法只调用了一次,只创建了一个FirstActivity实例。
若FirstActivity实例在任务栈中,但是不是在栈顶,又会如何呢?
修改代码,FirstActivity中启动SecondActivity,而SecondActivity又启动FirstActivity,两者均配置为singleTop。
修改FirstActivity代码:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 Button btn = (Button) findViewById(R.id.btn); 8 9 Log.i("TaskId","Create First:"+this+" "+getTaskId()); 10 11 btn.setOnClickListener(new OnClickListener() { 12 13 @Override 14 15 public void onClick(View v) { 16 17 18 19 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 20 21 startActivity(intent); 22 23 } 24 25 }); 26 27 }
修改SecondActivity代码:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 // TODO Auto-generated method stub 4 5 super.onCreate(savedInstanceState); 6 7 setContentView(R.layout.second_layout); 8 9 Button btn2 = (Button) findViewById(R.id.btn2); 10 11 Log.i("TaskId","Create Second:"+this + " "+getTaskId()); 12 13 btn2.setOnClickListener(new OnClickListener() { 14 15 16 17 @Override 18 19 public void onClick(View v) { 20 21 // TODO Auto-generated method stub 22 23 Intent intent = new Intent(SecondActivity.this, FirstActivity.class); 24 25 startActivity(intent); 26 27 } 28 29 }); 30 31 }
运行信息:
发现每次都新建了一个实例,即使当前Activity在任务栈中已然存在,但由于并没与处于栈顶,就要再次创建新的实例。
singleTask模式:可能会觉得singleTop模式还是浪费资源,明明栈中已然存在实例,只因为它不在栈顶便要重新创建。若是想重用不在栈顶的Activity实例,则需要使用singleTask模式,该模式下,新启动一个Activity时,检查当前任务栈中是否存在实例,若存在,但是不在栈顶也没关系,系统会把所有在这个实例之上的Activity实例统统出栈,这样这个实例就处于栈顶,然后就可以直接重用了。
修改Manifest.xml将两个Activity的launchMode都改为singleTask,源代码不需要改动,此时观察运行信息:
启动FirstActivity时,输出一条信息,点击FirstActivity中的按钮,由于SecondActivity在栈中不存在,创建了一个实例,然后点击SecondActivity中的按钮,发现FirstActivity实例已然存在,但是栈顶是SecondActiviy,则会将SecondActivity实例出栈,直接重用已存在的FirstActivity实例,所以,此时没有回调onCreate(),故而没有输出信息。
singleInstance模式:用于共享Activity时使用。设置为该模式的Activity不会与当前应用中的其他Activity公用一个任务栈,而是出于自己单独的任务栈中。而几个公用这个Actiivty的应用,共享这个单独的任务栈,就实现了该Activity实例的共享。不然的话,每个应用都有自己的任务栈,启动的Activity肯定在自己的任务栈中管理,根本做不到Activity实例的共享。
修改程序,添加一个ThirdActivity,程序运行的效果FirstActivity为入口,在其中点击按钮可以启动SecondActivity,SecondActivity的launchMode设置为singleInstance,点击SecondActivity中的按钮,可以启动ThirdActivity。ThirdActivity中的按钮可以启动SecondActivity。
FirstActivity和ThirdActivity的launchMode均设置为singleTask。
运行信息:
FirstActivity和ThirdActivity都在同一个栈中,id为28
而SecondActivity在id为29的栈中。
注意,此时,点击ThirdActivity中的按钮启动SecondActivity时,不会输出任何信息,因为直接重用了id为29的任务栈中的SecondActivity实例。若在ThirdActivity时,按下模拟器的返回按钮,销毁ThirdActivity,则会直接回到FirstActivity中,因为它俩是处在同一个栈中的。