一、四大组件
Android系统四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供器(Content Provider)。其中活动是所有Android应用程序的门面,凡是在应用中你看到的东西,都是放到活动中的。而服务就低调了,你无法看到它,但它会一直在后台默默的运行,即使用户退出了应用,服务仍然是可以继续运行的。广播接收器允许你的应用接收来自各处的广播消息,比如电话、短信等,当然你的应用同样也可以向外发出广播消息。内容提供器则为应用程序之间共享数据提供了可能,比如你想要读取系统电话簿中的联系人,就需要通过内容提供器来实现。
1 活动
1.1 活动是什么
活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互 。如果你曾经用 C,C++ 或者 Java 语言编程,你应该知道这些程序从 main() 函数开始。很类似的,Android 系统初始化它的程序是通过活动中的 onCreate() 回调的调用开始的。
1.2 活动的基本用法
1.2.1 在AndroidManifest文件中注册
所有的活动都需要在AndroidManifest.xml中进行注册,活动的注册生命要放在<application>标签内,此外还需要配置主活动,就是在<activity>标签的内部加入<intent-filter>标签,并在标签里添加<action android:name="android.intent.action.MAIN" />和<category android:name="android.intent.category.LAUNCHER" />。另外,如果你的应用程序没有声明任何一个活动作为主活动,这个程序仍然是可以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他应用在内部进行调用的,如支付宝快捷服务。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".FirstActivity"
android:launchMode="singleInstance"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
1.2.1 在活动中使用Toast
Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户。
点击按钮button1弹出一个Toast。通过静态方法makeText()创建出一个Toast对象,然后调用show()将Toast显示出来。makeText()方法需要传入3个参数。第一个参数是Context,也就是Toast要求的上下文,由于活动本身就是一个Context对象,因此这里直接传入FirstActivity.this。第二个参数是Toast显示的文本内容,第三个参数是显示的时长。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View c) {
Toast.makeText(FirstActivity.this, "You clicked Button 1", Toast.LENGTH_SHORT).show();
}
});
}
1.2.1 在活动中使用Menu
手机和电脑不同,手机的屏幕空间有限,因此充分地利用屏幕空间在手机界面中就显得非常重要了。Android给我们停供了一种方式,可以让菜单都能得到展示的同时,还不占用任何屏幕空间。
首先在res目录下新建menu文件夹,然后再menu文件夹下面新建menu.xml文件,在里面加如下代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"></item>
<item android:id="@+id/removie_item"
android:title="Removie"></item>
</menu>
这里我们添加了两个菜单选项,接着回到活动中,重写onCreateOptionsMenu()方法。通过getMenuInflater()方法能够得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了。
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
1.3 使用Intent再活动之间穿梭
Intent是Adnroid程序中各组件之间交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于 启动活动、启动服务以及发送广播场景等。我们首先构建出一个Intent ,传入(FirstActivity.this作为上下文,传入SecendActivity.class作为目标活动,即在FirstActivity这个活动的基础上打开SecendActivity这个活动。然后通过startActivity()方法来执行这个Intent 。
1.3.1 使用显示Intent
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View c) {
Intent intent = new Intent(FirstActivity.this,SecendActivity.class);
startActivity(intent);
}
});
1.3.2 使用隐式Intent
相比显示Intent,它并不明确指出我们想要启动哪个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。
通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的action和categroy,打开AndroidManifest.xml,添加代码:
<activity android:name=".SecendActivity">
<intent-filter>
<action android:name="com.example.activityest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
在<action>标签中我们指明了当前活动响应com.example.activityest.ACTION_START这个action,而<categroy>标签中包含了一些附加信息,更精确地指明了当前的活动能够响应得Intent中还可能带有的categroy。只有<action>和<categroy>中的内容同时匹配上Intent中指定的action和categroy时,这个活动才能响应应该的Intent。
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View c) {
Intent intent = new Intent("com.example.activityest.ACTION_START");
startActivity(intent);
}
});
直接将action的字符串传进去,表明想要启动能够响应com.example.activityest.ACTION_START这个action活动,categroy是默认值,所以能同样启动SecendActivity。
1.3.3 更多隐式Intent的用法
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动。
比如说你的应用程序需要展示一个网页,只需要调用系统的浏览器来打开就行了。我们首先指定了Intent的action是Intent.ACTION_VIEW,这是Android系统的内置动作,其常量值为android.Intent.ACTION_VIEW。然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View c) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com/"));
startActivity(intent);
}
});
与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指定当前活动能够响应什么类型的数据。<data>标签中主要可以配置以下内容。
- android:scheme。 用于指定数据的协议部分,如上例中的http部分
- android:host。 用于指定数据的主机名部分,如上例中的www.baidu.com部分
- android:port。 用于指定数据的端口部分,一般在主机名之后
- android:path。 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容
- android:mimeType。 用于指定可以处理的数据类型,允许使用通配符的方式进行指定
1.3.4 向下一个活动传递数据
在启动活动时传递数据的思路很简单,Intent中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就可以了。比如说再FirstActivity中有一个字符串,现在想把这个字符串传递到SecondActivity中,
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View c) {
String data = "hello SecendActivity";
Intent intent = new Intent(FirstActivity.this, SecendActivity.class)
intent.putExtra("extra_data", data);
startActivity(intent);
}
});
然后再SecondActivity中将数据取出,首先通过getIntent()方法获取到用于启动SecondActivity的Intent,然后调用getStringExtra()方法,传入相应的键值,就可以获得传递的数据了。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.secend_layout);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
}
1.3.5 返回数据给上一个活动
Activity中有一个startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁时候能够返回一个结果给上一个活动。
startActivityForResult()方法接收两个参数,第一个参数是Intent,第二个参数是请求码,用于在之后回调中判断数据的来源。修改FirstActivity。
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View c) {
Intent intent = new Intent(FirstActivity.this, SecendActivity.class);
startActivityForResult(intent, 1);
}
});
然后在SecondActivity中添加返回数据的逻辑。先构建了一个Intent用于传递数据,然后将要传递的数据放在Intent中,然后调用了setResult()方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只会用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把带有数据的Intent传递回去,然后调用finish()方法来销毁当前活动。
Button button2 = (Button) findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("data_return", "hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
});
由于使用startActivityForResult()方法来启动SecondActivity,在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity中重写这个方法来得到返回的数据。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
}
break;
default:
}
}
onActivityResult()方法带有三个参数,第一个参数requestCode,即请求码,我们需要通过其来判断数据来源。第二个参数resultCode,即我们在返回数据时传入的处理结果。第三个参数data,即携带返回数据的Intent。
1.4 活动的生命周期
1.4.1 返回栈
Android中的活动是可以层叠的,我们每启动一个新的活动,就会覆盖在原活动之上,然后点击Back键会销毁最上面的活动,下面的一个活动就会重新显示出来。
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法销毁一个活动时,处于栈顶的活动就出出栈。
1.4.2 活动状态
每个活动在其生命周期中最多可能会有4种状态
- 运行状态:当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。
- 暂停状态:当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。
- 停止状态:当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入停止状态。
- 销毁状态:当一个活动从返回栈种移除后就变成了销毁状态。
1.4.3 活动的生存周期
Activity类种定义了7各回调方法,覆盖了活动生命周期的每一个环节。
- onCreate()。它会在活动第一次被创建时调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。
- onStart()。这个方法在活动由不可见变为可见的时候调用。
- onResume()。这个方法在活动准备好和用户进行交互的时候调用。此时活动一定位于返回栈的栈顶,并且处于运行状态。
- onPause()。这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
- onStop()。这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
- onDestroy()。这个方法在活动被销毁之前调用,之后活动的状态变为销毁状态。
- onRestart()。这个方法在活动由停止状态变为运行状态之前调用,也就是说活动被重启启动了。
以上7个方法中除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为3种生存期。
- 完整生存期。活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
- 可见生存期。活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
-
前台生存期。活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态,此时的活动是可以和用户进行交互的,我们平时看到和接触最多的也就是这个状态下的活动。
1.4.4 活动被回收了怎么办
应用中有个活动A,用户在A的基础上启动了活动B,活动A就进入了停止状态,这个时候由于内存不足,将A回收掉了,然后用户按下Back键返回活动A,虽然也会正常显示活动A,但是这个并不会执行onRestart()方法,而是会执行活动A的onCreate()方法,因为A在这种情况下会被重新创建一次。
例如,在MainActivity中有个文本输入框,现在你输入了文字,然后启动NormalActivity,这时MainActivity由于系统内存不足被回收掉,这时点击Back键返回到MainActivity,你会发现输入的文字没得了,因为MainActivity被重新创建了。
这时我们可以使用Activity中提供的onSaveInstanceState()回调方法,这个方法可以保证活动被回收之前一定会被调用,因此可以通过这个方法来解决活动被祸首时临时数据得不到保存的问题。
onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据。
在MainActivity中添加如下代码用于保存临时数据:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "aaaaa";
outState.putString("data_key",tempData);
}
然后修改MainActivity中的onCreate()方法
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState!=null) {
String tempData = savedInstanceState.getString("data_key");
}
}
1.5 活动的启动模式
启动模式一共有4种,分别是 standard,SingleTop,SingleTask,SingleInstance。这四种模式中,standard模式是默认的模式,其他三个想要使用的话,要在AndroidMainFest中进行修改。(例如:<android launchMode ="singleTop">)。在实际项目种我们应该根据特定的需求为每个活动指定恰当的启动模式。
1.5.1 standard
standard是活动默认的启动模式。对于使用standard模式启动的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。1.5.2 singleTop
当活动的模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
1.5.3 singleTask
使用singleTop模式可以很好的解决重复创建栈顶活动的问题,但是如果该活动没得处于栈顶位置,还是可以创建多个活动实例的。当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动对的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
1.5.4 singleInstance
不同于以上3种启动模式,指定为singleInstance模式的活动会启动一个新的返回栈来管理这个活动。例如,假设我们的程序种有一个活动允许其他活动调用,在这种模式下,我们会有一个单独的返回栈来管理这个活动,不管哪个应用程序来访问这个活动,都公用的同一个返回栈,也就解决了共享活动实例的问题。
1.6 活动的最佳实践
1.6.1 知晓当前是在哪个活动
这个技巧将会教会你如何根据程序当前的界面就能判断出这是哪一个活动。
新建 BaseActivity ,然后让 BaseActivity 继承自 AppCompatActivity ,并重写onCreate()方法,如:
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
我们在onCreate()方法种获取了当前实例的类名,并通过Log打印出来。接下来让 BaseActivity 成为项目中所有活动的父类。我们可以通过打印出的类名来判断是哪个活动。
1.6.2 随时随地退出程序
新建一个ActivityCollector类作为活动管理器,
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity){
activities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for(Activity activity:activities){
if (!activity.isFinishing()){
activity.finish();
}
}
activities.clear();
}
}
在活动管理器中,我们通过一个List来暂存活动,然后提供了一个addActivity()方法用于向List中添加一个活动,提供了一个removeActivity()方法用于从List中移除活动,最后提供了一个finishAll()方法用于将List中存储的活动娶不销毁。
接下来修改 BaseActivity 中的代码,
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
在 BaseActivity 的 onCreate() 方法中国调用了 ActivityCollector 的 addActivity() 方法,表明将当前正在创建的活动添加到活动管理器李。然后在 BaseActivity 中重写 onDestroy() 方法,并调用了 ActivityCollector 的 removeActivity()方法,表明将一个马上要销毁的活动从活动管理器中移除。此外,不管你想在什么地方退出程序,只需要调用 ActivityCollector.finishAll()方法即可。
1.6.3 启动活动的最佳写法
假设 SecondActivity 中需要用到两个非常重要的字符串参数,在启动 SecondActivity 的时候必须传递过来,
传统写法
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("param1","data1");
intent.putExtra("param2","data2");
startActivity(intent);
但是在真正的项目中经常会有对接问题,比如 SecondActivity 不是你开发的,你不知道需要传递哪些数据,这时你可能需要阅读 SecondActivity 代码或者询问编写它的同事,但是你可以换种写法。
修改 SecondActivity 种的代码:
public class SecondActivity extends BaseActivity{
public static void actionStart(Context context,String data1,String data2){
Intent intent = new Intent(context,SecondActivity.class);
intent.putExtra("param1",data1);
intent.putExtra("param2",data2);
context.startActivity(intent);
}
...
}
我们在 SecondActivity 种添加了一个 actionStart() 方法,这个方法中完成了Intent的构建,另外所有 SecondActivity 中需要的数据都是通过 actionStart() 方法的参数传递过来的,然后把它存储到Intent中,最后调用 startActivity() 方法启动 SecondActivity。
这样写的好处是能够一目了然的知道启动 SecondActivity 需要传递哪些数据,另外简化了启动活动的代码
SecendActivity.actionStart(FirstActivity.this, "data1", "data2");