android教程

1 android studio

https://developer.android.google.cn/studio/

安装

创建Empty项目

 

 

 选择开发语言和支持的android版本

 

 

 创建模拟器

 

 

 2 android工程结构

 app模块

(1)mainfests,下面只有一个xml文件,AndroidMainfest.xml,它是App的运行配置文件

(2)java目录,下面有3个包,其中第一个是存放当前模块的java代码,后面两个包存放测试代码

(3)res,存放当前模块的资源文件:

  drawable目录存放图形描述文件与图片文件

  layout存放App页面的布局文件

  mipmap存放app的启动图标

  values目录存放一些常量定义文件,例如字符串常量string.xml、像素常量dimens.xml、颜色常量color.xml、样式风格常量style.xml

Gradle Script下面主要是工程的编译配置文件,主要有

(1)build.gradle,该文件分为项目级和模块级两种,用于描述App工程的编译规则

(2)proguard-rules.pro,改文件用于描述java代码的混淆规则

(3)gradle.properties,该文件用于配置编译工程的命令行参数,一般无需改动

(4)settings.gradle,该文件配置了需要编译哪些模块。初始内容为include ':app',表示只编译app模块

(5)local.properties,项目的本地配置文件,它在工程编译时自动生成,用于描述开发者电脑的环境配置,包括SDK的本地路径、NDK的本地路径等

 创建新的App页面:

第一步,layout目录下创建xml文件

 

 

 activity_main2.xml

<?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/text2"/>

</LinearLayout>

 

第二步,创建xml文件对应的java代码

public class MainActivity2 extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

 

第三步,在AndroidManifest.xml中注册页面配置

 <activity android:name=".MainActivity2"/> 

 

3 简单控件

3.1 文本控件

我们创建一个新的module:chapter03

创建一个layout,activity_text_view.xml

 

 

<?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">

</LinearLayout>

创建TextViewActivity.java

public class TextViewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_view);
        TextView tv_hello = findViewById(R.id.tv_hello);
        tv_hello.setText(R.string.hello);
    }
}

string.xml中定义字符串常量

 <string name="hello">你好,世界</string> 

4 布局

4.1 线性布局LinearLayout

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

orientation=horizontal和vertical

线性布局的权重,指的是线性布局的下级视图各自拥有多达比例的宽高,权重属性名叫layout_weight,但该属性不在LinearLayout节点设置,而在线性布局的直接下级视图设置,表示该下级视图占据的宽高比例。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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="横排第一个"
            android:textSize="17sp"
            android:textColor="#000000"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="横排第二个"
            android:textSize="17sp"
            android:textColor="#000000"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:text="竖排第一个"
            android:textSize="17sp"
            android:textColor="#000000"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:text="竖排第二个"
            android:textSize="17sp"
            android:textColor="#000000"/>
    </LinearLayout>
</LinearLayout>

4.2 相对布局RelativeLayout

相对布局的下级视图位置由其他视图决定。用于确定下级视图位置的参照物分两种:与该视图自身平级的视图,该视图的上级视图。如果不设置下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="#ffffff"
        android:text="我在中间"
        android:textSize="11sp"
        android:textColor="#000000"/>
</RelativeLayout>

 

 4.3 网格布局GridLayout

网格布局默认从左向右,从上向下排列。columnCount指定网格列数,rowCount指定网格行数

<?xml version="1.0" encoding="utf-8"?>
<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="0dp"
        android:layout_columnWeight="1"
        android:layout_height="60dp"
        android:background="#ffcccc"
        android:gravity="center"
        android:text="浅红色"
        android:textColor="#000000"
        android:textSize="17sp"/>
    <TextView
        android:layout_width="0dp"
        android:layout_columnWeight="1"
        android:layout_height="60dp"
        android:background="#ffaa00"
        android:gravity="center"
        android:text="橙色"
        android:textColor="#000000"
        android:textSize="17sp"/>
    <TextView
        android:layout_width="0dp"
        android:layout_columnWeight="1"
        android:layout_height="60dp"
        android:background="#00ff00"
        android:gravity="center"
        android:text="绿色"
        android:textColor="#000000"
        android:textSize="17sp"/>
    <TextView
        android:layout_width="0dp"
        android:layout_columnWeight="1"
        android:layout_height="60dp"
        android:background="#660066"
        android:gravity="center"
        android:text="深紫色"
        android:textColor="#000000"
        android:textSize="17sp"/>

</GridLayout>

 

 4.4 滚动视图ScrollView

ScrollView:垂直滚动

HorizontalScrollView:水平滚动

<?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">
    <HorizontalScrollView
        android:layout_width="wrap_content"
        android:layout_height="200dp">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <View
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#aaffff"/>
            <View
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#ff0000"/>
        </LinearLayout>
    </HorizontalScrollView>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <View
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#aaffff"/>
            <View
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#ff0000"/>
        </LinearLayout>
    </ScrollView>

</LinearLayout>

4.5 按钮控件Button

Button由TextView派生而来。新增了两个属性textAllCaps,它指定了是否将英文字母转为大写,默认时true。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"
    android:padding="5dp">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="下面的按钮英文默认大写"
        android:textColor="@color/black"
        android:textSize="17sp"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World"
        android:textColor="@color/black"
        android:textSize="17sp"
        android:onClick="doClick"/>
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这里查看按钮点击结果"
        android:textColor="@color/black"
        android:textSize="17sp"/>
</LinearLayout>
public class ButtonStyleActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_button_style);
        textView = findViewById(R.id.tv_result);
    }

    public void doClick(View view)
    {
        String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
        textView.setText(desc);
    }
}

上面例子中我们在xml文件中使用了onClick指定java代码,不建议这样,这样使布局文件和后端代码高耦合,而应该使用setOnClickListener方式添加监听器

点击监听器,通过setOnClickListener方法设置。按钮被按下少于500ms时会触发点击事件

长按监听器,通过setOnLongClickListener方法设置。按钮内按下超过500ms时触发

activity_button_click.xml

<?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:padding="5dp">
    <Button
        android:id="@+id/btn_click_single"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="单独按钮点击事件"
        android:textColor="@color/black"
        android:textSize="17sp"/>
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这里查看按钮点击结果"
        android:textColor="@color/black"
        android:textSize="17sp"/>
    <Button
        android:id="@+id/btn_click_public"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="公共按钮点击事件"
        android:textColor="@color/black"
        android:textSize="17sp"/>
</LinearLayout>

ButtonClickActivity.java

public class ButtonClickActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_result;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_button_click);
        tv_result = findViewById(R.id.tv_result);
        Button btn_click_single = findViewById(R.id.btn_click_single);
        btn_click_single.setOnClickListener(new MyOnClickListener(tv_result));

        Button btn_click_public = findViewById(R.id.btn_click_public);
        btn_click_public.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if(view.getId() == R.id.btn_click_public) {
            String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
            tv_result.setText(desc);
        }
    }

    static class MyOnClickListener implements View.OnClickListener {
        private final TextView textView;
        public MyOnClickListener(TextView textView) {
            this.textView = textView;
        }

        @Override
        public void onClick(View view) {
            String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
            textView.setText(desc);
        }
    }
}

按钮的长按示例

activity_button_long_click.xml

<?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">
    <Button
        android:id="@+id/btn_long_click"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="按钮长按事件"
        android:textColor="@color/black"
        android:textSize="17sp"/>
    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这里查看按钮点击结果"
        android:textColor="@color/black"
        android:textSize="17sp"/>
</LinearLayout>

ButtonLongClickActivity.java

public class ButtonLongClickActivity extends AppCompatActivity {

    private TextView tv_result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_button_long_click);
        tv_result = findViewById(R.id.tv_result);
        Button btn_long_click = findViewById(R.id.btn_long_click);
        btn_long_click.setOnLongClickListener(new View.OnLongClickListener(){
            @Override
            public boolean onLongClick(View view) {
                String desc = String.format("%s 您点击了按钮:%s", DataUtil.getNowTime(), ((Button)view).getText());
                tv_result.setText(desc);
                return true;
            }
        });

    }
}

图形定制按钮 

我们可以指定按钮的图形定制,比如按钮的背景,以及按下时按钮的背景

drawable/btn_nine_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/anniu" android:state_pressed="true"/>
    <item android:drawable="@drawable/anniu2"/>
</selector>

activity_drawable_state.xml

<?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:padding="5dp"
    android:gravity="center">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="默认样式的按钮"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/btn_nine_selector"
        android:text="定制样式的按钮"
        android:padding="5dp"/>
</LinearLayout>

 

4.6 图像视图ImageView

图像视图展示的图片通常在res/drawable目录。设置显式图片的方式有两种:在xml中用android:src设置和在java代码中调用setImageResource设置

activity_image_scale.xml

<?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">
    <ImageView
        android:id="@+id/iv_scale"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:layout_marginTop="5dp"
        android:src="@drawable/flower"
        android:scaleType="fitCenter"/>

</LinearLayout>

ImageScaleActivity.java

public class ImageScaleActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_scale);
        ImageView iv_scale = findViewById(R.id.iv_scale);
        iv_scale.setImageResource(R.drawable.flower);
    }
}

我们使用android:scaleType="fitCenter"调节图片的fit方式。

4.7 图像按钮ImageButton

ImageButton和Button的区别

Button即可显式文本也可显示图片,ImageButton只能显式图片

ImageButton上的图像可按比例缩放,而Button通过背景设置的图像会拉伸变形

Button只能靠背景显式一张图片,而ImageButton可分别在前景和背景显式图片,从而实现两个图片叠加的效果

<?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">
    <ImageButton
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:src="@drawable/flower"
        android:scaleType="fitCenter"/>

</LinearLayout>

4.8 Shape

drawable/shape_rect_gold.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 指定了形状内部填充的颜色 -->
    <solid android:color="#ffdd66"/>
    <!-- 指定了形状轮廓的粗细和颜色 -->
    <stroke android:width="1dp" android:color="#aaaaaa"/>
    <!-- 指定了形状四个圆角的半径 -->
    <corners android:radius="10dp"/>
</shape>

drawable/shape_oval_rose.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <!-- 指定了形状内部填充的颜色 -->
    <solid android:color="#ff66aa"/>
    <!-- 指定了形状轮廓的粗细和颜色 -->
    <stroke android:width="1dp" android:color="#aaaaaa"/>
</shape>

layout/activity_drawable_shape.xml

<?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">
    <View
        android:id="@+id/v_content"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_rect"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="圆角矩形背景"/>
        <Button
            android:id="@+id/btn_oval"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="椭圆背景"/>
    </LinearLayout>
</LinearLayout>

DrawableShapeActivity.java

public class DrawableShapeActivity extends AppCompatActivity implements View.OnClickListener {

    private View v_content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drawable_shape);
        v_content = findViewById(R.id.v_content);
        findViewById(R.id.btn_rect).setOnClickListener(this);
        findViewById(R.id.btn_oval).setOnClickListener(this);
        v_content.setBackgroundResource(R.drawable.shape_rect_gold);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_rect:
                v_content.setBackgroundResource(R.drawable.shape_rect_gold);
                break;
            case R.id.btn_oval:
                v_content.setBackgroundResource(R.drawable.shape_oval_rose);
                break;
        }
    }
}

 4.9 其他控件后续补充

5 Activity

5.1 Activity的启动和结束

从当前页面跳到新页面:startActivity(原页面.this, 目标页面.class)

从当前页面回到上一个页面,相当于关闭当前页面:finish()

activity_act_start.xml

<?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">
    <Button
        android:id="@+id/btn_act_next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳到下一个页面"/>
</LinearLayout>

activity_act_finish.xml

<?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">
    <ImageView
        android:id="@+id/iv_back"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:padding="5dp"
        android:src="@drawable/back"/>
    <Button
        android:id="@+id/btn_finish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="完成"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="按返回键或点击完成按钮均可返回上一个Activity"/>

</LinearLayout>

ActStartActivity.java

public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_start);
        findViewById(R.id.btn_act_next).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        startActivity(new Intent(this, ActFinishActivity.class));
    }
}

ActFinishActivity.java

public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_finish);
        findViewById(R.id.iv_back).setOnClickListener(this);
        findViewById(R.id.btn_finish).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
            finish();
        }
    }
}

修改清单文件AndroidManifest.xml

        <activity
            android:name=".ActFinishActivity"
            android:exported="false" />
        <activity
            android:name=".ActStartActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

5.2 Activity生命周期

onCreate 表示Activity正在被创建,这也是Activity的生命周期的第一个方法。
onRestart 表示Activity正在重新启动,此生命周期只有在onPause与onStop都执行过才会被调用
onStart 表示Activity正在被启动,即将开始,此时Activity已经可见但是还没有出现在前台,还无法交互
onResume 表示Activity已经可见并出现在前台可以与用户进行交互
onPause 表示Activity正在停止
onStop 表示Activity停止并不可见
onDestroy 表示Activity即将被销毁,这是Activity的最后一个回调

我们还以上一节中的ActStartActivity为例说明

public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {
    private static String TAG = "song";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"ActStartActivity onCreate");
        setContentView(R.layout.activity_act_start);
        findViewById(R.id.btn_act_next).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        startActivity(new Intent(this, ActFinishActivity.class));
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"ActStartActivity onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"ActStartActivity onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"ActStartActivity onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"ActStartActivity onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"ActStartActivity onDestroy");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"ActStartActivity onRestart");
    }
}

 5.3 Activity启动模式

任务栈

任务栈是Activity实例的容器。通常当一个Android应用程序启动时,系统会创建一个任务栈,此后这个应用程序所启动的Activity都将在这个任务栈中被管理。

 

 

 standard模式

该模式是Activity启动的默认启动方式,每启动一个Activity就会在栈顶创建一个新的实例

singleTop模式(栈顶复用模式)

启动Activity时,首先判断栈顶是否已经存在该Activity的实例,如果存在则不创建,复用栈顶的实例。

singleTask模式(栈内复用模式)

当启动一个Activity实例时,首先判断栈中是否已经存在该Activity实例,如果存在,则不创建,并且把这个Activity实例上面的所有Activity实例全部弹出栈。此后当我们点击返回按钮时,点击一次就退出页面了。

singleInstance模式

该种模式的Activity启动时,当要启动的Activity实例在栈中不存在,系统先创建一个新的任务栈,然后压入Activity;当要启动的Activity实例已存在,系统把该Activity所在的任务栈移到前台,从而使Activity展示。

例如创建三个Activity A、B、C,将B的启动模式设置成singleInstance,点击A跳转到B,点击B跳转到C,这时候点击返回按钮,你会发现会从C直接返回到A而不是B。因为B和A、C不是一个任务栈,B是单独、独立的一个任务栈。

示例:两个页面,A可以跳转到B,B也可以跳转到A,经过几轮跳转之后,点击返回B返回A,A再返回退出应用。

activity_jump_first.xml

<?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">
    <Button
        android:id="@+id/btn_jump_second"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到第二个页面"/>
</LinearLayout>

activity_jump_second.xml

<?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">
    <Button
        android:id="@+id/btn_jump_first"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到第一个页面"/>
</LinearLayout>

JumpFirstActivity.java

public class JumpFirstActivity extends AppCompatActivity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jump_first);
        findViewById(R.id.btn_jump_second).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this,JumpSecondActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
        startActivity(intent);
    }
}

JumpSecondActivity.java

public class JumpSecondActivity extends AppCompatActivity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jump_second);
        findViewById(R.id.btn_jump_first).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this,JumpFirstActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
        startActivity(intent);
    }
}

AndroidManifest.xml

        <activity
            android:name=".JumpSecondActivity"
            android:exported="false" />
        <activity
            android:name=".JumpFirstActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

 

 

 

实例2:一个登陆页面,当登陆后,跳转到登陆成功页面,当在登陆成功页面返回时,不返回登陆页面,而是直接退出app

activity_login_input.xml

<?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">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这里是登陆页面,省略了用户名密码输入框"/>
    <Button
        android:id="@+id/btn_jump_success"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="跳转到登陆成功页面"/>
</LinearLayout>

activity_login_success.xml

<?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">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这里是登陆页面,当返回时不必返回登陆页面,直接退出app,请按返回键查看效果"/>
</LinearLayout>

LoginInputActivity.java

public class LoginInputActivity extends AppCompatActivity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login_input);
        findViewById(R.id.btn_jump_success).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this,LoginSuccessActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);//设置启动标志:跳转到新页面时,栈中原来实例被清空,同时开辟新的任务栈
        startActivity(intent);
    }
}

LoginSuccessActivity.java

public class LoginSuccessActivity extends AppCompatActivity {

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

 5.4 Intent

显式Intent

直接指定源Activity和目的Activity。

 Intent intent = new Intent(this,JumpSecondActivity.class); 

隐式Intent

没有明确指定要跳转的目标Activity,只给出一个动作字符串让系统自动匹配。比如调用打电话功能,我们不知道具体的Activity对应的类是什么,这时我们需要使用隐式Intent

activity_action_uri.xml

<?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">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="点击以下按钮将向号码12345发起请求"/>
    <Button
        android:id="@+id/btn_dial"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到拨号页面"/>
    <Button
        android:id="@+id/btn_sms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到短信页面"/>
    <Button
        android:id="@+id/btn_my"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳到我的页面"/>
</LinearLayout>

ActionUriActivity.java

public class ActionUriActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_action_uri);
        findViewById(R.id.btn_dial).setOnClickListener(this);
        findViewById(R.id.btn_sms).setOnClickListener(this);
        findViewById(R.id.btn_my).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        String phoneNo = "12345";
        switch (v.getId()) {
            case R.id.btn_dial:
                intent.setAction(Intent.ACTION_DIAL);//设置意图动作
                Uri uri = Uri.parse("tel:"+phoneNo);
                intent.setData(uri);
                startActivity(intent);
                break;
            case R.id.btn_sms:
                intent.setAction(Intent.ACTION_SENDTO);//设置意图动作
                Uri uri2 = Uri.parse("smsto:"+phoneNo);
                intent.setData(uri2);
                startActivity(intent);
                break;
            case R.id.btn_my:
                intent.setAction("android.intent.action.song");//设置意图动作
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                startActivity(intent);
                break;
        }
    }
}

然后,我们修改在module3中之前写过的一个app的配置文件AndroidManifest.xml(添加绿色显示的内容,表示允许其他应用打开该应用)

        <activity
            android:name=".ImageButtonActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.song" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

 5.5 Activity间传送数据

Intent使用Bundle对象存放待传递的数据信息

向下一个Activity传送数据

activity_act_send.xml

<?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">
    <TextView
        android:id="@+id/tv_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="今天天气真不错"/>
    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="发送以上文字"/>
</LinearLayout>

activity_act_receive.xml

<?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">
    <TextView
        android:id="@+id/tv_receive"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

ActSendActivity.java

public class ActSendActivity extends AppCompatActivity implements View.OnClickListener{

    private TextView tv_send;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_send);
        tv_send = findViewById(R.id.tv_send);
        findViewById(R.id.btn_send).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, ActReceiveActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("request_time", DateUtil.getNowTime());
        bundle.putString("request_data", tv_send.getText().toString());
        intent.putExtras(bundle);
        startActivity(intent);
    }
}

ActReceiveActivity.java

public class ActReceiveActivity extends AppCompatActivity {

    private TextView tv_receive;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_receive);
        tv_receive = findViewById(R.id.tv_receive);
        //从上一个页面传来得意图中获取快递包裹
        Bundle bundle = getIntent().getExtras();
        String request_time = bundle.getString("request_time");
        String request_content = bundle.getString("request_data");
        String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
        tv_receive.setText(desc);
    }
}

AndroidMainfest.xml

        <activity
            android:name=".ActReceiveActivity"
            android:exported="false" />
        <activity
            android:name=".ActSendActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

 

向上一个Activity传送数据

上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作

下一个页面接收并解析请求数据,进行相应处理

下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹

上一个页面重写方法onActivityResult,解析得到下一个页面的返回数据

activity_act_request.xml

<?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">
    <TextView
        android:id="@+id/tv_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/btn_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="传送请求数据"/>
    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

activity_act_response.xml

<?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">
    <TextView
        android:id="@+id/tv_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/btn_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="返回应答数据"/>
    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

ActRequestActivity.java

public class ActRequestActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String mRequest = "你睡了吗,来我家睡吧";
    private ActivityResultLauncher<Intent> register;
    private TextView tv_response;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_request);
        TextView tv_request = findViewById(R.id.tv_request);
        tv_response = findViewById(R.id.tv_response);
        tv_request.setText("待发送的消息为:"+mRequest);
        findViewById(R.id.btn_request).setOnClickListener(this);

        register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if(null != result) {
                Intent intent = result.getData();
                if(null != intent && result.getResultCode() == Activity.RESULT_OK) {
                    Bundle bundle = intent.getExtras();
                    String response_time = bundle.getString("response_time");
                    String response_data = bundle.getString("response_data");
                    String desc = String.format("收到请求消息:\n请求时间为:%s\n请求内容为:%s", response_time, response_data);
                    tv_response.setText(desc);
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, ActResponseActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("request_time", DateUtil.getNowTime());
        bundle.putString("request_data", mRequest);
        intent.putExtras(bundle);
        register.launch(intent);
    }
}

 

ActResponseActivity.java

public class ActResponseActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String mResponse = "我还没睡";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_response);
        TextView tv_request = findViewById(R.id.tv_request);
        //从上一页传来的数据
        Bundle bundle = getIntent().getExtras();
        String request_time = bundle.getString("request_time");
        String request_content = bundle.getString("request_data");
        String desc = String.format("收到请求消息:\n请求时间为:%s\n请求内容为:%s", request_time, request_content);
        tv_request.setText(desc);

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

        TextView tv_response = findViewById(R.id.tv_response);
        tv_response.setText("待返回的消息为:"+mResponse);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        bundle.putString("response_time", DateUtil.getNowTime());
        bundle.putString("response_data", mResponse);
        intent.putExtras(bundle);
        //携带意图返回上一个页面,RESULT_OK表示处理成功
        setResult(Activity.RESULT_OK, intent);
        finish();
    }
}

 5.6 从string.xml获取配置

activity_read_string.xml

<?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">
    <TextView
        android:id="@+id/tv_resource"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

ReadStringActivity.java

public class ReadStringActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_read_string);
        TextView tv_resource = findViewById(R.id.tv_resource);
        //从string.xml中获取配置信息
        String value = getString(R.string.weather_str);
        tv_resource.setText(value);
    }
}

string.xml

<resources>
    <string name="app_name">chapter04</string>
    <string name="weather_str">晴天</string>
</resources>

5.7 从元数据中获取配置信息

元数据是指AndroidManifest.xml的<meta-data>标签中的配置。

代码中获取元数据的步骤:

调用getPackageManager方法获取当前应用的包管理器

调用包管理器的getActivityInfo方法获得当前活动的信息对象

活动信息对象的metaData时Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值

AndroidManifest.xml

        <activity
            android:name=".MetaDataActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="weather" android:value="晴天"/>
        </activity>

activity_meta_data.xml

<?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">
    <TextView
        android:id="@+id/tv_meta"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

MetaDataActivity.java

public class MetaDataActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_meta_data);
        TextView tv_meta = findViewById(R.id.tv_meta);
        PackageManager pm = getPackageManager();
        try {
            ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Bundle bundle = info.metaData;
            String weather = bundle.getString("weather");
            tv_meta.setText(weather);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 6 数据存储

1 SharePreferences

SharePreferences是Android的一个轻量级存储工具,采用的存储结构是key-value键值对的形式。

activity_share_write.xml

<?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">

    <EditText
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存到共享参数"/>

</LinearLayout>

ShareWriteActivity.java

public class ShareWriteActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name;
    private SharedPreferences preferences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share_write);
        et_name = findViewById(R.id.et_name);

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

        preferences = getSharedPreferences("config", Context.MODE_PRIVATE);

        reload();
    }

    private void reload() {
        String name = preferences.getString("name", null);
        if(null != name)
        {
            et_name.setText(name);
        }
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("name", name);
        editor.commit();
    }
}

2 SQLite

2.1 使用SQLiteDatabase创建和删除数据库

activity_database.xml

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_database_create"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="创建数据库"/>
        <Button
            android:id="@+id/btn_database_delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="删除数据库"/>
    </LinearLayout>
    <TextView
        android:id="@+id/tv_database"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

DatabaseActivity.java

public class DatabaseActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_database;
    private String mDatabaseName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_database);
        tv_database = findViewById(R.id.tv_database);
        findViewById(R.id.btn_database_create).setOnClickListener(this);
        findViewById(R.id.btn_database_delete).setOnClickListener(this);
        mDatabaseName = getFilesDir()+"test.db";
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_database_create:
                SQLiteDatabase db = openOrCreateDatabase(mDatabaseName, Context.MODE_PRIVATE, null);
                tv_database.setText("数据库创建成功");
                break;
            case R.id.btn_database_delete:
                boolean result = deleteDatabase(mDatabaseName);
                if(result) {
                    tv_database.setText("数据库删除成功");
                }
                else {
                    tv_database.setText("数据库删除失败");
                }
                break;
        }
    }
}

2.2 使用SQLiteOpenHelper操作数据库

 activity_sqlite_helper.xml

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="姓名"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="年龄"/>
        <EditText
            android:id="@+id/et_age"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_save"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="保存"/>
        <Button
            android:id="@+id/btn_delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="删除"/>
        <Button
            android:id="@+id/btn_update"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更新"/>
        <Button
            android:id="@+id/btn_query"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="查询"/>
    </LinearLayout>
</LinearLayout>

SQLiteHelperActivity.java

public class SQLiteHelperActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name;
    private EditText et_age;
    private UserDbHelper mHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite_helper);
        et_name = findViewById(R.id.et_name);
        et_age = findViewById(R.id.et_age);

        findViewById(R.id.btn_save).setOnClickListener(this);
        findViewById(R.id.btn_delete).setOnClickListener(this);
        findViewById(R.id.btn_update).setOnClickListener(this);
        findViewById(R.id.btn_query).setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mHelper = UserDbHelper.getInstance(this);
        mHelper.openWriteLink();
        mHelper.openReadLink();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mHelper.closeLink();
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        String age = et_age.getText().toString();
        User user = null;
        switch (v.getId()) {
            case R.id.btn_save:
                user = new User(1,name,Integer.parseInt(age),1,1,false);
                if (mHelper.insert(user) > 0) {
                    System.out.println("insert success");
                    Toast.makeText(this,"添加成功",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_delete:
                System.out.println(name);
                if (mHelper.deleteByName(name) > 0) {
                    System.out.println("delete success");
                    Toast.makeText(this,"delete成功",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_update:
                user = new User(1,name,Integer.parseInt(age),1,1,false);
                if (mHelper.update(user) > 0) {
                    System.out.println("update success");
                    Toast.makeText(this,"update成功",Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_query:
                List<User> list = mHelper.queryAll();
                for(User u : list) {
                    Log.d("song", u.toString());
                }
                break;
        }
    }
}

数据库操作类

public class UserDbHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "user.db";
    public static final String TABLE_NAME = "user_info";
    private static final int DB_VERSION = 1;
    public static UserDbHelper mHelper = null;
    private SQLiteDatabase mRDB = null;
    private SQLiteDatabase mWDB = null;

    private UserDbHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    public static UserDbHelper getInstance(Context context) {
        if(null == mHelper) {
            mHelper = new UserDbHelper(context);
        }
        return mHelper;
    }

    public SQLiteDatabase openReadLink() {
        if(null == mRDB || !mRDB.isOpen()) {
            mRDB = mHelper.getReadableDatabase();
        }
        return mRDB;
    }

    public SQLiteDatabase openWriteLink() {
        if(null == mWDB || !mWDB.isOpen()) {
            mWDB = mHelper.getWritableDatabase();
        }
        return mWDB;
    }

    public void closeLink() {
        if(null != mRDB && mRDB.isOpen()) {
            mRDB.close();
            mRDB = null;
        }
        if(null != mWDB && mWDB.isOpen()) {
            mWDB.close();
            mWDB = null;
        }
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name VARCHAR NOT NULL,age INTEGER NOT NULL,height LONG NOT NULL,weight FLOAT NOT NULL,married INTEGER NOT NULL);";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public long insert(User user) {
        ContentValues values = new ContentValues();
        values.put("name", user.name);
        values.put("age", user.age);
        values.put("height", user.height);
        values.put("weight", user.weight);
        values.put("married", user.married);
        return mWDB.insert(TABLE_NAME,null,values);
    }

    public long deleteByName(String name) {
        return mWDB.delete(TABLE_NAME,"name=?",new String[]{name});
    }

    public long update(User user) {
        ContentValues values = new ContentValues();
        values.put("name", user.name);
        values.put("age", user.age);
        values.put("height", user.height);
        values.put("weight", user.weight);
        values.put("married", user.married);
        return mWDB.update(TABLE_NAME,values,"name=?",new String[] {user.name});
    }

    public List<User> queryAll() {
        List<User> list = new ArrayList<>();
        Cursor cursor = mRDB.query(TABLE_NAME,null, null,null,null,null,null);
        while (cursor.moveToNext()) {
            User user = new User();
            user.id = cursor.getInt(0);
            user.name = cursor.getString(1);
            user.age = cursor.getInt(2);
            user.height = cursor.getLong(3);
            user.weight = cursor.getFloat(4);
            user.married = (cursor.getInt(5) == 0) ? false :true;
            list.add(user);
        }
        return list;
    }
}

实体类

public class User {
    public int id;
    public String name;
    public int age;
    public long height;
    public float weight;
    public boolean married;
    public User() {

    }

    public User(int id, String name, int age, long height, float weight, boolean married) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
        this.married = married;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                ", weight=" + weight +
                ", married=" + married +
                '}';
    }
}

我么可以在App Inspection查看数据库

 

2.3 数据库版本升级

指的是数据库的版本会有变化,如果代码中版本和数据库版本不一致,则会执行SQLiteOpenHelper的onUpdate方法。

2.4 Room框架

Room是谷歌推出的数据库处理框架,基于SQLite,通过注解技术极大地简化了数据库操作。

activity_room_write.xml

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="书籍名称"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="书籍作者"/>
        <EditText
            android:id="@+id/et_author"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_save"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="保存"/>
        <Button
            android:id="@+id/btn_delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="删除"/>
        <Button
            android:id="@+id/btn_update"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更新"/>
        <Button
            android:id="@+id/btn_query"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="查询"/>
    </LinearLayout>
</LinearLayout>

RoomWriteActivity.java

public class RoomWriteActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_author;
    private EditText et_name;
    private BookDao bookDao;

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

        et_name = findViewById(R.id.et_name);
        et_author = findViewById(R.id.et_author);

        findViewById(R.id.btn_save).setOnClickListener(this);
        findViewById(R.id.btn_delete).setOnClickListener(this);
        findViewById(R.id.btn_update).setOnClickListener(this);
        findViewById(R.id.btn_query).setOnClickListener(this);

        bookDao = MyApplication.getInstance().getBookDatabase().bookDao();
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        String author = et_author.getText().toString();

        switch (v.getId()) {
            case R.id.btn_save:
                BookInfo b1 = new BookInfo();
                b1.setName(name);
                b1.setAuthor(author);
                bookDao.insert(b1);
                break;
            case R.id.btn_query:
                List<BookInfo> list = bookDao.queryAll();
                for (BookInfo bookInfo : list) {
                    Log.d("song", bookInfo.toString());
                }
                break;
        }
    }
}

BookInfo.java

@Entity
public class BookInfo {
    @PrimaryKey(autoGenerate = true)
    private int id;

    private String name;
    private String author;

    //getter and setters

    @Override
    public String toString() {
        return "BookInfo{" + "id=" + id +", name='" + name + ", author='" + author + "}";
    }
}

BookDao.java

@Dao
public interface BookDao {
    @Insert
    void insert(BookInfo... book);
    @Delete
    void delete(BookInfo... book);
    @Update
    int update(BookInfo... book);

    @Query("SELECT * FROM bookinfo")
    List<BookInfo> queryAll();
}

BookDatabase.java

@Database(entities = {BookInfo.class}, version = 1, exportSchema = true)
public abstract class BookDatabase extends RoomDatabase {
    public abstract BookDao bookDao();
}

build.gradle

android {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":"$projectDir/schemas".toString()]//指定数据库schema导出的位置
            }
        }
    }
}

dependencies {
    implementation 'androidx.room:room-runtime:2.4.2'
    annotationProcessor 'androidx.room:room-compiler:2.4.2'
}

 

 

3 存储空间

app的存储空间有4种:内部私有、内部公共、外部私有、外部公共;内部私有和外部私有存储空间在应用卸载时删除。

activity_file_write.xml

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="姓名"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="年龄"/>
        <EditText
            android:id="@+id/et_age"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存"/>
    <Button
        android:id="@+id/btn_read"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读取"/>
    <TextView
        android:id="@+id/tv_txt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

FileWriteActivity.java

public class FileWriteActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText et_name;
    private EditText et_age;
    private TextView tv_txt;
    private String path;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_write);
        et_name = findViewById(R.id.et_name);
        et_age = findViewById(R.id.et_age);
        tv_txt = findViewById(R.id.tv_txt);

        findViewById(R.id.btn_save).setOnClickListener(this);
        findViewById(R.id.btn_read).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_save:
                String name = et_name.getText().toString();
                String age = et_age.getText().toString();
                String directory = null;
                //外部存储的私有空间
                directory = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
                //外部存储的公共空间
                directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();
                //内部存储的私有空间
                directory = getFilesDir().toString();
                path = directory + File.separatorChar + System.currentTimeMillis() + ".txt";
                FileUtil.saveText(path,"姓名:"+name+",年龄:"+age);
                System.out.println("保存成功");
                break;
            case R.id.btn_read:
                tv_txt.setText(FileUtil.readText(path));
                break;
        }
    }
}

FileUtil.java

public class FileUtil {
    //把文本写到指定路径下的文件中
    public static void saveText(String path, String txt) {
        BufferedWriter os = null;
        try {
            os = new BufferedWriter(new FileWriter(path));
            os.write(txt);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //从指定路径下的文件中读取文件中的内容
    public static String readText(String path) {
        BufferedReader is = null;
        String str = "";
        try {
            is = new BufferedReader(new FileReader(path));
            String line = null;
            while((line = is.readLine()) != null) {
                str = str+line;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return str;
    }
}

AndroidManifest.xml

该文件中需要增加如下内容

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
        android:requestLegacyExternalStorage="true"
</application>

 4 存储卡读写图片文件

activity_image_write.xml

<?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">
    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存"/>
    <Button
        android:id="@+id/btn_read"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读取"/>
    <ImageView
        android:id="@+id/iv_content"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:scaleType="center"/>
</LinearLayout>

ImageWriteActivity.java

public class ImageWriteActivity extends AppCompatActivity implements View.OnClickListener {
    private ImageView iv_content;
    private String path;

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

        iv_content = findViewById(R.id.iv_content);

        findViewById(R.id.btn_save).setOnClickListener(this);
        findViewById(R.id.btn_read).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_save:
                //获取当前App私有下载目录
                path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString()+ File.pathSeparatorChar+System.currentTimeMillis()+".jpeg";
                //从指定的资源文件中获取位图对象
                Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.flower);
                //把位图对象保存为图片文件
                FileUtil.saveImage(path, b1);
                break;
            case R.id.btn_read:
                //方式1
//                Bitmap b2 = FileUtil.readImage(path);
//                iv_content.setImageBitmap(b2);
                //方式2
//                Bitmap b2 = BitmapFactory.decodeFile(path);
//                iv_content.setImageBitmap(b2);
                //方式3
                iv_content.setImageURI(Uri.parse(path));
                break;
        }
    }
}

FileUtil.java

    public static Bitmap readImage(String path) {
        Bitmap bitmap = null;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(path);
            bitmap = BitmapFactory.decodeStream(fis);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }

 7 Application

Application是Android的一大组件,在App运行过程中有且仅有一个Application对象贯穿整个生命周期

MyApplication.java

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("song", "onCreate");
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        Log.d("song", "onTerminate");
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d("song", "onConfigurationChanged");
    }
}

AndroidManifest.xml

如果我们不创建MyApplication,应用程序有一个默认的Application。如果我们想使用自己定义的MyApplication,则需要在这个xml中指定。如下

<application
        android:name=".MyApplication"

 使用Application存储信息

activity_app_write.xml

<?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">
    <EditText
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存到Application"/>
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

AppWriteActivity.java

public class AppWriteActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name;
    private MyApplication app;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_write);
        et_name = findViewById(R.id.et_name);
        app = MyApplication.getInstance();

        findViewById(R.id.btn_save).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        app.infoMap.put("name",name);
        TextView tv_content = findViewById(R.id.tv_content);
        tv_content.setText(app.infoMap.get("name"));
    }
}

MyApplication.java

public class MyApplication extends Application {
    private static MyApplication myApp;
    public Map<String,String> infoMap = new HashMap<>();
    public static MyApplication getInstance() {
        return myApp;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        myApp = this;
        Log.d("song", "onCreate");
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        Log.d("song", "onTerminate");
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d("song", "onConfigurationChanged");
    }
}

 8 ContentProvider

ContentProvider为App存取内部数据提供统一的外部接口,让不同的应用之间得以共享数据。

8.1 ContentProvider的基本使用

 下面实例我们创建两个module:chapter07-server和chapter07-client,server为内容提供者,client通过这个Provider进行数据的插入、查询和删除。

chapter07-server

先创建一个provider

 

 UserInfoProvider.java

public class UserInfoProvider extends ContentProvider {
    private UserDbHelper dbHelper;
    @Override
    public boolean onCreate() {
        Log.d("song","UserInfoProvider onCreate");
        dbHelper = UserDbHelper.getInstance(getContext());
        return true;
    }
    //content://com.szj.chapter07_server.provider.UserInfoProvider/user
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.d("song","UserInfoProvider insert");
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        db.insert("user_info", null, values);
        return uri;
    }
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Log.d("song","UserInfoProvider query");
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        return db.query("user_info", projection,selection,selectionArgs,null,null,null);
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = db.delete("user_info",selection,selectionArgs);
        db.close();
        return count;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

UserDbHelper.java

public class UserDbHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "user.db";
    public static final String TABLE_NAME = "user_info";
    private static final int DB_VERSION = 1;
    public static UserDbHelper mHelper = null;

    private UserDbHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    public static UserDbHelper getInstance(Context context) {
        if(null == mHelper) {
            mHelper = new UserDbHelper(context);
        }
        return mHelper;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,name VARCHAR NOT NULL,age INTEGER NOT NULL,height LONG NOT NULL,weight FLOAT NOT NULL,married INTEGER NOT NULL);";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

AndroidMainfest.xml

修改如下标红加粗的部分,为Provider的全限定名。这个表示了该Provider的唯一标识。

        <provider
            android:name=".provider.UserInfoProvider"
            android:authorities="com.szj.chapter07_server.provider.UserInfoProvider"
            android:enabled="true"
            android:exported="true"></provider>

chapter7-client

activity-content-write.xml

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="姓名"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="年龄"/>
        <EditText
            android:id="@+id/et_age"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_save"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="保存"/>
        <Button
            android:id="@+id/btn_delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="删除"/>
        <Button
            android:id="@+id/btn_update"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更新"/>
        <Button
            android:id="@+id/btn_query"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="查询"/>
    </LinearLayout>
</LinearLayout>

ContentWriteActivity.java

public class ContentWriteActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText et_name;
    private EditText et_age;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_write);
        et_name = findViewById(R.id.et_name);
        et_age = findViewById(R.id.et_age);

        findViewById(R.id.btn_save).setOnClickListener(this);
        findViewById(R.id.btn_delete).setOnClickListener(this);
        findViewById(R.id.btn_update).setOnClickListener(this);
        findViewById(R.id.btn_query).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        String age = et_age.getText().toString();
        Uri uri = Uri.parse("content://com.szj.chapter07_server.provider.UserInfoProvider/user");
        switch (v.getId()) {
            case R.id.btn_save:
                ContentValues values = new ContentValues();
                values.put("name", name);
                values.put("age", Integer.parseInt(age));
                values.put("height", 1);
                values.put("weight", 1);
                values.put("married", 1);
                getContentResolver().insert(uri, values);
                break;
            case R.id.btn_delete:
                int count = getContentResolver().delete(uri,"name=?",new String[]{"songzhenjing"});
                if(count > 0) {
                    Log.d("song","delete success");
                }
                break;
            case R.id.btn_query:
                Cursor cursor = getContentResolver().query(uri, null, null, null, null, null);
                if(cursor != null) {
                    while (cursor.moveToNext()) {
                        String name1 = cursor.getString(1);
                        Log.d("song", name1);
                    }
                    cursor.close();
                }
                break;
        }
    }
}

AndroidManifest.xml

    <!-- 出于安全考虑,android11要求应用事先说明需要访问的其他软件包 -->
    <queries>
        <package android:name="com.szj.chapter07_server"/>
    </queries>

先启动server,再启动client。然后我们先新增记录,再查询记录再删除记录,可以观察到,可以成功通过Provider实现两个App之间的通信。

8.2 动态申请权限

动态申请权限的步骤:

检查App是否开启了指定权限---调用ContextCompat的checkSelfPermission方法

请求系统弹窗,以便用户选择是否开启权限---调用ActivityCompat的requestPermissions方法,即可命令系统自动弹出权限申请窗口

判断用户的权限选择结果---重写活动页面的权限请求回调方法onRequestPermissionsResult,在该方法内部处理用户的权限选择结果。

8.2.1 Lazy模式

activity_permission_lazy.xml

<?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">
    <Button
        android:id="@+id/btn_contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读写通讯录"/>
    <Button
        android:id="@+id/btn_sms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="收发短信"/>
</LinearLayout>

PermissionLazyActivity.java

public class PermissionLazyActivity extends AppCompatActivity implements View.OnClickListener {
    public static final String[] PERMISSIONS_CONTACTS = new String[] {
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS
    };
    public static final String[] PERMISSIONS_SMS = new String[] {
            Manifest.permission.SEND_SMS,
            Manifest.permission.READ_SMS
    };

    public static final int REQUEST_CODE_CONTACTS = 1;
    public static final int REQUEST_CODE_SMS = 2;

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

        findViewById(R.id.btn_contact).setOnClickListener(this);
        findViewById(R.id.btn_sms).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_contact:
                PermissionUtil.checkPermission(this,PERMISSIONS_CONTACTS,REQUEST_CODE_CONTACTS);
                break;
            case R.id.btn_sms:
                PermissionUtil.checkPermission(this,PERMISSIONS_SMS,REQUEST_CODE_SMS);
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE_CONTACTS:
                if(PermissionUtil.checkGrant(grantResults)) {
                    Log.d("song","通讯录权限获取成功");
                } else {
                    Log.d("song","通讯录权限获取失败");
                    jumpToSettings();
                }
                break;
            case REQUEST_CODE_SMS:
                if(PermissionUtil.checkGrant(grantResults)) {
                    Log.d("song","收发短信权限获取成功");
                } else {
                    Log.d("song","收发短信权限获取失败");
                    jumpToSettings();
                }
                break;
        }
    }

    //跳转到应用设置页面
    private void jumpToSettings() {
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package",getPackageName(),null));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

AndroidManifest.xml

    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>

8.2.2 hungry模式

该模式授权是在应用第一次安装的时候进行授权。且在代码里设置了当App安装时未授权,当点击按钮时将会再次询问授权。

activity_permission_hungry.xml

<?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">
    <Button
        android:id="@+id/btn_contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读写通讯录"/>
    <Button
        android:id="@+id/btn_sms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="收发短信"/>
</LinearLayout>

PermissionHungryActivity.java

public class PermissionHungryActivity extends AppCompatActivity implements View.OnClickListener {
    public static final String[] PERMISSIONS = new String[] {
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS,
            Manifest.permission.SEND_SMS,
            Manifest.permission.READ_SMS
    };

    public static final int REQUEST_CODE_ALL = 1;
    public static final int REQUEST_CODE_CONTACTS = 2;
    public static final int REQUEST_CODE_SMS = 3;

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

        findViewById(R.id.btn_contact).setOnClickListener(this);
        findViewById(R.id.btn_sms).setOnClickListener(this);

        PermissionUtil.checkPermission(this,PERMISSIONS, REQUEST_CODE_ALL);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_contact:
                PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[0],PERMISSIONS[1]},REQUEST_CODE_CONTACTS);
                break;
            case R.id.btn_sms:
                PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[2],PERMISSIONS[3]},REQUEST_CODE_SMS);
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE_ALL:
                if(PermissionUtil.checkGrant(grantResults)) {
                    Log.d("song","所有权限获取成功");
                } else {
                    Log.d("song","所有权限获取失败");
                    for (int i = 0; i < grantResults.length; i++) {
                        if(grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                            switch (permissions[i]) {
                                case Manifest.permission.READ_CONTACTS:
                                case Manifest.permission.WRITE_CONTACTS:
                                    Log.d("song","通讯录权限获取失败");
                                    jumpToSettings();
                                    break;
                                case Manifest.permission.READ_SMS:
                                case Manifest.permission.SEND_SMS:
                                    Log.d("song","收发短信权限获取失败");
                                    jumpToSettings();
                                    break;
                            }
                        }
                    }
                }
                break;
            case REQUEST_CODE_CONTACTS:
                if(PermissionUtil.checkGrant(grantResults)) {
                    Log.d("song","通讯录权限获取成功");
                } else {
                    Log.d("song","通讯录权限获取失败");
                    jumpToSettings();
                }
                break;
            case REQUEST_CODE_SMS:
                if(PermissionUtil.checkGrant(grantResults)) {
                    Log.d("song","收发短信权限获取成功");
                } else {
                    Log.d("song","收发短信权限获取失败");
                    jumpToSettings();
                }
                break;
        }
    }

    //跳转到应用设置页面
    private void jumpToSettings() {
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package",getPackageName(),null));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

PermissionUtil.java 和上面一样

8.3 添加和查询手机通信录

activity_contact_add.xml

<?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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="联系人姓名:"/>
        <EditText
            android:id="@+id/et_contact_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="联系人号码:"/>
        <EditText
            android:id="@+id/et_contact_phone"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="联系人邮箱:"/>
        <EditText
            android:id="@+id/et_contact_email"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent"/>
    </LinearLayout>
    <Button
        android:id="@+id/btn_add_contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加联系人"/>
    <Button
        android:id="@+id/btn_read_contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查询联系人"/>
</LinearLayout>

ContactAddActivity.java

public class ContactAddActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_contact_name;
    private EditText et_contact_phone;
    private EditText et_contact_email;

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

        et_contact_name = findViewById(R.id.et_contact_name);
        et_contact_phone = findViewById(R.id.et_contact_phone);
        et_contact_email = findViewById(R.id.et_contact_email);

        findViewById(R.id.btn_add_contact).setOnClickListener(this);
        findViewById(R.id.btn_read_contact).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_add_contact:
                //方式1 使用ContentResolver多次写入,每次一个字段(因为要写多个表,可能某一个表写入失败,可能造成联系人信息缺失)
                //addContacts(getContentResolver(),et_contact_name.getText().toString(),et_contact_phone.getText().toString(),et_contact_email.getText().toString());
                //方式2 批处理方式
                //每一次操作都是一个ContentProviderOperation,构建一个操作集合,然后一次性执行。好处是要么全部成功,要么全部失败,保证事务的一致性。
                addFullContacts(getContentResolver(),et_contact_name.getText().toString(),et_contact_phone.getText().toString(),et_contact_email.getText().toString());
                break;
            case R.id.btn_read_contact:
                readPhoneContacts(getContentResolver());
                break;
        }
    }

    @SuppressLint("Range")
    private void readPhoneContacts(ContentResolver resolver) {
        //先查询raw_contacts表,再根据raw_contacts_id去查询data表
        Cursor cursor = resolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null,null);
        while (cursor.moveToNext()) {
            int rawContactsId = cursor.getInt(0);
            Uri uri = Uri.parse("content://com.android.contacts/contacts/"+rawContactsId+"/data");
            Cursor dataCursor = resolver.query(uri,new String[] {ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.RawContacts.Data.DATA1, ContactsContract.RawContacts.Data.DATA2},
                    null, null, null);
            while (dataCursor.moveToNext()) {
                String data1 = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.RawContacts.Data.DATA1));
                String mimeType = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.RawContacts.Data.MIMETYPE));
                //是姓名
                if(mimeType.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) {
                    Log.d("song", "姓名:"+data1);
                } else if(mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
                    Log.d("song", "邮箱:"+data1);
                } else if(mimeType.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
                    Log.d("song", "号码:"+data1);
                }
            }
            dataCursor.close();
        }
        cursor.close();
    }

    private void addFullContacts(ContentResolver resolver, String name, String phone, String email) {
        ContentProviderOperation op_main = ContentProviderOperation
                .newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                .build();
        ContentProviderOperation op_name = ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                //第0个操作的id
                .withValueBackReference(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.RawContacts.Data.DATA2, name)
                .build();
        ContentProviderOperation op_phone = ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                //第0个操作的id
                .withValueBackReference(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.RawContacts.Data.DATA1, phone)
                .withValue(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                .build();
        ContentProviderOperation op_email = ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                //第0个操作的id
                .withValueBackReference(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.RawContacts.Data.DATA1, email)
                .withValue(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
                .build();
        //声明一个内容操作器的列表,并将上面4个操作器添加到该列表中
        ArrayList<ContentProviderOperation> operations = new ArrayList<>();
        operations.add(op_main);
        operations.add(op_name);
        operations.add(op_phone);
        operations.add(op_email);

        try {
            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //往手机通信录添加一个联系人信息
    private void addContacts(ContentResolver resolver, String name, String phone, String email) {
        ContentValues values = new ContentValues();
        //往raw_contacts添加联系人记录,并获取添加后的联系人编号
        Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
        long rawContactId = ContentUris.parseId(uri);

        ContentValues nameValues = new ContentValues();
        //关联联系人编号
        nameValues.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID,rawContactId);
        //姓名的数据类型
        nameValues.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        //联系人姓名
        nameValues.put(ContactsContract.RawContacts.Data.DATA2, name);
        //添加联系人的姓名记录
        resolver.insert(ContactsContract.Data.CONTENT_URI,nameValues);

        ContentValues phoneValues = new ContentValues();
        phoneValues.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID,rawContactId);
        phoneValues.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        phoneValues.put(ContactsContract.RawContacts.Data.DATA1, phone);
        //联系类型。1表示家庭,2表示工作
        phoneValues.put(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        resolver.insert(ContactsContract.Data.CONTENT_URI,phoneValues);

        ContentValues emailValues = new ContentValues();
        emailValues.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID,rawContactId);
        emailValues.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
        emailValues.put(ContactsContract.RawContacts.Data.DATA1, email);
        //联系类型。1表示家庭,2表示工作
        emailValues.put(ContactsContract.RawContacts.Data.DATA2, ContactsContract.CommonDataKinds.Email.TYPE_WORK);
        resolver.insert(ContactsContract.Data.CONTENT_URI,emailValues);
    }
}

下图是执行添加动作之后的结果

 

 下图是执行查询后的打印信息

2022-07-09 12:38:44.991 11595-11595/com.szj.chapter07_client D/song: 姓名:zhangsan
2022-07-09 12:38:44.991 11595-11595/com.szj.chapter07_client D/song: 号码:18866668888
2022-07-09 12:38:44.991 11595-11595/com.szj.chapter07_client D/song: 邮箱:1886688@qq.com

 8.4 监听短信内容

MonitorSmsActivity.java

public class MonitorSmsActivity extends AppCompatActivity {

    private SmsGetObserver mObserver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_monitor_sms);
        //给指定Uri注册内容观察器,一旦数据发生变化,就触发观察器的onChange方法
        Uri uri = Uri.parse("content://sms");
        mObserver = new SmsGetObserver(this);
        getContentResolver().registerContentObserver(uri, true, mObserver);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getContentResolver().unregisterContentObserver(mObserver);
    }

    private static class SmsGetObserver extends ContentObserver {
        private final Context mContext;

        public SmsGetObserver(Context context) {
            super(new Handler(Looper.getMainLooper()));
            this.mContext = context;
        }

        @SuppressLint("Range")
        @Override
        public void onChange(boolean selfChange, @Nullable Uri uri) {
            super.onChange(selfChange, uri);
            //onChage会多次调用,收到一条短信会调用两次onChange
            //mUri===content://sms/raw/20
            //mUri===content://sms/inbox/20
            //android7.0以上系统,点击标记为已读,也会调用一次
            //mUri===content://sms
            //收到一条短信都是uri后面都会有确定的一个数字,对应数据库的_id,比如上面的20
            if(uri == null) {
                return;
            }
            if(uri.toString().contains("content://sms/raw") || uri.toString().equals("content://sms")) {
                return;
            }

            Cursor cursor = mContext.getContentResolver().query(uri,new String[] {"address","body","date"}, null, null,"date DESC");
            while (cursor.moveToNext()) {
                //短信的发送号码
                String sender = cursor.getString(cursor.getColumnIndex("address"));
                //短信内容
                String content = cursor.getString(cursor.getColumnIndex("body"));
                Log.d("song",String.format("sender:%s,content:%s",sender,content));
            }
            cursor.close();
        }
    }
}

然后我们可以模拟收到一条短信,操作如下,打印出的监控短信内容如下

 

posted @ 2022-06-28 00:10  zhenjingcool  阅读(88)  评论(0编辑  收藏  举报