Android app开发学习笔记——Android四大组件-上
Android四大核心组件指的是Activity、Service、Broadcast Receiver 和ContentProvider。四大组件由Android系统进行管理和维护,一般都需要在清单文件中注册或者在代码中动态注册。
- Activity: 代表一个页面(串口)
- Service: 在后台默默做一些耗时工作
- Broadcast Receiver: 对感兴趣的外部事件进行监听,例如监听系统短信、手机网络状态改变等,当然也可以监听自己发送的广播
- ContentProvider: 多个应用程序之间的数据共享
一、Activity(活动)
Activity(活动)是用的最多、最基本的组件,是一种可以包含界面的组件。Activity代表一个页面,开发者可以通过setContentView(View)把界面(UI)放到该页面上。实现Activity需要进行下面两步:
- 重写onCreate方法,Activity创建的时候会调用这个方法
- 在onCreate中调用setContentView(int)设置界面布局,并且可以用findVIewById(int)查找界面上的某个控件
1.Activity的生命周期
- onCreate():活动第一次创建时被调用。在此完成活动初始化,如加载布局,绑定事件。
- onStart():此方法调用时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,也无法与用户进行交互
- onResume:此方法回调时说明Activity 已经在前台可见、可以跟用户进行交互了。
- onPause:此方法调用时表示Activity正在停止。
- onStop:表示Activity即将停止,此时Activity不可见,仅在后台运行。
- onRestart:表示Activity正在重启,当Activity由不可见变为可见状态时,该方法被回调。一般是用户打开了一个新的Activity,当前的Activity会被暂停(onPause和onStop被执行了),
接着又回到当前Activity 页面,onRestart方法就会被回调。 - onDestroy:此方法调用时表示Activity 即将被销毁,是Activity 生命周期的最后一个方法。在这个方法中可以做一些回收工作和最终的资释放(例如广播、Service等)。
2.启动Activity的两种方式
启动一个Activity,有显示和隐式两种启动方法
显示intent:
一般用于启动同一个应用中的Activity,效率高
用法如下:
先在AndroidManifest.xml中注册NextActivity
Intent intent =new Intent(this,NextActivity.class);
startActivity(intent);
隐式intent:
一般用于启动不同应用中的Activity,通过Intent Filter来实现。因为没有明确指出目标的Activity,所以效率相对低一些。
该方法同样需要在AndroidManifest.xml中注册NextActivity,只不过需要再添加intent-filter过滤器,在过滤器中增加标签,指定name的值(是一个字符串),隐式启动就是根据这个值来找到Activity.
<activity android:name=".NextActivity">
<intent-filter>
<action android:name="com.example.acticity.NextActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
接着用上面的name标签的值给Intent的构造方法
Intent intent = new Intent("com.example.acticity.SecondActivity");
startActivity(intent);
3.自定义Toast
Toast是Android系统的一种提示方式,显示某一段对话来提示用户,显示一段时间自动消息,不用占用屏幕空间
先自己封装一个CustomeToast类
public class CustomToast {
private static CustomToast _instance = null;
private Toast toast = null;
private final int MARGIN_DP = 50;
private CustomToast() {
}
public static CustomToast getInstance() {
if (_instance == null) {
_instance = new CustomToast();
}
return _instance;
}
public void cancel() {
if (toast != null) {
toast.cancel();
toast = null;
}
}
public void showToastCustom(Context ctx, String msg, int gravity) {
showToastCustom(ctx, msg,R.layout.toast_msg, R.id.txt_toast_message, gravity);
}
public void showToastCustom(Context ctx, String msg, int layoutResId, int txtResId, int gravity) {
cancel();//显示之前取消上次显示 这样每次都能显示最新的
try {
if (TextUtils.isEmpty(msg)) {
return;
}
View layout = View.inflate(ctx, layoutResId, null);
TextView txtMsg = layout.findViewById(txtResId);
txtMsg.setText(msg);
toast = new Toast(ctx);
toast.setDuration(Toast.LENGTH_SHORT);
if (gravity == Gravity.TOP) {
int marginVertical = (int) dip2px(ctx, MARGIN_DP);
toast.setGravity(gravity, 0,marginVertical);
} else if (gravity == Gravity.BOTTOM) {
int marginVertical = (int) dip2px(ctx, MARGIN_DP);
toast.setGravity(gravity, 0,marginVertical);
} else {
toast.setGravity(gravity, 0, 0);
}
toast.setView(layout);
toast.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static float dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
float result = dpValue * scale + 0.5f;
return result;
}
}
再编写如下Toast消息的页面toast_msg.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_custom_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/toast_bg"
android:orientation="vertical"
>
<TextView
android:id="@+id/txt_toast_message"
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dp"
android:text=""
android:textColor="#000000"/>
</LinearLayout>
其中toast_bg.xml如下
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="-90"
android:endColor="@color/浅蓝"
android:startColor="@color/white" />
<corners android:radius="5dip" />
<stroke
android:width="1dip"
android:color="#EEEEEE" />
</shape>
使用时仅需
CustomToast.getInstance().showToastCustom(Firstactivity.this,"干的漂亮!",Gravity.BOTTOM);
即可有如下效果
自定义Toast比自带的Toast好看,并且想显示什么效果只需要修改布局文件即可
4.Activity启动与退出动画
overridePendingTransition(enterAnim,exitAnim)
- 参数enterAnim表示的是从Activity A跳转到Activity B,进入B动画时的效果
- 参数exitAnim表示的是从Activity A跳转到Activity B,离开A动画时的效果
使用这个方法有两个注意事项
- overridePendingTransition方法需要在startActivity方法或者finish方法调用之后立即执行
- 若进入B或者离开A时不需要动画效果,则可以传值为0
例如使用Android自带的动画效果
Intent intent0=new Intent(Firstactivity.this,liebiao.class);
startActivity(intent0);
overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
当然还可以自定义动画效果
首先在res文件夹下新建anim文件夹,在anim文件夹下新建zoomin.xml文件
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:duration="@android:integer/config_mediumAnimTime"
android:fromXScale="2.0"
android:fromYScale="2.0"
android:pivotX="50%p"
android:pivotY="50%p"
android:toXScale="1.0"
android:toYScale="1.0"/>
</set>
其中其中
有一个scale标签,这个标签起到缩放效果。顺便解释一下 scale标签中各个属性的作用,
- android:duration:动画持续时间,以毫秒为单位。
- android:fromXScale:起始X尺寸比例。
- android:fromYScale:起始Y尺寸比例。
- android:pivotX:缩放起点X轴坐标,取值可以是数值(50)、百分数(50%)、百分数p(50%p)
当取值为数值时,缩放起点为View 左上角坐标加上具体数值像素;当取值为百分数时,表
示在当前View左上角坐标加上View宽度的具体百分比;当取值为百分数p时,表示在View
左上角坐标加上父控件宽度的具体百分比。 - android:pivotY:同 android:pivotX.
- android:toXScale:最终X尺寸比例。
- android:toYScale:最终Y尺寸比例。
再建立一个zoomout.xml文件
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:zAdjustment="top">
<scale
android:duration="@android:integer/config_mediumAnimTime"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%p"
android:pivotY="50%p"
android:toXScale=".5"
android:toYScale=".5"/>
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0"/>
</set>
- android:duration:动画持续时间,以毫秒为单位。
- android:fromAlpha:动画开始的透明度,取值为0.0-1.0,0.0表示完全透明,1.0表示保持原有状态不变
最后采用如下方法调用
overridePendingTransition(R.anim.zoomin, R.anim.zoomout);
ActivityOptions
Google在新的SDK中提供了另一种Activity的过渡动画——ActivityOptions,并且提供了兼容包ActivityOptionsCompat.
这个是Android5.0以上的版本才有的动画效果
内置的Activity之间的切换动画有一下几种效果
- Explode效果
- Slide 效果
- Fade 效果
- Shared Element效果
(1)Explode效果
在项目的res文件夹下新建transition文件夹,并在transition文件夹下新建explode.xml文件,内容如下
<?xml version="1.0" encoding="utf-8"?>
<explode xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="10000">
</explode>
用如下方法修改startActivity调用
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(Firstactivity.this).toBundle());
在目标activity的super之后,setContentView之前加入如下代码
先判断版本号足够再开启动画效果
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.KITKAT_WATCH){
Transition explpde = TransitionInflater.from(this).inflateTransition(R.transition.explode);
getWindow().setEnterTransition(explpde);
}
(2)Slide 效果
使用与Explode方法相同slide.xml文件如下
<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_cubic"
android:duration="1000"
>
<targets>
<target android:excludeId="@android:id/statusBarBackground"/>
</targets>
</slide>
- interpolator是属性设置插值器,就是用啦控制滑动的速度。这里使用SDK自带的decelerate_cubic
- duration设置动画执行时间
(3)Fade 效果
淡化效果与前门两种类似,新建fade.xml如下
<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000">
</fade>
(4)Shared Element 效果
共享元素效果与前面几种效果不同,共享元素是指定元素的过渡,给View增加android:transitionName属性
这里就不详细解释了可以参考Shared Element效果
5.Activity销毁
每个Activity都有自己的生命周期,打开了就要及时关闭,从而释放内存,也可以避免出现未知错误
要关闭当前的Activity,直接调用finish即可finish();
如果想关闭所有Activity,退出程序可以采用
法一
System.exit(0);//使用系统的方法,强制退出,终止程序
法二
android.os.Process.killProcess(android.os.Process.myPid());//直接终止当前的进程
上述两个方法虽然可以终止程序,但是会重启APP,并重新创建一个新的进程,于是我们只能自己想办法去实现
我们先建立一个MyApplication类,继承自Application,用于将所有Activity保存到栈中,便于删除时按顺序删除所有Activitiy。
public class MyApplication extends Application {
private static Stack<Activity> activityStack;//activity栈
/**
* 添加Activity到堆栈
*/
public void addActivity(Activity activity) {
if (activityStack == null) {
activityStack = new Stack<>();
}
if(!activityStack.contains(activity)){
Log.i("ansen","添加Activity:"+activity.getLocalClassName());
activityStack.add(activity);
}
}
/**
* 获取当前Activity(堆栈中最后一个压入的)
*/
public Activity currentActivity() {
Activity activity = activityStack.lastElement();
return activity;
}
public void removeActivity(Activity activity) {
if (activity != null&&activityStack.contains(activity)) {
Log.i("ansen","删除Activity:"+activity.getLocalClassName());
activityStack.remove(activity);
}
}
/**
* 结束所有Activity
*/
public void finishAllActivity() {
for (int i = 0, size = activityStack.size(); i < size; i++) {
if (null != activityStack.get(i)) {
activityStack.get(i).finish();
}
}
activityStack.clear();
Log.i("ansen","结束所有Activity");
}
}
我们还需要自定义一个BaseActivity作为所有Activity的父类,重写其创建(onCreate)与销毁(onDestory)方法,使得将创建的Activity自动存入栈中,不用每次单独对每个Activity操作。
public class BaseActivity extends AppCompatActivity {
private MyApplication myApplication;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (myApplication == null) {
// 得到Application对象
myApplication = (MyApplication) getApplication();
}
myApplication.addActivity(this);
}
//销毁所有Activity方法
public void finishAllActivity() {
myApplication.finishAllActivity();//调用myApplication销毁所有Activity方法
}
@Override
protected void onDestroy() {
super.onDestroy();
myApplication.removeActivity(this);
}
}
此时如果想要在某个Activity中退出程序,销毁所有Activity,只需要调用父类的BaseActivity的finishAllActivity方法即可。
6.Activity与Activity之间传递数据
(1)传递参数
打开一个界面的时候,可能需要把一些之传递过去,称为Activity传递参数。Activity与 Activity 之间基本采用Intent传递参数,通过Bundle实现。
当调用intent.putExtra方法时,系统会创建一个Bundle对象,Bundle对象有一个ArrayMap用来存储数据
此处我们传递了一个字符串
Intent intent=new Intent(this,SecondActivity.class);
//参数1:key 参数2:value
intent.putExtra("parameter","SecondActivity parameter");
startActivity(intent);
在SecindActivitry中通过如下方法获取刚才的值
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
String value=getIntent().getStringExtra("parameter");
Log.i("ansen",value);
}
}
(2)Activity的传值与回传值
采用startActivityForResult基本用法启动新的Activity
其作用是从A页面使用startActivityForResult()跳转到B页面,B页面点击返回时将新写入的值传回到A页面。
这里就不详细解释了
可以参考:
Android startActivityForResult基本用法
注意:在setResult后,要调用finish()销毁当前的Activity,否则无法返回到原来的Activity,就无法执行原来Activity的onActivityResult函数,看到当前的Activity没反应。
7.Activity软键盘弹出方式
在AndroidManifest.xml 中给 Activiy 设置android:windowSofilnputMode属性,可以避免输入法
面板遮挡输入框的问题。
android:windowSoftInputMode 属性有以下值:
- stateUnspecified:软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置。
- stateUnchanged:当这个Activity出现时,软键盘将一直保持在上一个Activity中的状态,无论是隐藏还是显示。
- stateHidden:用户选择Activity时,软键盘总是被隐藏。
- stateAlwaysHidden: 当该 Activity主窗口获取焦点时,软键盘也总是被隐藏的。
- state Visible:软键盘通常是可见的。
- stateAlwaysVisible:用户选择Activity时,软键盘总是显示的状态。
- adjustUnspecified:默认设置,通常由系统自行决定是隐藏或显示。
- adjustResize:该Activity总是调整屏幕的大小,以便留出软键盘的空间。
- adjustPan:当前窗口的内容将自动移动,以便当前焦点从不被键盘覆盖和用户总是能够看到输入内容的部分。
可以设置一个值或者多个值,多个用“”分割,例如:
android:windowSoftInputMode="stateVisible|adjustResize"
还有更多其他值,在这里就不列出了
8.Activity任务栈
Android 任务栈有以下特点:
- Android 任务栈又称为Task,是一个栈结构,具有后进先出的特性,用于存放我们的Activity组件。
- 我们每次打开一个新的Activity或者销毁Activity都会在任务栈中增加一条记录或者减少一条记录,任务栈保存Activity集合。
- 任务栈可以移动到后台,保留每一个Activity的状态,有序地给用户列出它们的任务,并且不丢失状态信息。
- 当把所有任务栈中的Activity清除出栈时,任务栈会被销毁,程序退出。
Android系统通过任务栈有序地管理每个Activity,并决定用哪个Activity跟用户交互,只有任务栈顶的 Activity才能跟用户进行交互。
任务栈的缺点:
- 每开启一次页面都会在任务栈中添加一个Activity,只有任务栈中的Activity全部清除出栈时,任务栈才会被销毁,程序才算真正退出,需要点击多次返回键才能退出程序。
- 每开启一次页面都会在任务栈中添加一个Activity,还会造成数据冗余,重复数据太多,会导致内存溢出的问题(OOM).
为了解决任务栈的缺点,我们引入了启动模式。
9.Activity 四种启动模式
启动模式(launchMode)在多个Activity跳转的过程中扮演着重要的角色,可以决定是否生成新的 Activity实例,是否重用已存在的Activity实例,是否和其他 Activity 实例共用一个task.这
里简单介绍一下task的概念。task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task.
Activity 一共有四种 launchMode:
- standard:系统默认的启动模式,即标准模式。
- singleTop:栈顶复用模式。
- singleTask:栈内复用模式。
- singleInstance:全局唯一模式。
怎么使用呢?很简单,只需要在AndroidManifest.xml文件中给activity 设置android:launchMode
属性即可。参考如下代码:
<activity android:name=".MainActivity" android:launchMode="standard">
</activity>
这四种启动模式对应不同的跳转模式。接下来详细介绍一下。
1.standard(标准模式)
standard 是Activity 默认的启动方式,如果你需要这种启动方式可以不需要设置android:launchMode 属性。这种模式是每次启动一个 Activiy 都会创建一个新的实例,不管这个例之前是否存在,这种模式下,谁启动了Activity,该Activity就属于启动该Activity的任务栈。
2. singleTop(栈顶复用模式)
这种模式下,如果新打开的Activity 已经在栈顶了,那就不会重新创建Activity实例,只会调用onNewIntent方法,如果新打开的Activity不在栈顶,而是在栈底或者栈中间,还是会创建一个
新的实例。
3. singleTask(栈内复用模式)
singleTask模式下,如果打开一个新的Activity,这个Activity在栈中存在,就会把这个Activity之上的Activity都销毁,然后这个Activity就会置顶。
假设现在有三个 Activity,即 Activity1、Activity2、Activity3,给 Activity1 设置成 singleTask模式。Activity1 启动Activity2,Activity2 启动 Activity3.这时任务栈里面有三个Activity,栈顶是Activity3,这个时候我们开启Activity1,运行之后只有Activity1还会存在。
使用 singleTask 模式有以下两点需要注意:
- 如果是其他App以singleTask模式启动 Activity1,将会创建一个新的任务栈。
- 如果以singleTask模式启动的Activity1 已经在后台的一个任务栈中,那么启动后,后台的任
务栈一起切换到前台。
4.singlelnstance(全局唯一模式)
singleInstance 模式比较特殊,这种模式下Activity 会单独占用一个栈,这个栈在整个系统中是唯一的。不同的App去打开singlelnstance模式的Activity,如果这个实例存在,不会重新创建。系统中永远只会有一个这样的实例。
部分内容和代码参考自《Android app开发从入门到精通》