2.1活动概念
是一种包含用户界面的组件,主要用于和用户进行交互。
2.2活动基本用法
MainActivity 就是一个活动
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
2.2.1创建活动两种方式:
1.右击com.example.activitytest –> New –> Activity –> Empty Activity ,创建Main2Activity。
创建完成后会在
res layout 自动生成activity_main2.xml文件
AndroidManifest.xml中自动生成 activity 的标签
<activity android:name=".Main2Activity"></activity>
2.右击com.example.activitytest –> New –>JacaClass-> 填入:
Name SuperClass
这种方式创建在res 和AndroidManifest.xml中不会自动生成
2.2.2创建和加载布局
1布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
tools:layout_editor_absoluteX="146dp"
tools:layout_editor_absoluteY="101dp"
tools:ignore="MissingConstraints" />
</LinearLayout>
向布局文件中添加Button
● android:id="@+id/button 给当前元素定义唯一标识
● android:layout_width=“wrap_content” 宽度
● android:layout_height=“wrap_content” 高度
● wrap_content :当前元素的高度只要能刚好包含里面的内容就行
● match_parent:和当前幅元素一样宽
2.加载布局:
项目中添加的资源文件都会在R文件中生成一个相应的资源id。通过setContentView()方法来加载当前活动的布局,而在setContentView()方法中我们传入的是布局文件的id。
setContentView(R.layout.Main2Activity);
2.2.3 AndroidManifest.xml文件分析
任何一个活动都需要在AndroidManifest中进行注册
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".Main2Activity"></activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
● package指定了程序的包名;
● name指定注册的是哪个活动;
● label指定活动中标题栏的内容,还会成为启动器(Launcher)中应用程序显示的名称;
● intent-filter标签指定当前活动为程序的主活动。
2.2.4活动中使用Toast
Toast是Android系统提供的一种信息提醒方式。信息在一段时间后会自动消失,并且不会占用任何屏幕空间。
通过点击Button 弹出Toast
通过findViewById()方法获取到在布局文件中定义的元素,传入R.id.button_1获取到Button实例。给button1设置一个监听器,然后在onClick()方法中执行监听事件。Toast输入的快捷键是Toast+Tab键。makeText()方法中的三个参数:
第一个参数:上下文Context。
第二个参数:Toast显示的文本内容。
第三个参数:Toast显示的时长,有两个内置常量,分别是Toast.LENGTH_SHORT和Toast.LENGTH_LONG。
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"弹窗",Toast.LENGTH_LONG).show();
}
});
}
}
2.2.5活动中Menu
创建menu步骤:
1.res文件创建menu
2.重写onCreateOptionMenu()方法
2.重写onOptionsItemSelected() 方法
在res目录下新建一个menu文件夹,右击res目录——>New——>Directory,输入文件夹名menu,点击OK。再在menu文件夹下新建一个名为main的菜单文件,右击menu文件夹——>New——>Menu resource file。然后在main.xml中添加如下代码:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="add">
</item>
<item
android:id="@+id/remove_item"
android:title="remove">
</item>
</menu>
其中:
item标签是用来创建具体的某一个菜单项;
android:id 给菜单项指定一个唯一的标识符;
android:title 给菜单项指定一个名称。
Activity中重写 onCreateOptionMenu() 方法,给当前活动创建菜单:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu,menu);
return true;
}
通过getMenuInflater()得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了。
其中:
参数一:指定通过哪个资源文件来创建菜单。
参数二:指定菜单项添加到哪一个Menu对象中。
返回true,表示允许创建的菜单显示出来;返回false,创建的菜单无法显示。
重写onOptionsItemSelected()方法,定义菜单响应事件。添加如下代码:
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this.getApplicationContext(),"add_item",Toast.LENGTH_LONG).show();
break;
case R.id.remove_item:
Toast.makeText(this.getApplicationContext(),"remove_item",Toast.LENGTH_LONG).show();
break;
}
return true;
}
2.2.6销毁一个活动
按Back键
调用finish()方法。
2.3 Intent的使用
Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动活动、启动服务以及发送广播等场景
2.3.1 Intent的显式使用
通过intent来启动Activity
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
其中,Intent中的参数:
参数一:启动活动的上下文;
参数二:指定想要启动的目标活动。
按Back键可以销毁当前活动,返回上一个活动。
2.3.2 Intent的隐式使用
隐式Intent不明确指出要启动的活动,而是指定一系列更为抽象的
action和category等信息
然后让系统去分析这个Intent,从而找出合适的活动去启动。
通过在activity标签下配置intent-filter的内容,可以指定当前活动能响应的action和category
action只有1个但是category可以有多个
调用分为2种
1.有默认的category
当有默认的category时,可以不用写
Intent intent = new Intent("action2.activity");
startActivity(intent);
2.自定义category
注意:隐式的Activity xml 中 Category要有 android.intent.category.DEFAULT
Intent intent = new Intent("action2.activity");
intent.addCategory("category2");
startActivity(intent);
AndroidManifest.xml
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="action2.activity" />
<category android:name="category2" />
<category android:name="android.intent.category.DEFAULT" /> //这条必须要有
</intent-filter>
</activity>
2.3.3 Intent更多用法
1.例如可以调用系统的浏览器来打开一个网页。
Intent intent1 = new Intent(Intent.ACTION_VIEW);
intent1.setData(Uri.parse("https://www.baidu.com"));
startActivity(intent1);
2.拨打电话
Intent intent2 = new Intent(Intent.ACTION_DIAL);
intent2.setData(Uri.parse("tel:10086"));
startActivity(intent2);
2.3.4 向下一个Activity传递数据
Intent不仅可以启动一个活动,还可以在启动活动的时候传递数据,传递数据的思路如下:
在启动页面中通过Intent提供的一系列putExtra()方法,将传递的数据放在Intent中
在被启动的页面中通过getIntent获取Intent对象,再调用getXXXExtra()方法获取值。
发送:
FirstActivity中传一个字符串到SecondActivity中,在FirstActivity中的代码如下:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class );
intent.putExtra("extra_data", data);
startActivity(intent);
}
});
这里采用的是显式Intent的方式来启动SecondActivity,并通过putExtra()方法传递一个字符串数据。putExtra()中的两个参数:
参数一:键。用于在后面从Intent中获取值。
参数二:要传递的数据值。
接受:
SecondActivity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
}
2.3.4 返回数据给上一个活动
如果在启动另外一个活动后,想得到被启动的活动的数据反馈,那么我们在启动活动的时候就不能再用startActivity()方法了,而应该使用
startActivityForResult()方法,该方法接收两个参数:
参数一:intent
参数二:请求码 用于在之后的回调中判断数据的来源。
firstActivity代码:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class );//显示调用
startActivityForResult(intent,1);
}
});
请求码要确定是一个唯一值,这里传入1。
因为我们使用的是startActivityForResult()方法来启动的SecondActivity,那么在SecondActivity被销毁后会回调上一个活动的onActivityResult()方法。因为我们需要在
FirstActivity中重写这个方法来得到返回的数据,代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra("data_return");
Log.d(TAG, returnData);
}
break;
default:
}
}
其中,onActivityResult()方法中带有三个参数:
参数一:requestCode,请求码。就是在活动时传入的请求码。
参数二:resultCode,结果码。就是在返回数据时传入的处理结果。
参数三:data,返回数据的Intent。
由于有可能在一个活动中调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调onActivityResult()方法,因此我们需要通过requestCode的值来判断数据的来源,再通过resultCode的值来判断处理结果是否成功,最后再从data中取值,这样才算完成了向上一个活动返回数据的工作。
SecondActivity代码:
SecondActivity中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑,代码如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
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();
}
});
}
这里我们构建了一个Intent,但这里的Intent仅仅只是用于传递数据,不启动其他任何Activity。然后将要反馈的数据放在intent中,然后再调用setResult()方法,该方法是专门用于向上一个活动返回数据的。setResult()方法有两个参数:
参数一:用于向上一个活动返回处理结果。一般只使用RESULT_OK或RESULT_CANCELED。
参数二:带有数据的Intent。
最后调用finish()方法来销毁当前活动。
上面我们是通过点击SecondActivity中的按钮来返回上一个活动,如果我们是直接按Back键返回到FirstActivity,那么数据就没法返回了。为了处理这种情况,我们可以通过重写
SecondActivity中的onBackPressed()方法来解决,代码如下:
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity2");
setResult(RESULT_OK, intent);
finish();
}
这样当用户按下Back键,就会去执行onBackPressed()方法中的代码,同样可以将数据返回到FirstActivity中去。
2.4 活动的生命周期
掌握Activity的生命周期对Android开发者来说是至关重要的。
2.4.1 返回栈
Android中的活动是可以层叠的,每启动一个新的活动,就会覆盖在原活动之上,当点击Back键或执行finish操作时,就会销毁最上面的活动,下面的一个活动就会重新显示出来。而Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈又被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它都会进入返回栈,然后处于栈顶的位置。
2.4.2 活动的状态
每个活动在其生命周期中最多可能会有4中状态。
- 运行状态
当Activity处于返回栈的栈顶时,这时就处于运行状态。系统最不愿意回收处于运行状态的活动。
- 暂停状态
当Activity不再处于栈顶位置,但仍然可见时,这时活动就会进入暂停状态。只有在内存极低的情况下,系统才会去考虑回收这种活动。
- 停止状态
当Activity不再处于栈顶位置,且不可见时,这时活动就会进入停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但当其他地方需要内存时,处于停止状态的活动有可能被系统回收。
- 销毁状态
当Activity从返回栈中移除后,就是处于销毁状态。系统非常喜欢回收处于这种状态的活动,从而来保证机器的内存充足。
2.4.3 活动的生存期
Activity中定义了7个回调方法来阐述活动生命周期的每一个环节:
oncreate(),该方法在活动第一次被创建的时候调用,主要完成一些初始化操作。例如加载布局、绑定事件等。
onStart() ,该方法在活动由不可见到可见的时候调用。
onResume() ,该方法是活动准备好和用户进行交互的时候调用。此时活动一定处于栈顶,且是运行状态。
onPause(),该方法在系统准备去启动或恢复另一个活动的时候调用。在这个方法中我们一般会快速的释放掉一些耗CPU的资源、保存一些关键的数据。
onStop(),该方法在活动完全不可见时调用。与onPause()的主要区别是,如果新启动的Activity是一个对话框式的活动,那么onPause()会执行,onStop()不会执行。
onDestory(),该方法在活动被销毁之前调用,之后的活动状态变为销毁状态。
onRestart(),该方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动。
上面7种方法除了onRestart()方法,其他的都是两两相对,从而又可以将活动分为3中生存期:
1.完整生存期
onCreate() ——> onDestory()
2. 可见生存期
onstart() ——> onStop()
3. 前台生存期
onResume() ——> onPause()
了更好的理解Activity的生命周期,Android官方提供了一张示意图:
2.4.5 活动被回收了怎么办
如果活动处于停止状态时,就有可能因为内存不足被系统回收掉,这是我们就需要在回收之前保存活动的一些临时数据。Activity中提供了一个onSaveInstanceState()回调方法,该方法可以保证在活动回收之前被调用。因此我们可以在该方法中保存一些重要的临时数据。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key",tempData);
}
可以看到Bundle保存数据也是通过键值对的形式保存。
数据保存下来了,那该从哪里去取呢?我们发现onCreate()方法中有一个Bundle类型的参数,这个参数保存了所有保存的数据。我们可以通过相应的键来取出对应的值。
在MainActivity的onCreate()方法中取出值:
//获取Bundle中保存的数据
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
2.5 活动的启动模式
启动模式共有4种,分别为standard、singleTop、singleTask和singleInstance。可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。
2.5.1 standard
standard又称作“标准式”,在不显式指定的情况下,所有的活动都是使用这种启动模式。使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动活动都会创建该活动的一个新实例。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Task id is " + getTaskId());
setContentView(R.layout.first_layout);
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
在FirstActivity的基础上启动FirstActivity,并添加一条打印信息,用于打印当前活动的实例。连续点击两次按钮,可以看到logcat中打印信息如下所示:
com.example.activitytest.FirstActivity@6eb0acd
com.example.activitytest.FirstActivity@5fbd8e1
com.example.activitytest.FirstActivity@40b5390
可以看出每点击一次就会创建一个新的FirstActivity实例。此时返回栈中就有3个FirstActivity实例,因此我们需要连续按3此Back键才能退出程序。
2.5.2 singleTop
singleTop又称作“栈顶复用式”,就是在启动活动时,如果发现返回栈的栈顶已经是该活动了,那么就直接使用它,不会再创建新的实例
修改AndroidManifest.xml中的FirstActivity的启动模式,如下所示:
<activity
android:name=".FirstActivity"
android:launchMode="singleTop"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
之后无论你点击多少次都不会再有新的打印信息出现。因为此时FirstActivity处于栈顶的位置,不会再创建新的实例。但是当FirstActivity不处于栈顶的位置时,这时再启动FirstActivity是会创建新的实例的。
2.5.3 singleTask
singleTask又称作“栈内复用式”,每次启动活动时都会先在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用,并把这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
修改AndroidManifest.xml中的FirstActivity的启动模式,如下所示
<activity
android:name=".FirstActivity"
android:launchMode="singleTask"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
2.5.4 singlestance
singlestance又称作“单例式”。指定为singlestance模式的活动会启用一个新的返回栈来管理这个活动,有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用同一个返回栈,从而达到了共享活动实例的目的。
修改AndroidManifest.xml中的SecondActivity的启动模式,如下所示:
<activity android:name=".SecondActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
将SecondActivity的启动模式指定为singleInstance。
分别修改FirstActivity、SecondActivity、ThirdActivity中onCreate()方法中的log打印信息:
Log.d(TAG, "Task id is " + getTaskId());
修改代码,在FirstActivity中启动SecondActivity,在SecondActivity中启动ThirdActivity,查看logcat中的打印信息,如下所示:
com.example.activitytest D/FirstActivity: Task id is 153
com.example.activitytest D/SecondActivity: Task id is 154
com.example.activitytest D/ThirdActivity: Task id is 153
从logcat信息中可以看出,SecondActivity的任务栈id与FirstActivity和ThirdActivity均不同,所以SecondActivity确实是放在一个单独的返回栈中。
2.6 活动最佳实例
2.6.1 知晓当前是在哪一个活动
步骤:
1.创建一个BaseActivity 类,并继承AppCompatActivity,这个类不在AndroidManifest.xml中注册
2.类中重写onCreate()方法
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());//打印类名
}
3.新创建的都继承BaseActivity类
例如:MainActivity 继承BaseActivity 类
Main2Activity 继承BaseActivity 类
当启动两个类时,打印:
2020-04-07 22:02:47.958 5069-5069/com.example.activitys D/base: Base: MainActivity
2020-04-07 22:02:59.792 5069-5069/com.example.activitys D/base: Base: Main2Activity
2.6.2 随时随地退出程序
创建一个集合类对所有的活动进行管理,这样就可以实现程序随时随地退出。
定义一个addActivity()方法来向List中添加一个活动;
定义一个removeActivity()方法用于从List中移除活动;
定义一个finishAll()方法将List中存储的活动全部销毁掉。
public class Collection {
private static List<Activity> list = new ArrayList<>();
public static void addActivity(Activity activity) {
list.add(activity);
}
public static void removeActivity(Activity activity) {
list.remove(activity);
}
public static void finishAll() {
for (Activity activity:list) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
其他界面在创建时调用:
Collection.addActivity(this);
如果想要退出:
Collection.finishAll();
你还可以在销毁所有活动的代码后面加上杀掉当前进行的代码,以保证程序完全退出,杀掉进程的代码如下所示:
android.os.Process.killProcess(android.os.Process.myPid());
其中,killProcess()方法用于杀掉一个进程,它接收一个进程id参数,可以通过myPid()方法获取当前进程的id。需要注意的是,killProcess()方法只能用于杀掉当前程序的进程,不能使用这个方法杀掉其他程序。
2.6.3 启动活动的最佳写法
通常情况下我们启动一个活动的写法如下:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
startActivity(intent);
但如果我们不知道启动SecondActivity需要传递哪些数据?那我们就需要去看SecondActivity的源码或问开发SecondActivity的同事,这样就显示比较繁琐。
为了优化这种问题,我们只需要在开发SecondActivity时就定义一个启动方法:
public static void actionStart(Context context, String data1, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("parm1", data1);
intent.putExtra("parm2", data2);
context.startActivity(intent);
}
直接在SecondActivity方法中定义一个actionStart()方法,将需要的数据通过参数传递过来,然后将它们存储在Intent中,最后再调用startActivity()方法启动SecondActivity。
这样我们在其他地方启动SecondActivity就很方便:
SecondActivity.actionStart(FirstActivity.this,"data1","data2");