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开发从入门到精通》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

for-nothing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值