Android 学习笔记(一)

607 阅读30分钟

创建 app 工程

创建 Empty Activity

image.png

项目配置

  • 程序名
  • 包名
  • 路径
  • 语言
  • SDK API
  • 构建程序 image.png

打印输出

Log.d("调试", "调试信息");
Log.e("错误", "错误信息");
Log.w("警告", "警告信息");
Log.i("一般", "一般信息");
Log.v("冗余", "冗余信息");

image.png

工程目录

切换到 App 查看目录

  • manifests/AndroidManifest.xml 是 app 运行下的配置信息
  • java 存放java 测试文件和源文件
  • res:
    • drawable:存放图形描绘文件与图片文件
    • layout:存放App页面的布局文件
    • mipmap:存放App 启动图标
    • values:存放一些常量定义文件,例如字符string.xml、像素常量colors.xml、样式风格定义style.xml

Gradle Scripts 工程编译配置文件

  1. build.gradle:该文件分为项目级别与模块级别,用于描述 app 工程编译规则
  2. proguard-rules.pro:该文件描述java代码的混淆规则
  3. gradle.properties 该文件用于配置编译工程的命令行参数,一般无需改动
  4. settings.gradle:配置需要编译哪些模块,初始为 include app 表示编译app模块
  5. local.properties:项目的本地配置文件,在工程编译时生成,描述开发者电脑的环境配置,包括sdk本地路径,Ndk本地路径

image.png image.png

gradle 配置

Gradle 是一个项目自动化构建工具,帮我们做了依赖、打包、部署、发布、各种渠道的差异管理工作。

阅读 模块的 gradle 配置信息

plugins {
    // 插件用于构建 Android 应用程序
    id("com.android.application")
}

android {
    // 应用程序的命名空间
    namespace = "com.example.myapplication"
    // api 33,对应 Android 13
    compileSdk = 33

    // 默认配置
    defaultConfig {
        // 应用程序包名
        applicationId = "com.example.myapplication"
        // 最小支持的sdk
        minSdk = 21
        // 当前sdk
        targetSdk = 33
        // 版本代码
        versionCode = 1
        // 版本名称
        versionName = "1.0"
        // 单元测试的位置
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    // 构建类型设置
    buildTypes {
        // 发布版本相关的设置
        release {
            // 是否开启代码混淆
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
    // 编译选项
    compileOptions {
        // 使用java8 的特性
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

// 库依赖
dependencies {
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.8.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

gradle 配置插件使用国内库

AndroidManifest.xml Activity 认识

Activity 是一个应用程序组件,用户可以交互完成某一项任务

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!--
     allowBackup:允许备份
     icon:手机上的图标
    label:应用程序的显示名称
    roundIcon:圆角名称、
    supportsRtl:是否支持阿拉伯语波斯语,从右往左排列,true支持
    theme:显示主题
    -->
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <!--activity 舞台
        name:默认的舞台也是第一个页面,展示是什么,配置主页
        -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

界面显示与应用

使用 html 描绘布局,使用java完成逻辑

LinearLayout 是 Android 中的一个布局容器,用于按照水平或垂直方向排列其子视图。它是 ViewGroup 的子类,允许通过指定方向将子视图排列在一条线上

自适应写一个

<?xml version="1.0" encoding="utf-8"?><!--
LinearLayout: 是ViewGroup 的子类
layout_width 和 layout_height 设置布局的宽度为与父容器相匹配,即占满整个父容器的宽度
orientation="vertical" 设置布局的方向为垂直方向,子视图将垂直排列
android:gravity="center" 是一个布局属性,设置 ViewGroup 内部的子视图相对于容器的对齐方式
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <!--
    这是一个简单的 TextView 元素的 XML 布局代码片段。
    在这个 TextView 中,展示了 "Hello Word" 这个文本。
    通过设置 id 属性,我们可以在代码中引用和操作这个 TextView。
    tools:ignore="HardcodedText" 用于告诉开发工具在静态分析时忽略硬编码文本的警告。
    宽度高度为适应宽高
-->
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Word"
        tools:ignore="HardcodedText" />

</LinearLayout>

在java中修改

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 加载布局文件
    setContentView(R.layout.activity_main);
    // 获取界面上的文本框
    TextView viewById = findViewById(R.id.tv);
    // 修改文本
    viewById.setText("你好世界!");
}

创建新页面与java代码

页面在 app\src\main\res\layout 下面创建,选择 Layout xml file

image.png

  1. 输入文件名称,下面是默认的跟标签

image.png

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/main_name2"/>

</LinearLayout>

在主 xml 中添加按钮,点击跳转新页面

<!--
创建一个按钮,高度和宽度由内容撑开,文本引用 string中的button
-->
<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/button"/>
  1. 创建 java 代码,在 java目录下面第一个包下创建一个java文件
package com.example.myapplication;

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class myActivity2 extends AppCompatActivity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*
         * AppCompatActivity 中的方法,用于指定布局,布局为 activity_main2
         */
        setContentView(R.layout.activity_main2);
    }
}
  1. 在 main 根目录下 AndroidManifest.xml 中添加 activity 活动页

创建 myActivity2

public class myActivity2 extends AppCompatActivity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*
         * AppCompatActivity 中的方法,用于指定布局,布局为 activity_main2
         */
        setContentView(R.layout.activity_main2);
    }
}

在主 activity 中跳转

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView viewById = findViewById(R.id.tv);
        viewById.setText("你好世界!");

        Button button = findViewById(R.id.button);
        // 创建点击事件
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 跳转到 我们先创建的页面
                Intent intent = new Intent();
                // 跳转指定类
                /*
                 * MainActivity.this 因为在匿名内部类,拿不到this
                 * myActivity2.class 跳转到这里
                 */
                intent.setClass(MainActivity.this, myActivity2.class);
                // 执行跳转
                startActivity(intent);
            }
        });
    }
}

单位

  • px:图像元素,在大小不一样的屏幕上,越大的屏幕,越清晰,每一个px的像素占据的大小不一样
  • dpi(像素密度):它的尺寸只与屏幕尺寸有关,也叫做dp,相同英寸的屏幕,计算的dp是一样的

计算规则:假设屏幕英寸 4.95,宽 1920 高 1080
dpi =19202+108024.95\frac{\sqrt[]{1920^2 + 1080^2}}{4.95}
dpi = 444.62

  • density(密度):指屏幕上每平方英寸(2.54^2 平方厘米)含有的像素点数量,上面得出的结果是 444.62 ^ 2
  • dip:就是将 dpi 转化成 px 的工具,计算1 dpi = 多少 px,因为最终是以 px显示在页面上

总结:像素密度越高,越清晰。

对于相同尺寸的手机,即使分辨率不同,同dp的组件占用的屏幕比例也会相同

文本

设置文本颜色

TextView viewById = findViewById(R.id.tv);
// 使用 color 类设置文本颜色
viewById.setTextColor(Color.GREEN);
// 另一种设置方式,0xff00ff00
// 0x 十六进制,ff 不透明,00 红色,ff绿色,00
viewById.setTextColor(0xff00ff00);

视图

设置 组件的宽高

  • 视图宽度通过属性 android:layout_width 设置
  • 视图高度通过属性 android:layout_height 设置

值下列 3 种

  • match_parent:表示与上级视图保持一致。
  • wrap_content:表示与内容自适应。
  • 以dp为单位的具体尺寸
<TextView
    android:id="@+id/tv"
    android:layout_width="50sp"
    android:layout_height="wrap_content"
    android:text="无什么也没有"
    tools:ignore="HardcodedText" />

使用 java 代码修改宽高

需要确保 xml 中 设置的是由内容撑开大小

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 获取 组件
        TextView viewById = findViewById(R.id.tv);
        // 获取布局参数,注意默认用的是px布局,需要把 dp 转换成 px
        ViewGroup.LayoutParams params = viewById.getLayoutParams();
        params.width = dip2px(this, 300);
//        params.height = 0;
        // 重新设置
        viewById.setLayoutParams(params);
    }

    // 根据手机屏幕从 dp 转换成 px像素
    public static int dip2px(Context context, float dbValue) {
        // 获取当前手机像素密度(1个dp = 几个px)
        float density = context.getResources().getDisplayMetrics().density;
        // 四舍五入取整
        return Math.round(dbValue * density);
    }

间距

设置视图的间距有两种方式:

  • 采用layout_margin属性
    1. layout_margin
    2. layout_marginLeft
    3. layout_marginTop
    4. layout_marginRight
    5. layout_marginBottom
  • 采用padding属性
    1. padding
    2. paddingLeft
    3. paddingTop
    4. paddingRight
    5. paddingBottom
<!--设置背景色为黄色-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:gravity="center"
    android:background="#00aaff"
    android:orientation="vertical">

    <!--中间布局颜色为黄色-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffff99"
        android:layout_margin="20dp"
        android:padding="60dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ff0000"/>
    </LinearLayout>
    </LinearLayout>

对齐方式

设置视图的对齐方式有两种途径:

  • 采用layout_gravity属性,它指定了当前视图相对于上级视图的对齐方式。
  • 采用gravity属性,它指定了下级视图相对于当前视图的对齐方式。

layout_gravity与gravity的取值包括:left、top、right、bottom,还可以用竖线连接各取值,例如left|top表示即靠左又靠上,也就是朝左上角对齐。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:background="#ffff99"
    android:gravity="center"
    android:orientation="horizontal">

    <!--  第一个子布局背景红色,中下对齐,下级视图靠左对齐
    layout_weight="1":分配权重最大
    底部对齐
    gravity:控制自己的内部属性
    -->
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:layout_gravity="bottom"
        android:layout_marginEnd="10dp"
        android:layout_weight="1"
        android:background="#ff0000"
        android:gravity="left">

        <View
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#00ffff" />

    </LinearLayout>

    <!-- 顶端对齐 -->
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:layout_gravity="top"
        android:layout_weight="1"
        android:background="#ff0000"
        tools:ignore="Suspicious0dp"
        android:gravity="right">

        <View
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#00ffff" />
    </LinearLayout>

</LinearLayout>

布局

线性布局LinearLayout

线性布局内部的各视图有两种排列方式:

  • orientation属性值为:
    • horizontal:内部视图在水平方向从左往右排列。
    • vertical:内部视图在垂直方向从上往下排列。
    • 默认水平方向排列

权重:指的是线性布局的下级视图各自拥有多大比例的宽高。

权重属性名叫:layout_weight

  • layout_width填0dp时:layout_weight 表示水平方向的宽度比例。
  • layout_height填0dp时,layout_weight表示垂直方向的高度比例。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff99"
    android:orientation="vertical">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="竖排排第一个"
            android:textColor="#000000"
            android:textSize="17sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="竖排排第二个"
            android:textColor="#000000"
            android:textSize="17sp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="横排第一个"
            android:textColor="#000000"
            android:textSize="17sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="横排第二个"
            android:textColor="#000000"
            android:textSize="17sp" />

    </LinearLayout>

</LinearLayout>

相对布局RelativeLayout

  • 相对布局的下级视图位置由其他视图决定。
  • 用于确定下级视图位置的参照物分两种:
    • 与该视图自身平级的视图
    • 该视图的上级视图(也就是它归属的RelativeLayout)

如果不设定下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角。

相对位置的属性取值相对位置说明
layout_toLeftOf当前视图在指定视图的左边
layout_toRightOf当前视图在指定视图的右边
layout_above当前视图在指定视图的上方
layout_below当前视图在指定视图的下方
layout_alignLeft当前视图与指定视图的左侧对齐
layout_alignRight当前视图与指定视图的右侧对齐
layout_alignTop当前视图与指定视图的顶部对齐
layout_alignBottom当前视图与指定视图的底部对齐
layout_centerInParent当前视图在上级视图中间
layout_centerHorizontal当前视图在上级视图的水平方向居中
layout_centerVertical当前视图在上级视图的垂直方向居中
layout_alignParentLeft当前视图与上级视图的左侧对齐
layout_alignParentRight当前视图与上级视图的右侧对齐
layout_alignParentTop当前视图与上级视图的顶部对齐
layout_alignParentBottom当前视图与上级视图的底部对齐

演示一个指定id,和不指定id,不指定id跟父级,并且可以指定多个

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/ssss"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我在中间"
        android:layout_centerInParent="true"
        android:textColor="#000000"
        android:textSize="16sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我在水平中间"
        android:layout_centerHorizontal="true"
        android:textColor="#000000"
        android:textSize="16sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="在中间的左边"
        android:layout_toLeftOf="@id/ssss"
        android:textColor="#000000"
        android:textSize="16sp" />

</RelativeLayout>

网格布局GridLayout

网格布局,能实现多行多列的排序。

默认从左往右、从上到下排列

新增了两个属性:

  • columnCount属性,它指定了网格的列数,即每行能放多少个视图
  • rowCount属性,它指定了网格的行数,即每列能放多少个视图
<?xml version="1.0" encoding="utf-8"?><!--
columnCount:2 行
rowCount:2列
-->
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnCount="2"
    android:rowCount="2">

    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:background="#ffcccc"
        android:gravity="center"
        android:text="浅红色" />
    
    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:background="#ffaa00"
        android:gravity="center"
        android:text="浅红色" />

</GridLayout>

滚动视图ScrollView

滚动视图有两种:

  • ScrollView,垂直方向的滚动视图
  • HorizontalScrollView 水平方向的滚动视图

注意事项:

  1. 垂直方向滚动时,layout_width 属性值设置为 match_paren,layout_height属性值设置为wrap_content。

  2. 水平方向滚动时,layout_width 属性值设置为 wrap_content,layout_height 属性值设置为match_parent

  3. Binary XML file line #32 in com.example.myapplication:layout/activity_main: HorizontalScrollView can host only one direct child,如果报错上面的错误,表示出现了多个节点。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!--水平方向的滚动视图,当前高度 200-->
    <HorizontalScrollView
        android:layout_width="wrap_content"
        android:layout_height="200dp">

        <!-- 滚动视图,只能出现唯一的节点 -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <TextView
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#aaffff" />

            <TextView
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#ffff00"/>

        </LinearLayout>
    </HorizontalScrollView>

    <!--
    垂直方向滚动视图,高度自适应
    android:fillViewport="true":强制填充满整个屏幕,即使子节点未达到
    -->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">

        <!-- 滚动视图,只能出现唯一的节点 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#00ff00" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#ffffaa"/>

        </LinearLayout>

    </ScrollView>

</LinearLayout>

按钮

Button 由 TextView 派生而来,Button 默认拥有背景,内部居中对其,需要注意下面两个属性。

  • textAllCaps:默认为false,表示不转不写,开启会将文本转换为大写
  • onClick属性:用来接管用户的点击动作,指定了点击按钮时要触发哪个方法
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <!--定义一个按钮,不转大写文字,定义函数 onclick-->
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello Word"
        android:textAllCaps="false"
        android:onClick="dbClick"
        />

    <TextView
        android:id="@+id/tv_string"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点击按钮查看哦!"/>

</LinearLayout>

java代码

public class MainActivity extends AppCompatActivity {

    // 声明一个文本视图
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 在主方法中获取
        textView = findViewById(R.id.tv_string);
    }

    // activity_main.xml 中按钮绑定了点击方法doClick
    // 必须是公共的,私有的会报错
    // view 实际是点击的源
    public void dbClick(View view) {
        textView.setText("我看到内容啦!");
        // 获取 button 按钮定义的文本内容
        Button view1 = (Button) view;
        // 转成文本
        String text = view1.getText().toString();
        Log.d("2222", text);
    }

}

代码实现监听点击

@Override
protected void onCreate(Bundle savedInstanceState) {
    // 在主方法中获取
    Button view = findViewById(R.id.tv_string);
    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ((Button) v).setText("我点击了!");
        }
    });
}

技巧:这样可以全部按钮事件进行整合

findViewById(R.id.btn_cancel).setOnClickListener(this);

代码实现监听长按点击

@Override
protected void onCreate(Bundle savedInstanceState) {
请    // 在主方法中获取
    Button view = findViewById(R.id.tv_string);
    view.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            ((Button) v).setText("我长按了!");
            return true;
        }
    });
}

不可用按钮

  • 不可用按钮:按钮不允许点击,即使点击也没反应,同时按钮文字为灰色;
  • 可用按钮:按钮允许点击,点击按钮会触发点击事件,同时按钮文字为正常的黑色;

是否允许点击由enabled属性控制,属性值为true时表示允许点击,为false时表示不允许点击。

xml 禁用按钮

<!--
android:enabled="false" false 禁用按钮,默认true
-->
<Button
    android:id="@+id/tv_string"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:enabled="false"
    android:text="Hello Word"
    android:textAllCaps="false" />

代码设置禁用

// 在主方法中获取
Button view = findViewById(R.id.tv_string);
// 设置为 true
view.setEnabled(true);

图像

图像视图ImageView

注意导入图片必须以英文开头

默认 fitCenter

  • src="@drawable/apple":引用图片路径
<!--
     android:scaleType="fitXY" 全面拉伸
     android:scaleType="fitStart" 使图片位于上方或者左侧并拉伸
     android:scaleType="fitEnd" 使图片位于下方方或者右侧并拉伸
     android:scaleType="fitCenter" 使图片位于视图中间并拉伸
     android:scaleType="center" 使图片位于中间
     android:scaleType="centerCrop" 使图片充满视图并拉伸中间
     android:scaleType="centerInside" 使图片缩小并位于中间,只能缩小
    -->
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="220dp"
    android:scaleType="center"
    android:src="@drawable/apple" />

在代码中设置图片

// 获取布局中的 ImageView 组件,通过 ID 为 R.id.img 来查找
ImageView imgId = findViewById(R.id.img);

// 设置 ImageView 显示的图像资源,R.drawable.apple 是一个指向 res/drawable 目录下的图像资源的引用
imgId.setImageResource(R.drawable.apple);

图像按钮ImageButton

<!--
src 设置资源 scaleType 设置缩放
-->
<ImageButton
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:src="@drawable/apple"
    android:scaleType="fitCenter"
    />

按钮背景色不能更换问题

在 res/values/themes.xml 将 Style 更改下面

<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge">

同时展示文本与图像

<!--
    android:drawableLeft="@drawable/ic_launcher_foreground" 设置图标在左侧
    android:padding="8dp" 图片与文字的间距
    -->
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:drawableLeft="@drawable/ic_launcher_foreground"
        android:text="图标在左边"
        android:padding="8dp"
        android:textColor="@color/white" />

活动组件—Activity

打开新页面

// 跳转新页面
findViewById(R.id.nextButton).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 当前页面,目标页面
        startActivity(new Intent(MainActivity.this, MainActivity2.class));
    }
});

回到上一个页面,关闭当前页面

// 点击完成,回到上一个页面,也就是关闭当前页面
findViewById(R.id.finiesh).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 关闭当前页面
        finish();
    }
});

生命周期

  • onCreate:创建活动。把页面布局加载进内存,进入了初始状态
  • onStart:开始活动。把活动页面显示在屏幕上,进入了就绪状态
  • onResume:恢复活动。活动页面进入活跃状态,能够与用户正常交互
  • onPause:暂停活动。页面进入暂停状态,无法与用户正常交互。
  • onStop:停止活动。页面将不在屏幕上显示。
  • onDestroy:销毁活动。回收活动占用的系统资源,把页面从内存中清除。
  • onRestart:重启活动。重新加载内存中的页面数据。
  • onNewIntent:重用已有的活动实例。

打开新页面的方法调用顺序为

graph LR
onCreate --> onStart --> onResume

关闭旧页面的方法调用顺序为onPause→onStop→onDestroy

graph LR
onPause --> onStop --> onDestroy

image.png

跳转页面声明周期执行顺序

  • 主页面活动创建(onCreate)
  • 主页面活动就绪(onStart)
  • 主页面活动中(onResume)

跳转到第二页面

  • 主页面活动暂停(onStop)
  • 第二页面活动创建(onCreate)
  • 第二页面活动就绪(onStart)
  • 第二页面活动中(onResume)
  • 主页面活动停止(onStop)

从第二页面返回到第一页面

  • 第二页面活动暂停(onStop)
  • 主页面活动重启(onRestart)
  • 主页面活动就绪(onStart)
  • 主页面活动中(onResume)
  • 第二页面活动停止(onStop)
  • 第二页面活动销毁(onRestoty)
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("one", "主页面活动创建");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e("one", "主页面活动就绪");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e("one", "主页面活动中");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e("one", "主页面活动暂停");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e("one", "主页面活动停止");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("one", "主页面活动销毁");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.e("one", "主页面活动重启");
    }

活动存入栈中,是先进后出的数据结构。

配置启动模式

<!--
android:launchMode="standard" 配置启动模式为 标准模式,也就是默认的
android:launchMode="singleTop" 单顶模式
android:launchMode="singleTask" 单任务模式
android:launchMode="singleInstance" 单实例模式
-->
<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="standard">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
</application>

标准模式:按照栈的先进后出

活动栈状态操作结果
ActivityA启动 ActivityBActivityA -> ActivityB
ActivityA -> ActivityB启动 ActivityCActivityA -> ActivityB -> ActivityC
ActivityA -> ActivityB -> ActivityC返回ActivityA -> ActivityB
ActivityA -> ActivityB启动 ActivityDActivityA -> ActivityB] -> ActivityD

单顶模式:检查栈顶是否是启动的activity,如果是就不会重新启动,采用复用

活动栈状态操作结果
ActivityA启动 ActivityBActivityA -> ActivityB
ActivityA -> ActivityB启动 ActivityCActivityA -> ActivityB -> ActivityC
ActivityA -> ActivityB -> ActivityC启动 ActivityCActivityA -> ActivityB -> ActivityC
ActivityA -> ActivityB -> ActivityC启动 ActivityDActivityA -> ActivityB -> ActivityC -> ActivityD

单任务实例:栈里面存在活动,复用该活动,并清除该活动以上所有活动

活动栈状态操作结果
ActivityA启动 ActivityBActivityA -> ActivityB
ActivityA -> ActivityB启动 ActivityCActivityA -> ActivityB -> ActivityC
ActivityA -> ActivityB -> ActivityC启动 ActivityBActivityA -> ActivityB

单实例模式:启动新活动时,将该活动的实例放入一个新的栈中,原栈的实例列表保持不变。 适用于需要与应用的其余部分隔离的任务。

栈1状态栈2状态操作结果
[ActivityA]启动 ActivityB[ActivityA] -> [ActivityB]
[ActivityA][ActivityB]

通过代码设置启动标识

启动标志说明备注
Intent.FLAG_ACTIVITY_NEW_TASK开辟一个新的任务栈。类似于 Standard ,不同于如果原来没有栈,则会创建新的栈
Intent.FLAG_ACTIVITY_SINGLE_TOP当栈顶为待跳转的活动实例时,则重用栈顶的实例。等同于 singleTop
Intent.FLAG_ACTIVITY_CLEAR_TOP当栈中存在待跳转的活动实例时,则重新创建一个新实例,并清除原实例上方的所有实例。类似于 singleTask 不同的是会先 onDestory 在 onCreate
Intent.FLAG_ACTIVITY_NO_HISTORY栈中不保存新启动的活动实例。与 Standard 类似,区别于不保存活动实例
Intent.FLAG_ACTIVITY_CLEAR_TASK跳转到新页面时,栈中的原有实例都被清空。该标志非常暴力,需要结合 Intent.FLAG_ACTIVITY_NEW_TASK 使用
// 跳转新页面
findViewById(R.id.nextButton).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, MainActivity2.class);
        // 配置启动方式,开辟一个新的任务线
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
});

显式Intent和隐式Intent

显示 Intent

Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信

  • 标明本次通信请求从哪里来、到哪里去、要怎么走。
  • 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
  • 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。

Intent 属性

元素名称设置方法说明与用途
ComponentsetComponent组件,它指定意图的来源与目标
ActionsetAction动作,它指定意图的动作行为
DatasetData即Uri,它指定动作要操纵的数据路径
CategoryaddCategory类别,它指定意图的操作类别
TypesetType数据类型,它指定消息的数据类型
ExtrasputExtras扩展信息,它指定装载的包裹信息
FlagssetFlags标志位,它指定活动的启动标志
// (1) 在 Intent 构造函数中指定
Intent intent1 = new Intent(this, ActNextActivity.class);
startActivity(intent1);

// (2) 调用 setClass 方法指定
Intent intent2 = new Intent();
intent2.setClass(this, ActNextActivity.class);
startActivity(intent2);

// (3) 调用 setComponent 方法指定
Intent intent3 = new Intent();
ComponentName component = new ComponentName(this, ActNextActivity.class);
intent3.setComponent(component);
startActivity(intent3);

隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配。

隐式 Intent

动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。

系统动作如下:

系统动作常量名系统动作的常量值说明
ACTION_MAINandroid.intent.action.MAINApp启动时的入口
ACTION_VIEWandroid.intent.action.VIEW向用户显示数据
ACTION_SENDandroid.intent.action.SEND分享内容
ACTION_CALLandroid.intent.action.CALL直接拨号
ACTION_DIALandroid.intent.action.DIAL准备拨号
ACTION_SENDTOandroid.intent.action.SENDTO发送短信
ACTION_ANSWERandroid.intent.action.ANSWER接听电话

实现拨号功能

// 找到布局中的按钮,并设置点击事件监听器
findViewById(R.id.nextButton).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 在按钮点击事件中执行以下操作

        // 创建一个新的 Intent 对象,用于实现拨号功能
        Intent intent = new Intent();

        // 设置 Intent 的动作为拨号操作
        intent.setAction(Intent.ACTION_DIAL);

        // 创建一个 Uri 对象,指定电话号码
        Uri uri = Uri.parse("tel:" + 12345);

        // 将 Uri 对象设置为 Intent 的数据,表示拨打指定的电话号码
        intent.setData(uri);

        // 启动 Intent,系统将打开拨号应用并填充指定的电话号码
        startActivity(intent);
    }
});

Activity 之间传递参数

Intent 对象通过putExtra方法可以携带各种类型的参数信息

注意:启动表示会影响数据传递

A 页面

// 创建一个新的 Intent 对象,从当前活动 MainActivity 启动到 CalulatotActiviti
Intent intent = new Intent(MainActivity.this, CalulatotActiviti.class);

// 将键值对数据添加到 Intent 中,键为 "key",值为 "value"
intent.putExtra("key", "value");

// 启动目标活动 CalulatotActiviti
startActivity(intent);

B 页面

// 获取当前活动收到的 Intent
Intent intent = getIntent();

// 从 Intent 中获取字符串类型的数据,键名为 "key"
String key = intent.getStringExtra("key");

// 打印获取到的数据,如果数据不为空,输出数据;否则输出 "空的"
Log.e("222", key != null ? key : "空的");

类型数据读写

数据类型读方法写方法
整型数getIntputInt
浮点数getFloatputFloat
双精度数getDoubleputDouble
布尔值getBooleanputBoolean
字符串getStringputString
字符串数组getStringArrayputStringArray
字符串列表getStringArrayListputStringArrayList
可序列化结构getSerializableputSerializable

使用 Bundle 传递参数

A 页面

// 创建日期格式化对象,指定格式为 "yyyy-MM-dd HH:mm:ss"
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 获取当前日期和时间,并使用格式化对象将其格式化为字符串
String formattedDate = dateFormat.format(new Date());

// 创建一个 Bundle 对象,用于存放键值对数据
Bundle bundle = new Bundle();

// 将格式化后的日期字符串添加到 Bundle 中,键为 "key"
bundle.putString("key", formattedDate);

// 创建一个 Intent 对象
Intent intent = new Intent();

// 将 Bundle 对象放入 Intent 中
intent.putExtras(bundle);

// 启动目标活动
startActivity(intent);

B 页面

// 获取当前活动收到的 Intent
Intent intent = getIntent();

// 从 Intent 中获取携带的 Bundle 对象
Bundle extras = intent.getExtras();

// 从 Bundle 中获取字符串类型的数据,键名为 "key"
String key = extras.getString("key");

A页面 接收 B 页面的数据

// 创建一个 ActivityResultLauncher 对象,用于启动新的 Activity 并处理返回的结果
ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                // 在这里处理从 CalulatotActiviti 返回的结果
                // 从返回的 ActivityResult 中获取返回的 Intent
                Intent data = result.getData();

                // 通过 Intent 获取附加的 Bundle 数据
                Bundle extras = data.getExtras();

                // 从 Bundle 中获取具体的字符串数据
                String data1 = extras.getString("data");

                // 打印获取到的数据
                Log.i("222", data1);
            }
        });



findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // 创建意图对象
        Intent intent = new Intent(MainActivity.this, CalulatotActiviti.class);
        // 创建包裹
        Bundle bundle = new Bundle();
        // 创建日期格式化对象,指定格式
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        bundle.putString("key", dateFormat.format(new Date()));
        intent.putExtras(bundle);
        // 打开新页面,并传递数据
        resultLauncher.launch(intent);
    }
});

B 页面返回数据

// 点击事件
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 创建意图
        Intent data = new Intent();
        // 创建包裹
        Bundle bundle = new Bundle();
        bundle.putString("data", "数据传递成功!");
        data.putExtras(bundle);
        // 返回数据
        setResult(Activity.RESULT_OK, data);
        // 关闭页面
        finish();
    }
});

获取相册中的图片并展示

// 图片标签
private ImageView imageView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 获取 ImageView 引用
    imageView = findViewById(R.id.image_tu);

    // 创建一个 ActivityResultLauncher 对象,用于启动新的 Activity 并处理返回的结果
    ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result != null) {
                        // 获取返回的 Intent
                        Intent data = result.getData();
                        // 从 Intent 中获取选中图片的 Uri
                        Uri selectedImageUri = data.getData();
                        Log.i("3333", selectedImageUri.toString());
                        
                        // 将选中图片的 Uri 设置到 ImageView 中显示
                        imageView.setImageURI(selectedImageUri);
                    }
                }
            });

    // 设置按钮点击事件,启动图片选择操作
    findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // 创建意图对象,用于打开图片选择器
            Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
            // 启动图片选择器,并等待返回结果
            resultLauncher.launch(intent);
        }
    });
}

调用相机

private ImageView imageView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    imageView = findViewById(R.id.image_tu);

    // 获取相机返回值
    ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    // 处理相机返回的结果
                    Intent data = result.getData();

                    if (result.getResultCode() == Activity.RESULT_OK && data != null) {
                        // 从 Intent 中获取缩略图数据
                        Bundle extras = data.getExtras();
                        if (extras != null) {
                            Bitmap thumbnail = (Bitmap) extras.get("data");
                            imageView.setImageBitmap(thumbnail);
                        } else {
                            // 处理无法获取缩略图的情况
                            // 可以根据之前设置的保存路径加载完整图片
                            // Uri photoUri = getPhotoUri();
                            // imageView.setImageURI(photoUri);
                        }
                    } else {
                        // 拍照取消或失败
                        // 可以根据需要添加相应的逻辑
                    }
                }
            });

    findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            // 启动相机
            resultLauncher.launch(intent);
        }
    });
}

广播组件—Broadcast、服务组件—Service,以及如何在组件之间传递消息数据

Broadcast 用于 Android 组件之间的灵活通信,与Activity的区别在于:

  • 活动只能一对一通信,广播可以一对多,一人发送广播,多人接收处理
  • 对于发送者来说,广播不需要考虑接收者有没有在工作,接收方在工作就接收广播,不在工作就丢弃广播。
  • 对于接收者来说,因为可能会收到各式各样的广播,所以接收方要自行过滤符合条件的广播,才能进行解包处理。

方法:

  • sendBroadcast:发送广播。
  • registerReceiver:注册接收器,一般在onStart或onResume方法中注册。
  • unregisterReceiver:注销接收器,一般在onStop或onPause方法中注销。

广播的收发步骤分为三步:

  1. 发送标准广播
  2. 定义广播接收器
  3. 开关广播接收器(即使关闭,避免内存泄漏)

标准广播

异步性: 标准广播是异步进行的,发送广播的组件不需要等待广播接收器处理完毕。

效率较高: 由于是异步的,发送标准广播不会阻塞发送者的执行,因此效率较高。但也因为是异步的,不能保证接收者按照注册的先后顺序接收广播。

不可终止: 发送标准广播后,无法中止或修改广播的传递。这也意味着无法在广播传递过程中终止其他接收器的执行。

不可取消: 一旦广播发送出去,就无法取消。

适用场景: 标准广播适用于不需要同步和有序执行的场景,例如通知其他应用程序某个事件已经发生。

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    // 默认显示的广播信息
    private String mDesc = "这里查看广播信息";

    // 标准广播接收器
    private StandardReceiver standardReceiver;

    // 指定广播动作
    private final static String STANDARD_ACTION = "com.itcast";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化 TextView
        textView = findViewById(R.id.v_count);

        // 设置按钮点击事件
        findViewById(R.id.btn_guangbo).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 创建指定动作的意图
                Intent intent = new Intent(STANDARD_ACTION);
                // 发送广播
                sendBroadcast(intent);
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 在 Activity 启动时注册广播接收器
        standardReceiver = new StandardReceiver();
        IntentFilter intentFilter = new IntentFilter(STANDARD_ACTION);
        registerReceiver(standardReceiver, intentFilter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 在 Activity 停止时注销广播接收器,以避免内存泄漏
        unregisterReceiver(standardReceiver);
    }

    // 广播接收器类
    private class StandardReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent != null && intent.getAction() != null && intent.getAction().equals(STANDARD_ACTION)) {
                // 接收到广播时更新 TextView 的文本
                mDesc = "收到一条标准的广播:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
                textView.setText(mDesc);
            }
        }
    }
}

有序广播

一个广播存在多个接收器,这些接收器需要排队收听广播,这意味着该广播是条有序广播。

先收到广播的接收器A,既可让其他接收器继续收听广播,也可中断广播不让其他接收器收听。

设置优先级,让接收器比其他接收器前面收到信息

private TextView textView;

private String mDesc = "这里查看广播信息\n";

// 有序广播接收器A
private orderAReceiver orderAReceiver;

// 有序广播接收器B
private orderBReceiver orderBReceiver;

// 指定广播接头对号
private final static String ORDER_ACTION = "有序广播";

// 选中框
private CheckBox ck_abort;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 赋值组件
    textView = findViewById(R.id.v_count);
    ck_abort = findViewById(R.id.checkFail);

    findViewById(R.id.btn_guangbo).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 创建指定动作的意图
            Intent intent = new Intent(ORDER_ACTION);
            // 发送有序广播
            sendOrderedBroadcast(intent, null);
        }
    });


}

/**
 * 给接收器设置优先级,让 B的优先级更高, 即使 A先注册
 * B 也会在 A前面收到信息
 * 配置接收指定信息
 */
@Override
protected void onStart() {
    super.onStart();
    orderAReceiver = new orderAReceiver();
    IntentFilter intentFilterA = new IntentFilter(ORDER_ACTION);
    // 设置 接收器 A 的优先级 8
    intentFilterA.setPriority(8);
    registerReceiver(orderAReceiver, intentFilterA);

    orderBReceiver = new orderBReceiver();
    IntentFilter intentFilterB = new IntentFilter(ORDER_ACTION);
    // 设置 接收器 B 的优先级 10
    intentFilterB.setPriority(10);
    registerReceiver(orderBReceiver, intentFilterB);
}

@Override
protected void onStop() {
    super.onStop();
    unregisterReceiver(orderAReceiver);
    unregisterReceiver(orderBReceiver);
}

/**
 * 定义有序广播接收器
 * 也要继承 BroadcastReceiver
 * 允许中断广播,后面的接收器将不在接收
 */
// 实例 A
private class orderAReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null && intent.getAction().equals(ORDER_ACTION)) {
            mDesc += "\n接收器A 收到一条有序的广播:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
            textView.setText(mDesc);

            if (ck_abort.isChecked()) {
                // 中断广播
                abortBroadcast();
            }
        }
    }
}

// 实例 B
private class orderBReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null && intent.getAction().equals(ORDER_ACTION)) {
            mDesc += "\n接收器B 收到一条有序的广播:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
            textView.setText(mDesc);
            if (ck_abort.isChecked()) {
                // 中断广播
                abortBroadcast();
            }
        }
    }
}

静态广播注册(震动)

创建

image.png

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

同时 AndroidManifest.xml 自动添加接收器的节点配置

<receiver
    android:name="receiver.MyReceiver"
    android:enabled="true"
    android:exported="true">
</receiver>

在自动接收器中,设置功能,比如震动提醒

由于震动功能需要获取权限,需要在 AndroidManifest.xml 中添加

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- 声明应用程序需要的权限 -->
    <!-- 请求声音权限 -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <!-- 请求震动权限 -->
    <uses-permission android:name="android.permission.VIBRATE" />
public class MyReceiver extends BroadcastReceiver {

    // 指定广播接头对号
    private final static String ORDER_ACTION = "震动广播";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(ORDER_ACTION)) {
            // 从系统服务中获取震动管理器
            Vibrator vibrator = context.getSystemService(Vibrator.class);
            /**
             * createOneShot 一次性震动
             * 5000 震动一秒
             * VibrationEffect.DEFAULT_AMPLITUDE 默认震动
             */
            VibrationEffect vibrationEffect = VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE);
            // 使用创建的震动效果进行震动
            vibrator.vibrate(vibrationEffect);
        }
    }
}

业务实现

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(R.id.btn_guangbo).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 创建指定动作的意图
            Intent intent = new Intent(MyReceiver.STATIC_ACTION);
            // 发送静态广播,需要通过 setComponent 方法指定接收器的接收器类
            ComponentName componentName = new ComponentName(MainActivity.this, MyReceiver.class);
            intent.setComponent(componentName);
            // 发送静态广播
            sendBroadcast(intent);
        }
    });
}

定时器

设置定时器

// 获取闹钟管理器
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
// 设置一次性定时器,参数一:类型 参数二:执行时机(毫秒) 参数三:延迟意图 PendingIntent,即要执行的操作
alarmManager.set(AlarmManager.RTC_WAKEUP, 1000, pendingIntent);

设置一次性定时器,不同的是,即使设备保持空闲状态,也会执行定时器。

// 获取闹钟管理器
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setAndAllowWhileIdle();

重复定时器
设置重复定时器,参数一:类型,参数二:首次执行时间(毫秒),参数三,下次执行的间隔时间(毫秒)
参数四:延迟的意图

// 获取闹钟管理器
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating();

取消定时器

// 获取闹钟管理器
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
// 取消,参数意图
alarmManager.cancel();

PendingIntent

  • 代表延迟的意图,指向的组件不会马上激活
  • 是一类消息的组合,包含目标的 Intent 对象、请求代码和请求方法等信息

定时器广播接收器

public class MainActivity extends AppCompatActivity {

    // 声明一个闹钟广播事件的标识串
    private String ALARM_ACTION = "com.example.chapter04.alarm";

    // 描述信息
    private String mDesc = "";

    private TextView viewById;

    // 广播接收器
    private AlarmReceiver alarmReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewById = findViewById(R.id.text);

        // 发送广播
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 创建一个意图,用于触发闹钟广播
                Intent intent = new Intent(ALARM_ACTION);
                // 创建一个与用于广播延迟意图
                // FLAG_IMMUTABLE 表示这个意图,不可改变
                PendingIntent broadcast = PendingIntent.getBroadcast(MainActivity.this,
                        0, intent, PendingIntent.FLAG_IMMUTABLE);

                // 从系统服务中获取闹钟管理器
                AlarmManager alarmManager = getSystemService(AlarmManager.class);
                long delayTime = System.currentTimeMillis() + 5 * 1000;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    // 允许在空闲时发送广播
                    alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, delayTime, broadcast);
                } else {
                    // 设置一次性闹钟,延迟发送闹钟广播
                    alarmManager.set(AlarmManager.RTC_WAKEUP, delayTime, broadcast);
                }
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 创建并注册闹钟广播接收器
        alarmReceiver = new AlarmReceiver();
        // 创建意图过滤器,指定事件来源
        IntentFilter filter = new IntentFilter(ALARM_ACTION);
        // 注册接收器
        registerReceiver(alarmReceiver, filter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 注销接收器
        unregisterReceiver(alarmReceiver);
    }

    // 定义一个闹钟广播接收器
    public class AlarmReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent != null) {
                // 更新描述信息
                mDesc = "闹钟时间到达!" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
                        Locale.getDefault()).format(new Date());
                // 在界面上显示描述信息
                viewById.setText(mDesc);

                // 获取震动管理器
                Vibrator vibrator = context.getSystemService(Vibrator.class);
                // 创建一次性震动效果
                VibrationEffect oneShot = VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE);
                // 执行震动
                vibrator.vibrate(oneShot);
            }
        }
    }
}

重复执行广播,在广播接收器的 onReceive 方法中,再调用点击事件触发的函数,即可