在相应的位置(Ch.1中介绍过) 创建.java
的Activity文件和.xml
的布局文件, 创建后, AS会自动将Activity在manifests中注册:
<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=".FirstActivity">activity> //此为新增Activity
application>
可以使用AS中的可视化编辑, 这里介绍直接在xml中添加控件:
添加一个Button元素后的空间布局xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button_1"
/>
LinearLayout>
其中
xml 语法 android:id="@+id/button_1"
, 类似于上头的引用
当写成这样时就是引用: android:id="@+id/button_1"
, 多一个+时就是创建
android:layout width指定了当前元素的宽度,这里使用match parent 表示让当前元素和父元素一样宽
android:layout height 指定了当前元素的高度,这里使用wrap_content表示当前元素的高度只要能刚好包含里面的内容就行
android:text指定了元素中显示的文字内容
这里如果没有在res中的string中定义内容再引用, 而是直接定义, AS会给出警告, 所以推荐这么做
而后在MainActivity中加载这个布局:
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
}
}
使用资源的操作在Ch.1中介绍过
而后, 设置程序启动后的入口, 在Ch.1中也介绍过:
Toast是一个弹出后会很快消失的小提示框, 可以很方便的通知信息并且不会占用屏幕的布局空间
在代码中使用Toast:
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
//添加的代码:
Button Button_1 = findViewById(R.id.button_1);
Button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//内部类语法: FirstActivity.this表示外部类的引用
Toast.makeText(FirstActivity.this, "You Checked Button_1", Toast.LENGTH_SHORT).show();
}
});
}
}
通过findViewById()方法获取到在布局文件中定义的元素,并返回view
这里传入R.id.button 1, 即Button_1 的ID,来得到按钮的实例,而后强制转换为Button对象
调用setonClickListener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法。因此需要咋onClick() 中编写相关代码
通过静态工厂方法makeText()创建出一个Toast对象,然后调用show()将Toast显示
这里需要注意的是,makeText()方法需要传入3个参数
添加menu菜单:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
这里使用onCreateOptionMenu()方法
其实Activity中有一套机制实现对菜单的管理
使用onCreateOptionsMenu & onPrepareOptionsMenu 来添加菜单
使用onOptionsItemSelected来监听菜单中的Item, 并设置点击后执行的代码
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/**
* 此方法用于初始化菜单,其中menu参数就是即将要显示的Menu实例。 返回true则显示该menu,false 则不显示;
* (只会在第一次初始化菜单时调用) Inflate the menu; this adds items to the action bar
* if it is present.
*/
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
/**
* 在onCreateOptionsMenu执行后,菜单被显示前调用;如果菜单已经被创建,则在菜单显示前被调用。 同样的,
* 返回true则显示该menu,false 则不显示; (可以通过此方法动态的改变菜单的状态,比如加载不同的菜单等)
*/
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onOptionsMenuClosed(Menu menu) {
/**
* 每次菜单被关闭时调用. (菜单被关闭有三种情形,menu按钮被再次点击、back按钮被点击或者用户选择了某一个菜单项)
*/
super.onOptionsMenuClosed(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
/**
* 菜单项被点击时调用,也就是菜单项的监听方法。
* 通过这几个方法,可以得知,对于Activity,同一时间只能显示和监听一个Menu 对象。
*/
return super.onOptionsItemSelected(item);
}
而后, 内部使用getMenuInflater().inflate(R.menu.main,menu);
将布局文件中的菜单添加进去
本方法主要的好处是实现了模型(Model)与视图(View)的分离
需要在res目录中添加menu文件夹, 并在其中创建Menu resource file 才能使用
其中:
调用Activity的getMenuInflater()得到一个MenuInflater
MenuInflater是用来加载menu布局文件的, 与LayoutInflater类似,应用程序运行时会预先加载资源中的布局文件,如果Menu布局中的资源比较多,会影响性能,所以可以选择MenuInflater方式用的时候加载,这样减轻了应用程序运行时很多负担
使用inflate方法来把布局文件中的定义的菜单(第一个参数)加载给指定的menu对象(第二个参数)
MenuInflater的标准使用方法:
只有Activity.getMenuInflater()方法和MenuInflater(Context context)方法来获得MenuInflater对象, 然后调用MenuInflater的方法inflate(int menuRes, Menu menu)方法加载布局
这里介绍另一种加载菜单的方法:
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(Menu.NONE, Menu.First+1 , 0, "设置").setIcon(R.drawable.setting);
return true;
}
使用add()方法, 但是由于没有将模型(Model)与视图(View)的分离, 所以现在基本不用…
使用back键销毁当前显示的活动
在程序中, 使用Activity的finish() 方法销毁当前活动
想测试一下显示Toast再延时3000ms退出, 但是发现Toast并没有被显示, 即使再开一条线程也是一样…后头在看啥原因
Button Button_2= (Button) findViewById(R.id.button_2);
Button_2.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Thread toast= new Thread(new Runnable(){
@Override
public void run() {
Toast.makeText(FirstActivity.this,"Activity deleting",Toast.LENGTH_SHORT).show();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finish(); //这里调用finish(), 结束finish所在的Activity
}
});
Ch.1中介绍的intent:
Intent 并不是 Android 应用程序四大核心组件之一,但是其重要性无可替代
Android 应用程序核心组件中的三大核心组件 —— Activity、Service、BroadcastReceiver。通过消息机制被启动激活,而所使用的消息就是 Intent。Intent 是对即将要进行的操作的抽象描述,承担了 Android 应用程序三大核心组件相互之间的通信功能
现在由于只介绍到了Activity, 所以暂时只涉及Intent与Activity之间的联系
显式,即直接指定需要打开的activity对应的类
构造方法:
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
直接在构造函数中传入指定打开的Activity类, 这是最常用的方法
其中intent的构造函数:
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}
其中第二个参数是Class类:
Class类保存的是类的类型信息, 相当于对象的抽象类型集合
有三种获得Class对象的方式:
- Class.forName(“类的全限定名”)
- 实例对象.getClass()
- 类名.class (类字面常量)
拐弯抹角setComponent()方法:
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
//或者intent.setClassName(this, "com.example.app.SecondActivity");
//或者intent.setClassName(this.getPackageName(),"com.example.app.SecondActivity");
startActivity(intent);
类似的setClass / setClassName方法
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
//或者intent.setClassName(this, "com.example.app.SecondActivity");
//或者intent.setClassName(this.getPackageName(),"com.example.app.SecondActivity");
startActivity(intent);
其中, Activity类提供了两种启动Activity的方法:
Context.startActvity();
Activity.startActivityForResult();
隐式,不明确指定启动哪个Activity,而是设置Action、Data、Category,让系统来筛选出合适的Activity。筛选是根据所有的
来筛选
intent匹配模式:
实现:
首先在项目的AndroidManifest.xml文件中设置如下内容:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
activity>
为SecondActivity添加了intent_filter内容
其中action&category可以自定义, 但是通常设置为包名+action名的形式, 便于与其他包进行区分
而android.intent.category.DEFAULT
是默认的一种category, 会在startAction()调用时自动添加进intent中
而后在程序中设置:
Button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "You Checked Button_1", Toast.LENGTH_SHORT).show();
Intent intent= new Intent("com.example.activitytest.ACTION_START");
startActivity(intent);
}
});
如果使用是其他category ,需要使用addCategory()方法添加到intent对象中
其中每个intent对象可以添加多个category
Intent对象大致包括7大属性:Action(动作)、Data(数据)、Category(类别)、Type(数据类型)、Component(组件)、Extra(扩展信息)、Flag(标志位)。其中最常用的是Action属性和Data属性。
类型 | 作用 |
---|---|
ACTION_MAIN |
表示程序入口 |
ACTION_VIEW |
自动以最合适的方式显示Data |
ACTION_EDIT |
提供可以编辑的 |
ACTION_PICK |
选择一个一条Data,并且返回它 |
ACTION_DAIL |
显示Data指向的号码在拨号界面Dailer上 |
ACTION_CALL |
拨打Data指向的号码 |
ACTION_SEND |
发送Data到指定的地方 |
ACTION_SENDTO |
发送多组Data到指定的地方 |
ACTION_RUN |
运行Data,不管Data是什么 |
ACTION_SEARCH |
执行搜索 |
ACTION_WEB_SEARCH |
执行网上搜索 |
ACRION_SYNC |
执同步一个Data |
ACTION_INSERT |
添加一个空的项到容器中 |
Broadcast Actions:
类型 | 作用 |
---|---|
ACTION_TIME_TICK |
当前时间改变,并即时发送时间,只能通过系统发送。调用格式"android.intent.action.TIME_TICK" |
ACTION_TIME_CHENGED |
设置时间。调用格式"android.intent.action.TIME_SET" |
**Data:**表示与动作要操纵的数据
一个URI对象是一个引用的data的表现形式,或是data的MIME类型;data的类型由Intent的action决定
类型 | 作用 |
---|---|
android:scheme | 用于指定数据的协议部分,如上例中的http部分。 |
android:host | 用于指定数据的主机名部分,如上例中的www.baidu.com部分。 |
android:port | 用于指定数据的端口部分,一般紧随在主机名之后。 |
android:path | 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。 |
android:mimeType | 用于指定可以处理的数据类型,允许使用通配符的方式进行指定。 |
**Category:**用来表现动作的类别
一个包含Intent额外信息的字符串,表示哪种类型的组件来处理这个Intent。任何数量的Category 描述都可以添加到Intent中,但是很多intent不需要category,下面列举一些常用的category:
类型 | 作用 |
---|---|
CATEGORY_DEFAULT |
把一个组件Component设为可被implicit启动的 |
CATEGORY_LAUNCHER |
把一个action设置为在顶级执行。并且包含这个属性的Activity所定义的icon将取代application中定义的icon |
CATEGORY_BROWSABLE |
当Intent指向网络相关时,必须要添加这个类别 |
CATEGORY_HOME |
使Intent指向Home界面 |
CATEGORY_PREFERENCE |
定义的Activity是一个偏好面板Preference Panel |
**Type:**指定数据类型
一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。
**Component:**目的组件
指定Intent的目标组件名称,当指定了这个属性后,系统将跳过匹配其他属性,而直接匹配这个属性来启动对应的组件。
**Extra:**扩展信息
Intent可以携带的额外 key-value 数据,你可以通过调用putExtra()
方法设置数据,每一个 key对应一个 value数据。你也可以通过创建 Bundle对象来存储所有数据,然后通过调用putExtras()
方法来设置数据。
类型 | 作用 |
---|---|
EXTRA_BCC |
存放邮件密送人地址的字符串数组 |
EXTRA_CC |
存放邮件抄送人地址的字符串数组 |
EXTRA_EMAIL |
存放邮件地址的字符串数组 |
EXTRA_SUBJECT |
存放邮件主题字符串 |
EXTRA_TEXT |
存放邮件内容 |
EXTRA_KEY_EVENT |
以KeyEvent对象方式存放触发Intent 的按键 |
EXTRA_PHONE_ NUMBER |
存放调用ACTION_CALL 时的电话号码 |
setFlags()
或者addFlags()
可以把标签flag用在Intent中。类型 | 作用 |
---|---|
FLAG_ACTIVITY_CLEAR_TOP |
相当于SingleTask |
FLAGE_ACTIVITY_SINGLE_TOP |
相当于SingleTop |
FLAG_ACTIVITY_NEW_TASK |
类似于SingleInstance |
FLAG_ACTIVITY_NO_HISTORY |
当离开该Activity后,该Activity将被从任务栈中移除 |
简要介绍uri:
通用资源标志符(Universal Resource Identifier, 简称"URI")。
Uri代表要操作的数据,Android上可用的每种资源 - 图像、视频片段等都可以用Uri来表示
Uri一般由三部分组成:
访问资源的命名机制
存放资源的主机名
资源自身的名称,由路径表示
通常使用Uri 的parse() 方法将资源转化为Uri:
public static Uri parse(String uriString) {
return new StringUri(uriString);
}
简要介绍setData:
public @NonNull Intent setData(@Nullable Uri data) {
mData = data;
mType = null;
return this;
}
接收一个Uri对象, 并转化为为目标intent对象设置Data
调用拨号程序
// 调用拨打电话,给10010拨打电话
Uri uri = Uri.parse("tel:10010");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);
// 直接拨打电话,需要加上权限
Uri callUri = Uri.parse("tel:10010");
Intent intent = new Intent(Intent.ACTION_CALL, callUri);
// 给10010发送内容为“Hello”的短信
Uri uri = Uri.parse("smsto:10010");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello");
startActivity(intent);
// 发送彩信(相当于发送带附件的短信)
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra("sms_body", "Hello");
Uri uri = Uri.parse("content://media/external/images/media/23");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/png");
startActivity(intent);
// 打开百度主页
Uri uri = Uri.parse("https://www.baidu.com");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
// 给[email protected]发邮件
Uri uri = Uri.parse("mailto:[email protected]");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);
// 给[email protected]发邮件发送内容为“Hello”的邮件
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, "[email protected]");
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("text/plain");
startActivity(intent);
// 给多人发邮件
Intent intent=new Intent(Intent.ACTION_SEND);
String[] tos = {"[email protected]", "[email protected]"}; // 收件人
String[] ccs = {"[email protected]", "[email protected]"}; // 抄送
String[] bccs = {"[email protected]", "[email protected]"}; // 密送
intent.putExtra(Intent.EXTRA_EMAIL, tos);
intent.putExtra(Intent.EXTRA_CC, ccs);
intent.putExtra(Intent.EXTRA_BCC, bccs);
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("message/rfc822");
startActivity(intent);
// 打开Google地图中国北京位置(北纬39.9,东经116.3)
Uri uri = Uri.parse("geo:39.9,116.3");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
// 路径规划:从北京某地(北纬39.9,东经116.3)到上海某地(北纬31.2,东经121.4)
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=39.9 116.3&daddr=31.2 121.4");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/foo.mp3");
intent.setDataAndType(uri, "audio/mp3");
startActivity(intent);
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, 2);
// 打开拍照程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 1);
// 取出照片数据
Bundle extras = intent.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");
// 获取并剪切图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true"); // 开启剪切
intent.putExtra("aspectX", 1); // 剪切的宽高比为1:2
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", 20); // 保存图片的宽和高
intent.putExtra("outputY", 40);
intent.putExtra("output", Uri.fromFile(new File("/mnt/sdcard/temp"))); // 保存路径
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);
// 剪切特定图片
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setClassName("com.android.camera", "com.android.camera.CropImage");
intent.setData(Uri.fromFile(new File("/mnt/sdcard/temp")));
intent.putExtra("outputX", 1); // 剪切的宽高比为1:2
intent.putExtra("outputY", 2);
intent.putExtra("aspectX", 20); // 保存图片的宽和高
intent.putExtra("aspectY", 40);
intent.putExtra("scale", true);
intent.putExtra("noFaceDetection", true);
intent.putExtra("output", Uri.parse("file:///mnt/sdcard/temp"));
startActivityForResult(intent, 0);
// 打开手机应用市场,直接进入该程序的详细页面
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
String fileName = Environment.getExternalStorageDirectory() + "/myApp.apk";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(fileName)),
"application/vnd.android.package-archive");
startActivity(intent);
Uri uri = Uri.parse("package:" + packageName);
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
startActivity(intent);
// 进入系统设置界面
Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
startActivity(intent);
使用Intent类的putExtra()方法 , 将数据暂存在intent中, 启动之后, 在从中取出即可
Intent中提供了一大堆的重载方法, 可以满足基本需求
并且其中都以键值对的形式储存
在启动的程序中调用intent并取出数据:
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity", data);
}
}
getIntent()方法获取到用于启动SecondActivity的Intent
getStringExtra()方法,传入相应的键值,可以得到传递的数据
Intent中针对不同的值
类型, 有不同的get方法
即Activity_1启动了Activity_2, 当Activity_2结束后, 将数据传回Activity_1
实现这个功能, 首先需要在Activity_1 中使用startActivityForResult()
方法启动Activity_2, 而不是使用之前的startActivity()
方法:
这里修改按钮点击事件:
从first_activity启动second_activity:
Button Button_1 = (Button) findViewById(R.id.button_1);
Button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "You Checked Button_1", Toast.LENGTH_SHORT).show();
Intent intent =new Intent("com.example.activitytest.ACTION_START");
intent.putExtra("data_return", "First call Second.");
startActivityForResult(intent, 1);
}
});
在second_activity中修改如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button button_2 = findViewById(R.id.button_2);
button_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =new Intent();
intent.putExtra("return_data", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
});
}
点击按钮后会结束second_activity, 并向上一个活动first_activity传递数据, 其中数据用一个intent对象保存
setResult()函数设置返回的数据:
第一个参数resultCode标志码, 可用于后头的判断
通常只使用RESULT_CANCELED 或 RESULT OK这两个值
第二个参数是返回的数据 ,用Intent对象储存, 其中的intent就是用来传递数据的, 没其他用途
而后, 当second_activity被销毁后, 会调用上一个活动的onActivityResult()方法, 所以这里需要对first_activity中的方法进行重载:
头两个参数就是之前设置的标志码
通常在onActivityResult()方法中使用这两个标志码判断是什么对象返回的数据, 返回啥数据, 而后执行相应的代码
第二次参数就是返回的数据
可在onActivityResult()方法中提取
注意这里getExtra获取信息时输入的键值要相同, 否则返回nullPtr
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case '1': {
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra("return_data");
Log.d("FirstActivity", returnData);
}
break;
}
default:
}
return;
}
而后可在AS的Logcat中收到D/FirstActivity: Hello FirstActivity
运行状态
当一个活动位于返回栈的栈顶时,此时活动就处于运行状态。系统不会回收处于运行状态的活动
暂停状态
当一个活动不再处于栈顶位置,但仍然可见时,这个活动就进入了暂停状态。因为并不是每个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域。处于暂停状态的活动仍然是完全存活着的,系统一般不会回收这种活动,只有在内存极低的情况下,系统才会主动考虑回收这种活动。
停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统就会回收这种状态的活动,从而保证内存充足。
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() 方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态,此时的活动是可以和用户进行交互的,平时看到和接触最多的就是前台生存期下的活动。
当activity进入stop状态后, 有可能被系统finish释放内存并销毁, 而在这之前必定还会调用一个onSaveInstanceState()方法
如果销毁后又需要start这个activity ,则会中onCreate()方法开始
onSaveInstanceState()方法主要用于在activity将要被销毁时, 保存其中的数据, 并在调用onCreate()重新创建activity时将数据恢复
可以看到, onSaveInstanceState()方法和onCreate()方法都有一个Bundle类型的参数, 这俩就是数据保存于读取的关键:
Bundle简介:
Bundle主要用于传递数据;它保存的数据,是以key-value(键值对)的形式存在的。
Bundle和Intent区别
仅记录有用内容
使用系统内置的Theme:
Activity注册的.xml文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.activitylifecycletest">
<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=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog">
activity>
<activity android:name=".NormalActivity" />
<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>
其中DialogActivity设置使用的Theme为@style/Theme.AppCompat.Dialog
有很多系统内置的Theme可供使用, 暂时不做拓展
对每一个活动状态的函数进行重载 ,输出调试信息:
/**
* 在onCreate方法后调用,用于恢复UI状态
* 从savedInstanceState恢复UI状态
* 这个savedInstanceState也被传递给了onCreate
* 自Activity上次可见之后,只有当系统终止了该Activity时,才会被调用
* 在随后的Activity进程的可见生存期之前被调用
*
* @see android.app.Activity#onRestoreInstanceState(android.os.Bundle)
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.i(TAG, "Activity-----onRestoreInstanceState is call start.......");
super.onRestoreInstanceState(savedInstanceState);
}
/**
* 在可见生存期开始时调用
*
* @see android.app.Activity#onStart()
*/
@Override
protected void onStart() {
Log.i(TAG, "Activity-----onStart is call start.......");
super.onStart();
}
/**
* 在Activity活动状态生存期开始时调用
*
* @see android.app.Activity#onResume()
*/
@Override
protected void onResume() {
Log.i(TAG, "Activity-----onResume is call start.......");
super.onResume();
}
/*
* 把UI状态保存到outState中
* 如果进程被运行时终止并重启,那么这个Bundle会被传递给onCreate和onRestoreInstanceState
* @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
//保存数据:
outState.putString("data key", tempData);
}
/**
* 在Activity活动状态生存期结束时被调用
* 挂起UI更新,线程或者CPU密集的进程
*/
@Override
protected void onPause() {
Log.i(TAG, "Activity-----onPause is call start.......");
super.onPause();
}
/**
* 挂起UI更新,线程或者处理
* 当Activity不可见时,保存所有的编辑或者状态改变
* 调用这个方法后,进程可能会被终止
*/
@Override
protected void onStop() {
Log.i(TAG, "Activity-----onStop is call start.......");
super.onStop();
}
/**
* 清理所有的资源,包括结束线程,关闭数据库连接等
*/
@Override
protected void onDestroy() {
Log.i(TAG, "Activity-----onDestroy is call start.......");
super.onDestroy();
}
/**
* 当Activity由不可见到可见时调用
*/
@Override
protected void onRestart() {
Log.i(TAG, "Activity-----onRestart is call start.......");
super.onRestart();
}
修改onCreate()方法读取信息:
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "Activity-----onCreate is call start.......");
//由于经常出现null, 所以必须要防止
if (savedInstanceState != null) {
String savedData = savedInstanceState.getString("data key");
Log.i(TAG, savedData);
}
...
}
Android有四种启动活动的模式,分别是standard,singleTop,singleTask,singleInstance
可以在manifest.xml里通过android:launchMode属性来选择启动模式。
standard是活动默认的启动模式,在不进行显式指定的情况下,所有的活动都会自动使用这种启动模式。对于standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动的时候都会创建一个该活动的实例
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Log.d("FirstActivity:", "onCreate: "+this.toString());
Button button = findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
在这个当前活动里边启动当前活动,从逻辑来看确实没有什么意义,但是重点是研究standard模式,因此不必在意这段代码的实际用途。我们在oncreate()方法中添加了一条打印语句,用于打印当前活动的实例。我们可以看到打印的信息如图所示:
从打印的信息我们可以看出,每点击一次按钮 就会创建出一个当前活动的实例,也就是说,每点击一次就会在返回栈中添加一次实例,点击Back的时候,添加了几次就要点击多少次Back
在有些情况下,standard模式不太合理,活动明明已经在栈顶了,为什么再次启动的时候还需要再次创建一个新的活动实例呢? 这只是系统默认的一种启动模式而已,你完全可以更具自已的需求进行修改,比如说使用singleTop模式,当活动的模式指定为singleTop了,在启动活动的时候如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
修改AndroidMainifest.xml中活动的启动模式,改为singleTop,如下所示
<activity android:name=".FirstActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
然后重新运行程序,查看打印的日志,你会发现只创建了一个活动的实例,每当要启动一个活动时就会判断栈顶的元素是不是要启动的活动,如果是则直接使用栈顶的活动,否则创建新的活动。当要启动的活动未处于栈顶时,还是会创建新的实例的
使用singleTop模式可以很好的解决重复创建栈顶活动的问题,如但是如第二个所说的,如果该活动并没有处于栈顶的位置,还是可能会创建多个活动的实例。那么有没有什么办法让某个活动在应用程序中只有一个实例呢?这里就要用到了singleTask模式,当活动的模式指定为singleTask后,每次启动活动时先会在返回栈中检查是否存该互动的实例,如果有,则使用,并把在这个活动之上的所有互动统统出栈,如果没有则创建一个新的活动实例。
<activity android:name=".FirstActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
singleInstance应该是四种启动模式中最为复杂的一个了,不同于以上三种启动模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理活动, 想象以下场景,假设我们程序中有一个活动时允许其他程序调用的,如果我们想实现其他程序和我们的程序共享这个活动的实例,应该如何实现呢?使用前面三种肯定是做不到的,因为每个程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然创建了新的实例。而使用singleInstance模式就可以解决这个问题。在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪一个程序来访问这个活动,都使用的是同一个返回栈。
<activity android:name=".SecondActivity"
android:launchMode="singleInstance">
activity>
修改SecondActivity的启动模式为singleInstance,然后修改FirstActivity中onCreate()方法中的代码
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
//使用getTaskTId()方法获得当前返回栈back stack的ID
Log.d("FirstActivity:", "onCreate: "+getTaskId());
Button button = findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
在onCreate()方法中打印当前返回栈的Id,然后修改SecondActivity中onCreate()方法的代码
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Log.d("SecondActivity:", "onCreate: "+getTaskId());
Button button = findViewById(R.id.button_2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
startActivity(intent);
}
});
}
最后修改ThirdActivity的onCreate()中的代码,如下所示:
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
Log.d("ThirdActivity:", "onCreate: "+getTaskId());
}
}
同样在onCreate()方法中打印了当前返回栈的id,现在运行程序,在FirstActivity活动中点击按钮进入SecondActivity,在SecondActivity中点击按钮进入ThirdActivity中,查看打印的信息,如图所示:
当处于ThridActivity活动中时,点击Back将直接返回到了FirstActivity中,在按下返回键又会返回到SecondActivity中,然后点击Back才会退出程序,原理其实很简单,由于FirstActivity和Thirdactivity是放在同一个返回栈中的,所以第一次点击Back显示的是FirstActivity,当再次点击Back将这时当前返回栈已经空了,于是显示了另外一个返回栈的栈顶活动,即SecondActivity,最后按下Back,则会退出程序,因为所有返回栈都已经空了,所以就退出了程序。
这里主要介绍一些使用Activity的技巧:
这里主要利用在onCreate()方法中输出当前活动的类名来实现
但是由于对每一个活动的onCreate()方法都添加一个输出有点太麻烦了, 所以采用中间父类的方法, 在中间父类中设置输出类名的代码, 而后所有的自建活动都继承自此父类, 自动获得输出类名的特性
创建一个中间父类, 继承自自建活动的父类AppCompatActivity, 并加上输出代码:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
这种思路可以用在很多需要公共代码的地方
如下头的一键退出程序
这里主要利用一个自定义的活动管理器, 使用List容器储存活动, 实现add&delete方法用于常规的活动添加与删除, 并创建finishAll()方法用于一次性finish所有的活动
而后不论在程序的何处, 只需要调用finishAll()即可一键退出程序
活动管理器的定义:
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();
}
}
}
}
修改中间父类:
由于仅仅管理活动的增删, 没有涉及具体活动的内容, 所有这里仅仅使用的是类型参数为Activity的ArrayList
并且add & delete都是中间父类指针
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
//将现有活动添加到活动管理器中
ActivityCollector.addActivity(this);
//重写onDestory(), 在活动销毁时移除
@Override protected void onDestroy () {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
}
这里主要解决了活动启动另一个活动时的数据传递问题
之前的向下一个活动中传递数据
, 中介绍的是, 使用intent的putExtra()方法把数据塞到intent对象中并启动目标活动
但是, 主要问题是, 这样非常不方便
启动下一个活动时, 无论是参数数量还是数据类型都需要去看其源码
优化的方法是在下一个活动中设置public static 函数用于启动活动实例, 而后启动时仅仅需要向这个方法传递特定的参数即可, 并且还可以根据参数获知数据类型与数量:
有点类似于工厂方法
public static void actionStart(Context context, String datal, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("paraml", datal);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
启动方法:
button1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
SecondActivity.actionStart(FirstActivity.this,"datal","data2");
});
}
理活动的增删, 没有涉及具体活动的内容, 所有这里仅仅使用的是类型参数为Activity的ArrayList
并且add & delete都是中间父类指针
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
//将现有活动添加到活动管理器中
ActivityCollector.addActivity(this);
//重写onDestory(), 在活动销毁时移除
@Override protected void onDestroy () {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
}
这里主要解决了活动启动另一个活动时的数据传递问题
之前的向下一个活动中传递数据
, 中介绍的是, 使用intent的putExtra()方法把数据塞到intent对象中并启动目标活动
但是, 主要问题是, 这样非常不方便
启动下一个活动时, 无论是参数数量还是数据类型都需要去看其源码
优化的方法是在下一个活动中设置public static 函数用于启动活动实例, 而后启动时仅仅需要向这个方法传递特定的参数即可, 并且还可以根据参数获知数据类型与数量:
有点类似于工厂方法
public static void actionStart(Context context, String datal, String data2) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("paraml", datal);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
启动方法:
button1.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
SecondActivity.actionStart(FirstActivity.this,"datal","data2");
});
}