Android基础教程——从入门到精通(上)

这篇具有很好参考价值的文章主要介绍了Android基础教程——从入门到精通(上)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • 本文是对B站教程 动脑学院 Android教程 学习过程中所做的笔记。
  • 文章分为上下两部分,此文是上部分,下部分链接为:Android基础教程——从入门到精通(下)
  • 源视频教程并没有录制全,本文还补充了 Service 和 网络通信 的内容
  • 文章介绍详细,示例代码丰富,相信跟着本教程可以打下很好的Android基础。

一、开发环境搭建

  1. 安装android studio
  2. 安装 sdk(当前使用最新版33)

生皮鞣制工艺流程,android,android,gradle,android studio

  1. 手动下载gradle

    (更新:弄完之后有时候没用,可以再试试挂梯子,换网络之类的)

    如果第一次启动AndroidStudio没有报错则无需设置,这里是因为我启动完之后下载gradle报错:

    could not install gradle distribution from 'https://services.gradle.org/dist
    

    可能是网络问题连接不到,所以手动下载。

    点击上面提示的链接下载压缩包,然后解压到C:\Users\OYMN\.gradle\wrapper\dists\gradle-7.2-bin\2dnblmf4td7x66yl1d74lt32g

    生皮鞣制工艺流程,android,android,gradle,android studio

  2. 安装模拟器

    使用androidstudio提供的模拟器,或者自行下载第三方安卓模拟器(雷电模拟器)

二、简单控件

1. 文本显示

设置文本内容有两种方式:

  • 在 XML 文件中通过属性 android:text 设置文本
  • 在 Java 代码中调用文本视图对象的 setText 方法设置文本

引用字符串资源:

  • 在XML文件中引用(@string/xxx)
  • 在Java代码中引用(R.string.xxx)

其余设置文本字体大小,颜色等都是可以通过关键词+代码提示很容易就能知道怎么写,这里就不赘述。

2. 按钮

Button继承于TextView,因此它们拥有的属性都是共通的。

除此之外,Button最重要的是点击事件。

  • 点击监听器:通过setOnClickListener方法设置。按钮被按住少于500毫秒时,会触发点击事件。

  • 长按监听器:通过setOnLongClickListener方法设置。按钮被按住超过500毫秒时,会触发长按事件。

3. 常用布局

(1)线性布局LinearLayout

特点:要不水平排列,要不竖直排列,通过orintation进行设置(horiztal为水平,vertical为竖直)

权重属性:通过layout_weight来设置,在线性布局的直接下级进行设置,表示该下级布局占据的宽高比例。

  • layout_width填0dp时,layout_weight表示水平方向的宽度比例。
  • layout_height填0dp时,layout_weight表示垂直方向的高度比例。

(3)相对布局RelativeLayout

相对布局中的视图位置由两个因素所影响:

  • 与该视图平级的其他视图
  • 上级视图(也就是它归属的RelativeLayout)

相对位置的一些取值:

生皮鞣制工艺流程,android,android,gradle,android studio

(3)网格布局GridLayout

顾名思义该布局适用于表格类型的布局。

4. 图像显示

图片一般放在res/drawable目录下,设置图像显示一般有两种方法:

  • 在XML文件中,通过属性android:src设置图片资源,属性值格式形如 @drawable/不含扩展名的图片名称。
  • 在Java代码中,调用setImageResource方法设置图片资源,方法参数格式形如 R.drawable.不含扩展名的图片名称。

(1)图像的缩放问题:

ImageView本身默认图片居中显示,若要改变图片的显示方式,可通过scaleType属性设定,该属性的取值说明如下:

生皮鞣制工艺流程,android,android,gradle,android studio

(2)图像按钮ImageButton:

ImageButton是显示图片的图像按钮,但它继承自ImageView,而非继承Button。

ImageButton和Button之间的区别有:

  • Button既可显示文本也可显示图片,ImageButton只能显示图片不能显示文本。
  • ImageButton上的图像可按比例缩放,而Button通过背景设置的图像会拉伸变形。
  • Button只能靠背景显示一张图片,而ImageButton可分别在前景和背景显示图片,从而实现两张图片叠加的效果。

三、Activity

Activity是安卓开发四大组件之一,非常重要。

1. Activity的启动和结束

Activity的启动这里指的是跳转,从一个页面跳转到一个新的页面,就相当于启动了一个新的页面。

示例:

bt.setOnClickListener(new View.OnClickListener(){

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, MainActivity2.class);
        startActivity(intent);
    }
});

结束Activity:调用 finish()

2. Activity的生命周期

onCreate:此时将页面布局加载到内存中,初始化页面。

onStart:将页面展示在屏幕。

onResume:此时页面能够和用户进行交互。

onPause:页面进入暂停状态,无法和用户进行交互。

onStop:页面不在屏幕显示。

onDestory:回收Activity占用的资源,彻底销毁该Activity。

onRestart:onStop状态可以转为onRestart状态。

onNewIntent:重用已存在的活动实例。如果一个Activity已经启动了,并且存在与当前栈,而当前栈的启动模式为SingleTask,SingleInstance,SingleTop(此时在任务栈顶端),那么再次启动该Activity的话,并不会重新进行onCreate,而是会执行onNewIntent方法。

生皮鞣制工艺流程,android,android,gradle,android studio

3. Activity的启动模式

Android允许在创建Activity时设置启动模式,通过启动模式控制Activity的出入栈行为。

(1)静态设置

设置方式:打开AndroidManifest.xml文件,给activity添加属性android:launchMode。如以下表示该activity使用standard标准模式,默认也是标准模式。

<activity android:name=".JumpFirstActivity" android:launchMode="standard" />

launchMode的取值有:

生皮鞣制工艺流程,android,android,gradle,android studio

生皮鞣制工艺流程,android,android,gradle,android studio

生皮鞣制工艺流程,android,android,gradle,android studio

生皮鞣制工艺流程,android,android,gradle,android studio

生皮鞣制工艺流程,android,android,gradle,android studio

(2)动态设置

通过 Intent 动态设置 Activity启动模式:

intent.setFlags();

4. Activity之间传递信息

Intent能够让Android各组件之间进行沟通。

Intent可以完成3部分工作:

  • 表明本次通信从哪里来,往哪里走,要怎么走。
  • 发送方可以携带消息给接收方,接收方可以从收到的Intent解析数据。
  • 发送方如果想要知道接收方的处理结果,接收方也可以通过Intent返回结果。

Intent的一些组成元素:

生皮鞣制工艺流程,android,android,gradle,android studio

(1)显式Intent和隐式Intent

1. 显式Intent

创建方式:

  • 在Intent的构造函数中指定:

    Intent intent = new Intent(this, NextActivity.class);
    
  • 调用setClass指定:

    Intent intent = new Intent();
    intent.setClass(this, NextActivity.class);
    
  • 调用setComponent指定:

    Intent intent = new Intent();
    ComponentName component = new ComponentName(this, NextActivity.class);
    intent.setComponent(component);
    
2. 隐式Intent:

没有明确指定所要跳转的页面,而是通过一些动作字符串来让系统自动匹配。

通常是App不想向外暴露Activity的名称,只给出一些定义好的字符串。这些字符串可以自己定义,也有系统定义的。

常见的系统动作如下:

生皮鞣制工艺流程,android,android,gradle,android studio

下面以调用系统拨号页面举例:

String phone = "12345";
Intent intent = new Intent();
//这里表示设置意图动作为准备拨号
intent.setAction(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + phone));
startActivity(intent);

如果想要跳转到自己定义的activity:

步骤一:在AndroidManifest.xml找到该activity,添加action和category标签,同时设置exported为true,表示允许被其他activity调用。

生皮鞣制工艺流程,android,android,gradle,android studio

步骤二:调用过程和上面一样:

Intent intent = new Intent();
intent.setAction("android.intent.action.activity2");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);

(2)向下一个Activity发送消息:

Intent重载了很多putExtra方法用于传递各种类型的信息,包括整数类型,字符串等。但是显然通过调用putExtra方法会很不好管理,因为数据都是零碎传递。所以Android引入了Bundle,其内部是一个Map,使用起来也和Map一样。

生皮鞣制工艺流程,android,android,gradle,android studio

示例:

Intent intent = new Intent(this, NextActivity.class);
//通过bundle包装数据
Bundle bundle = new Bundle();
bundle.putString("stringKey", "stringValue");
intent.putExtras(bundle);
startActivity(intent);

然后下一个Activity就可以通过intent获取到所想要的数据了:

Bundle bundle = getIntent().getExtras();
String stringValue = bundle.getString("stringKey");

(3)向上一个Activity返回消息:

上一个页面跳转到下一个页面,同时携带数据:

private ActivityResultLauncher<Intent> register;

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

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

    //回调函数,返回到这个页面时所执行的程序
    register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
       			//回调函数
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result != null) {
                        Intent intent = result.getData();
                        if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                            //获取到返回的数据
                            Bundle bundle = intent.getExtras();
                            //...
                        }
                    }
                }
            });
}

@Override
public void onClick(View v) {
    Intent intent = new Intent(this, MainActivity3.class);
    //跳转下一页面
    register.launch(intent);

}

下一个页面接受到数据,处理之后返回结果给上一个页面:

Bundle bundle = getIntent().getExtras();
//...页面进行处理
//返回数据给上一个页面
Bundle bundle = new Bundle();
bundle.putString("stringKey", "stringValue");
intent.putExtras(bundle);
setResult(Activity.RESULT_OK, intent);
finish();

5. Activity获取一些附加信息

(1)获取资源信息:

//获取strings.xml中的字符串资源
String text = getString(R.string.text);
//获取color.xml中的颜色资源
int black = getColor(R.color.black);

(2)获取元数据信息:

try {
    //获取包管理器
    PackageManager pm = getPackageManager();
    //获取当前的Activity信息
    ActivityInfo activityInfo = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
    Bundle bundle = activityInfo.metaData;
    String text2 = bundle.getString("text2");

} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
}

四、数据存储

1. 共享参数SharedPreferences

(1)使用:

sharedPreferences是安卓的一个轻量级存储工具,采用的方式是key-value,以xml文件形式存在,文件路径为/data/data/应用包名/shared_prefs/文件名.xml。


适合场景:

  1. 简单且孤立的数据
  2. 文本数据,二进制数据则不合适
  3. 需要持久化的数据,也就是重启APP后数据仍然存在且有效。

实际开发中,sharedPreferences经常用来存储的数据有:APP的个性化配置信息,用户使用APP的行为信息等。

sharedPreferences对数据的存储和读取类似Map,提供put和set方法。

获取数据可以通过SharedPreferences对象获取:

//第一个参数表示文件名,第二个参数表示私有模式
SharedPreferences shared = getSharedPreferences("fileName", MODE_PRIVATE);
String name = shared.getString("name");

而存储数据则还需要借助Editor类:

SharedPreferences.Editor editor = shared.edit();
editor.putString("name", "oymn");
editor.putInt("age", 20);
editor.commit();

(2)应用实例:记住密码功能

  1. 声明一个共享参数对象,并在onCreate中调用getSharedPreferences方法获取共享参数的实例。
  2. 登录成功时,如果用户勾选了“记住密码”,就使用共享参数保存手机号码与密码。

所以在登录页面的onCreat方法中添加获取共享参数的代码:

// 从share_login.xml获取共享参数对象
mShared = getSharedPreferences("share_login", MODE_PRIVATE);
// 获取共享参数保存的手机号码
String phone = mShared.getString("phone", "");
// 获取共享参数保存的密码
String password = mShared.getString("password", "");
et_phone.setText(phone); // 往手机号码编辑框填写上次保存的手机号
et_password.setText(password); // 往密码编辑框填写上次保存的密码

接着在登录成功方法中添加保存功能:

// 如果勾选了“记住密码”,就把手机号码和密码都保存到共享参数中
if (isRemember) {
    SharedPreferences.Editor editor = mShared.edit(); // 获得编辑器的对象
    editor.putString("phone", et_phone.getText().toString()); // 添加名叫phone的手机号码
    editor.putString("password", et_password.getText().toString()); // 添加名叫password的密码
    editor.commit(); // 提交编辑器中的修改
}

2. 数据库SQLite

SQLite是安卓的一种小巧的嵌入式数据库,基本使用和思路和Mysql无异。

(1)SQLiteDatabase

java代码层面借助SQLiteDatabase来对SQLite进行操作。

//创建数据库text.db
SQLiteDatabase db = openOrCreateDatabase(getFileDir() + "/test.db", Context.MODE_PRIVATE, null);

生皮鞣制工艺流程,android,android,gradle,android studio

(2)SQLiteOpenHelper

由于SQLiteDatabase存在局限性,一不小心就会重复打开数据库,处理数据库的升级也不方便;因此Android提供了数据库帮助器SQLiteOpenHelper,帮助开发者合理使用SQLite。

SQLiteOpenHelper的具体使用步骤如下:

  • 步骤一,新建一个继承自SQLiteOpenHelper的数据库操作类,按提示重写onCreate和onUpgrade两个方法。其中,onCreate方法只在第一次打开数据库时执行,在此可以创建表结构;而onUpgrade方法在数据库版本升高时执行,在此可以根据新旧版本号变更表结构。
  • 步骤二,为保证数据库安全使用,需要封装几个必要方法,包括获取单例对象、打开数据库连接、关闭数据库连接,说明如下:
    • 获取单例对象:确保在App运行过程中数据库只会打开一次,避免重复打开引起错误。
    • 打开数据库连接:SQLite有锁机制,即读锁和写锁的处理;故而数据库连接也分两种,读连接可调用getReadableDatabase方法获得,写连接可调用getWritableDatabase获得。
    • 关闭数据库连接:数据库操作完毕,调用数据库实例的close方法关闭连接。
  • 步骤三, 提供对表记录增加、删除、修改、查询的操作方法。能被SQLite直接使用的数据结构是ContentValues类,它类似于映射Map,也提供了put和get方法存取键值对。
    • 区别之处在于:ContentValues的键只能是字符串,不能是其他类型。ContentValues主要用于增加记录和更新记录,对应数据库的insert和update方法。
    • 记录的查询操作用到了游标类Cursor,调用query和rawQuery方法返回的都是Cursor对象,若要获取全部的查询结果,则需根据游标的指示一条一条遍历结果集合。Cursor的常用方法可分为3类,说明如下:

生皮鞣制工艺流程,android,android,gradle,android studio

(3)代码举例:

public class UserDBHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "user.db";   //数据库名称
    private static final int DB_VERSION = 1;   //数据库的版本号
    private static UserDBHelper helper = null;   //单例
    private SQLiteDatabase sdb = null;  //数据库实例
    public static final String TABLE_NAME = "user_info";   //表名

    public UserDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    public UserDBHelper(Context context, int version) {
        super(context, DB_NAME, null, version);
    }

    //通过单例模式获取 UserDBHelper 的唯一实例
    public static synchronized UserDBHelper getInstance(Context context, int version) {
        if (version > 0 && helper == null) {
            helper = new UserDBHelper(context, version);
        } else if (helper == null) {
            helper = new UserDBHelper(context);
        }

        return helper;
    }

    //打开读连接
    public SQLiteDatabase openReadLink() {
        if (sdb == null || !sdb.isOpen()) {
            sdb = helper.getReadableDatabase();
        }

        return sdb;
    }

    //打开写连接
    public SQLiteDatabase openWriteLink() {
        if (sdb == null || !sdb.isOpen()) {
            sdb = helper.getWritableDatabase();
        }

        return sdb;
    }

    //关闭数据库连接
    public void closeLink() {
        if (sdb != null && sdb.isOpen()) {
            sdb.close();
            sdb = null;
        }
    }

    //创建数据库,执行建表语句
    @Override
    public void onCreate(SQLiteDatabase db) {
        //先删除已存在表
        String drop_sql = "drop table if exists " + TABLE_NAME + ";";
        db.execSQL(drop_sql);

        //创建表
        String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
                + "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
                + "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
                + "height INTEGER NOT NULL," + "weight FLOAT NOT NULL,"
                + "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
                //演示数据库升级时要先把下面这行注释
                + ",phone VARCHAR" + ",password VARCHAR"
                + ");";

        db.execSQL(create_sql);
    }

    //修改表结构
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (newVersion > 1) {
            //Android的ALTER命令不支持一次添加多列,只能分多次添加
            String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN phone VARCHAR;";
            db.execSQL(alter_sql);

            alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
            db.execSQL(alter_sql); // 执行完整的SQL语
        }
    }

    //根据指定条件删除记录
    public int delete(String condition) {
        return sdb.delete(TABLE_NAME, condition, null);
    }

    //删除全部记录
    public int deleteAll() {
        return sdb.delete(TABLE_NAME, "1=1", null);
    }

    //根据条件查询记录
    public List<UserInfo> query(String condition) {
        String sql = String.format("select rowid,_id,name,age,height,weight,married,update_time," +
                "phone,password from %s where %s;", TABLE_NAME, condition);
        //执行查询语句,该语句返回结果集的游标
        Cursor cursor = sdb.rawQuery(sql, null);

        ArrayList<UserInfo> userInfos = new ArrayList<>();

        //循环取出游标指向的结果集
        while (cursor.moveToNext()) {
            UserInfo userInfo = new UserInfo();
            userInfo.name = cursor.getString(2);
            userInfo.age = cursor.getInt(3);
            userInfos.add(userInfo);
        }

        cursor.close();
        return userInfos;
    }

    //往表里添加一条记录
    public long insert(UserInfo userinfo) {
        ArrayList<UserInfo> userInfos = new ArrayList<>();
        userInfos.add(userinfo);
        return insert(userInfos);
    }

    //往表里添加多条记录
    public long insert(List<UserInfo> userInfos) {

        long result = -1;

        for (UserInfo userInfo : userInfos) {
            //如果名字相同,则更新记录
            if (userInfo.name != null && userInfo.name.length() > 0) {
                String condition = String.format("name = '%s'", userInfo.name);
                List<UserInfo> dbUserInfoList = query(condition);
                if (dbUserInfoList != null && dbUserInfoList.size() > 0) {
                    update(userInfo, condition);
                    //返回其id
                    result = dbUserInfoList.get(0).id;
                    continue;
                }
            }
            //其余情况则说明记录不重复,添加新纪录
            ContentValues cv = new ContentValues();
            cv.put("name", userInfo.name);
            cv.put("age", userInfo.age);
            result = sdb.insert(TABLE_NAME, "", cv);
            if(result == -1){
                return result;
            }
        }

        return result;
    }

    //根据指定条件更新表记录
    public int update(UserInfo userInfo, String condition) {

        ContentValues cv = new ContentValues();
        cv.put("name", userInfo.name);
        cv.put("age", userInfo.age);

        return sdb.update(TABLE_NAME, cv, condition, null);
    }

}

(4)优化记住密码:

上面通过SharedPreferences存储密码的方式还是存在一定的局限性,该方式只能记住一个用户的登录信息,当下一个用户登录后,上一个用户的信息将会被覆盖。正确的记住密码功能应该是输入手机号自动补充密码,因此,可以考虑使用数据库来进行存储。

主要的改造如下:

  1. 声明一个数据库的helper对象,在Activity的OnResume方法中获取数据库连接,在OnPause方法中关闭数据库连接。
private UserDBHelper helper;

@Override
protected void onResume() {
    super.onResume();
    //获取数据库帮助器实例 (此处是单例,所以不怕重复获取)
    helper = UserDBHelper.getInstance(this, 1);
    //恢复页面时则获取连接
    helper.openWriteLink();
}

@Override
protected void onPause() {
    super.onPause();
    //暂停页面时就断开连接
    helper.closeLink();
}
  1. 登录成功后,如果用户勾选了记住密码功能,则保存到数据库。也就是在loginSuccess方法中添加如下:
if (isRemember) {
    UserInfo info = new UserInfo(); // 创建一个用户信息对象
    info.phone = et_phone.getText().toString();
    info.password = et_password.getText().toString();
    info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");
    mHelper.insert(info); // 往用户数据库添加登录成功的用户信息
}
  1. 用户进行登录时,根据输入手机号自动查找密码:
// 根据手机号码查询指定记录
public UserInfo queryByPhone(String phone) {
    UserInfo info = null;
    List<UserInfo> infoList = query(String.format("phone='%s'", phone));
    if (infoList.size() > 0) { // 存在该号码的登录信息
    info = infoList.get(0);
    }
    return info;
}

3. 存储卡

(1)私有空间和公有空间

为了更规范地管理手机存储空间,Android从7.0开始将存储卡划分为私有存储和公共存储两大部分,也就是分区存储方式,系统给每个App都分配了默认的私有存储空间。App在私有空间上读写文件无须任何授权,但是若想在公共空间读写文件,则要在AndroidManifest.xml里面添加下述的权限配置。

<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG"/>

但是即使App声明了完整的存储卡操作权限,系统仍然默认禁止该App访问公共空间。打开手机的系统设置界面,进入到具体应用的管理页面,会发现该应用的存储访问权限被禁止了。

既然存储卡分为公共空间和私有空间两部分,它们的空间路径获取也就有所不同。若想获取公共空间的存储路径,调用的是Environment.getExternalStoragePublicDirectory方法;若想获取应用私有空间的存储路径,调用的是getExternalFilesDir方法。

 //获取系统的公共存储路径
String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();

//获取系统的私有存储路径
String privatePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();

boolean isLegacy = true;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
    //Android10的存储空间默认采用分区方式,这里是判断是使用传统方式还是分区方式
    isLegacy = Environment.isExternalStorageLegacy();
}

(2)在存储卡上读写文件

文本文件的读写借助IO流 FileOutputStream(写文件)和 FileInputStream(读文件)

// 把字符串保存到指定路径的文本文件
public static void saveText(String path, String txt) {
    // 根据指定的文件路径构建文件输出流对象
    try (FileOutputStream fos = new FileOutputStream(path)) {
   		fos.write(txt.getBytes()); // 把字符串写入文件输出流
    } catch (Exception e) {
    	e.printStackTrace();
    }
}
// 从指定路径的文本文件中读取内容字符串
public static String openText(String path) {
    String readStr = "";
    // 根据指定的文件路径构建文件输入流对象
    try (FileInputStream fis = new FileInputStream(path)) {
        byte[] b = new byte[fis.available()];
        fis.read(b); // 从文件输入流读取字节数组
        readStr = new String(b); // 把字节数组转换为字符串
    } catch (Exception e) {
    	e.printStackTrace();
    }
    return readStr; // 返回文本文件中的文本字符串
}

(3)在存储卡上读写 图片文件

文本文件可以转化为对字符串的读写,而图像的读写就需要借助专门的位图工具Bitmap处理。不同图像来源获取Bitmap的方式不同,有三种:

  1. 从指定资源文件中获取:decodeResource,例如从资源文件img.png获取位图对象:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
  1. 从指定路径下获取:decodeFile,但是要注意从Android10开始,该方法只能获取私有空间下的图片,公共空间下获取不了。
Bitmap bitmap = BitmapFactory.decodeFile("C:\\Users\\OYMN\\Pictures\\onepunch.jpg");
  1. 从指定的输入流中获取,比如使用IO流打开图片文件,然后作为参数传入decodeStream:
public static Bitmap openImage(String path) {
    Bitmap bitmap = null; // 声明一个位图对象
    // 根据指定的文件路径构建文件输入流对象
    try (FileInputStream fis = new FileInputStream(path)) {
    	bitmap = BitmapFactory.decodeStream(fis); // 从文件输入流中解码位图数据
    } catch (Exception e) {
    	e.printStackTrace();
    }
    return bitmap; // 返回图片文件中的位图数据
}

获取到图片之后就可以通过ImageView的setImageBitmap进行设置了。

有多种读取图片的方式,但是写图片只有一种方式。通过Bitmap的compress方法将位图数据压缩到文件输出流:

public static void saveImage(String path, Bitmap bitmap){
    //根据文件路径构建文件输出流
    try(FileOutputStream fos = new FileOutputStream()){
        //将位图数据压缩到文件输出流
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
    }catch(Exception e){
        e.printStackTrace();
    }
}

以下演示一下完整的文件读写操作:

// 获取当前App的私有下载目录
String path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() +
"/";
// 从指定的资源文件中获取位图对象
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.huawei);
String file_path = path + DateUtil.getNowDateTime("") + ".jpeg";
FileUtil.saveImage(file_path, bitmap); // 把位图对象保存为图片文件
tv_path.setText("图片文件的保存路径为:\n" + file_path);
// 获取当前App的私有下载目录
mPath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/";
// 获得指定目录下面的所有图片文件
mFilelist = FileUtil.getFileList(mPath, new String[]{".jpeg"});
if (mFilelist.size() > 0) {
// 打开并显示选中的图片文件内容
String file_path = mFilelist.get(0).getAbsolutePath();
tv_content.setText("找到最新的图片文件,路径为"+file_path);
// 显示存储卡图片文件的第一种方式:直接调用setImageURI方法
//iv_content.setImageURI(Uri.parse(file_path)); // 设置图像视图的路径对象
// 第二种方式:先调用BitmapFactory.decodeFile获得位图,再调用setImageBitmap方法
//Bitmap bitmap = BitmapFactory.decodeFile(file_path);
//iv_content.setImageBitmap(bitmap); // 设置图像视图的位图对象
// 第三种方式:先调用FileUtil.openImage获得位图,再调用setImageBitmap方法
Bitmap bitmap = FileUtil.openImage(file_path);
iv_content.setImageBitmap(bitmap); // 设置图像视图的位图对象

4. 应用组件Application

Application是Android的一大组件,在App运行期间只有一个Application对象贯穿整个应用的生命周期。因此,Application适合保存全局变量,主要是以下三类数据:

  • 会频繁读取的信息:如用户名,手机号码等

  • 不方便通过intent传递的数据,如位图对象,非字符串的集合对象等。

  • 容易因频繁分配内存而导致内存泄漏的对象,如Handler处理器实例等。

    生皮鞣制工艺流程,android,android,gradle,android studio

通过Application实现对全局内存的读写:

  1. 先继承Application,并获取唯一实例:
public class MyApplication extends Application {

    private static MyApplication myApplication;   //Application唯一实例

    public Map<String, String> map = new HashMap<>();   //当作全局变量,用来存储数据

    public static MyApplication getInstance(){
        return myApplication;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // 在打开应用时对静态的应用实例赋值
        myApplication = this;
    }
}

  1. 在AndroidManifest.xml 通过name属性添加该Application

生皮鞣制工艺流程,android,android,gradle,android studio

  1. 接下来就可以通过该Application在整个App中存取数据了:

如在MainActivity6存储数据:

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

    //存储数据
    MyApplication myApplication = MyApplication.getInstance();
    myApplication.map.put("myKey", "myValue");

    //跳转到MainActivity5
    View bt5 = findViewById(R.id.bt5);
    bt5.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity6.this, MainActivity5.class);
            startActivity(intent);
        }
    });

}

在MainActivity5中获取数据:

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

    TextView tv = findViewById(R.id.tv);
    tv.setText(MyApplication.getInstance().map.get("myKey"));  //成功获取到数据
}

5. 实战:购物车

五、内容共享

1. 在应用之间共享数据

接下来将介绍Android的四大组件之一ContentProvider,通过ContentProvider封装内部数据的外部访问接口,实现不同应用能够互相传输数据。

和ContentProvider搭配使用的还有:ContentResolver(内容解析器),ContentObserver(内容观察器)。

上面提到的SQLite可以操作自身的数据库,而ContentProvider则是作为中间接口,通过SQLiteOpenHelper和SQLiteDatabase间接操控数据库,实现为其他应用提供数据的功能。

生皮鞣制工艺流程,android,android,gradle,android studio

使用举例如下:

  1. 创建一个UserInfoProvider,用来提供用户信息给外界应用

    在弹出的右键菜单中依次选择New→Other→Content Provider

    此时会自动修改两处地方:

    (1)一是在AndroidManifest.xml中添加该Provider的配置信息:

    生皮鞣制工艺流程,android,android,gradle,android studio

    (2)二是创建的这个Provider会继承ContentProvider,并重写了一些方法。

Server端代码:

public class UserInfoProvider extends ContentProvider {

    //这里是上面实现的dbHelper,用来操作本地数据库
    private UserDBHelper userDBHelper;

    //初始化
    @Override
    public boolean onCreate() {
        //初始化 dbHelper
        userDBHelper = UserDBHelper.getInstance(getContext());

        return true;
    }

    //插入
    //uri格式:content://com.example.secondandroidapp.UserInfoProvider/user
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //使用sqlite插入数据
        SQLiteDatabase db = userDBHelper.getWritableDatabase();
        db.insert(UserDBHelper.TABLE_NAME, null, values);

        return uri;
    }

    //查询
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

        SQLiteDatabase db = userDBHelper.getReadableDatabase();
        return db.query(UserDBHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, null);
    }
    
    //删除
	@Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = 0;
        switch (uriMatcher.match(uri)) {
            //这种是uri不带参数:"content://com.example.secondandroidapp.UserInfoProvider/user"
            case USER:
                // 获取SQLite数据库的写连接
                SQLiteDatabase db = userDBHelper.getWritableDatabase();
                // 执行SQLite的删除操作,并返回删除记录的数目
                count = db.delete(UserDBHelper.TABLE_NAME, selection,
                        selectionArgs);
                db.close();
                break;
            //这种是uri带参数:"content://com.example.secondandroidapp.UserInfoProvider/user/2"
            case USERS:
                String id = uri.getLastPathSegment();
                SQLiteDatabase db2 = userDBHelper.getWritableDatabase();
                count = db2.delete(UserDBHelper.TABLE_NAME, "id = ?", new String[]{id});
                db2.close();
                break;
        }
        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");
    }
}
  1. 利用ContentProvider只实现服务端App的数据封装,如果客户端App想访问对方的内部数据,就要通过内容解析器ContentResolver访问。

    生皮鞣制工艺流程,android,android,gradle,android studio

ContentProvider的Uri结构如下:content://authority/data_path/id

Client的代码如下:

public class MainActivity7 extends AppCompatActivity {

    private static Uri ContentUri = Uri.parse("content://com.example.secondandroidapp.UserInfoProvider/user");

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

        Button insertButton = findViewById(R.id.insertButton);
        insertButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ContentValues values = new ContentValues();
                values.put("name", "陈鸿荣");
                values.put("age", "20");
                //获取到ContentResolver之后调用插入方法进行插入
                getContentResolver().insert(ContentUri, values);
            }
        });

        Button deleteButton = findViewById(R.id.deleteButton);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // content://com.example.secondandroidapp.UserInfoProvider/user/2
                Uri uri = ContentUris.withAppendedId(ContentUri, 2);
                int count = getContentResolver().delete(uri, null, null);
            }
        });
    }
}

出于安全考虑,Android11需要事先声明需要访问的其他应用:

在AndroidManifest.xml中添加如下:

<queries>
    <!--服务端应用包名 -->
    <package android:name="com.example.secondandroidapp"/>
    
    <!--或者直接指定authorities-->
    <!-- <provider android:authorities="com.example.secondandroidapp.UserInfoProvider"/>   -->
</queries>

2. 使用内容组件获取通讯信息

(1)运行时动态申请权限

在上面讲公共存储空间与私有存储空间提到,App若想访问存储卡的公共空间,就要在AndroidManifest.xml里面添加下述的权限配置。

<!-- 存储卡读写 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG" />

然而即使App声明了完整的存储卡操作权限,从Android 7.0开始,系统仍然默认禁止该App访问公共空间,必须到设置界面手动开启应用的存储卡权限才行。尽管此举是为用户隐私着想,可是人家咋知道要手工开权限呢?就算用户知道,去设置界面找到权限开关也颇费周折。为此Android支持在Java代码中处理权限,处理过程分为3个步骤:

  1. 检查App是否开启了指定权限:

    调用ContextCompat的checkSelfPermission方法

  2. 请求系统弹窗,以便用户选择是否开启权限:

    调用ActivityCompat的requestPermissions方法,即可命令系统自动弹出权限申请窗口。

  3. 判断用户的权限选择结果,是开启还是拒绝:

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

动态申请权限有两种方式:饿汉式 和 懒汉式。

接下来通过获取通讯权限和短信权限来进行举例说明:

首先是懒汉式:当需要某种权限的时候再去申请

public class PermissionUtil {

    //检查权限,返回true表示完全启用权限,返回false则表示为完全启用所有权限
    public static boolean checkPermission(Activity activity, String[] permissions, int requestCode){

        //Android6.0之后采取动态权限管理
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M){
            int check = PackageManager.PERMISSION_GRANTED;  // 0

            for (String permission : permissions) {
                check = ContextCompat.checkSelfPermission(activity, permission);
                if(check != PackageManager.PERMISSION_GRANTED){
                    break;
                }
            }
            //如果未开启该权限,则请求系统弹窗,好让用户选择是否开启权限
            if(check != PackageManager.PERMISSION_GRANTED){
                //请求权限
                ActivityCompat.requestPermissions(activity, permissions, requestCode);
                return false;
            }

            return true;
        }

        return false;
    }

    //检查权限数组,返回true表示都已经授权
    public static boolean checkGrant(int[] grantResults) {

        if(grantResults != null){
            for (int grant : grantResults) {
                if(grant != PackageManager.PERMISSION_GRANTED){
                    return false;
                }
            }
            return true;
        }

        return false;
    }
}

通过两个按钮模拟分别获取权限:

public class PermissionLazyActivity extends AppCompatActivity {

    //通讯录的读写权限
    private static final String[] PERMISSION_CONTACT = {
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS
    };

    //短信的读写权限
    private static final String[] PERMISSION_SMS = {
            Manifest.permission.SEND_SMS,
            Manifest.permission.RECEIVE_SMS
    };

    private static final int REQUEST_CODE_CONTACTS = 1;
    private 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(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionUtil.checkPermission(PermissionLazyActivity.this, PERMISSION_CONTACT, REQUEST_CODE_CONTACTS);
            }
        });

        //获取短信权限
        findViewById(R.id.btn_sms).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionUtil.checkPermission(PermissionLazyActivity.this, PERMISSION_SMS, REQUEST_CODE_SMS);
            }
        });
    }

    // 用户选择权限结果后会调用该回调方法
    @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("hhh", "通讯录获取成功");
                }else{
                    Log.d("hhh", "通讯录获取失败");
                    //跳转到设置界面
                    jumpToSettings();
                }
                break;
            case REQUEST_CODE_SMS:
                if(PermissionUtil.checkGrant(grantResults)){
                    Log.d("hhh", "短信权限获取成功");
                }else{
                    Log.d("hhh", "短信权限获取失败");
                    //跳转到设置界面
                    jumpToSettings();
                }
                break;
        }
    }

    //跳转到设置界面
    private void jumpToSettings(){
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package", getPackageName(), null));
        intent.addFlags(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.SEND_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>

效果如下:

懒汉式:在页面打开之后就一次性需要用户获取所有权限。

public class PermissionHungryActivity extends AppCompatActivity {

    //所需全部读写权限
    private static final String[] PERMISSIONS = {
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS,
            Manifest.permission.SEND_SMS,
            Manifest.permission.RECEIVE_SMS
    };

   	//
    private static final int REQUEST_CODE_ALL = 0;

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

        //检查是否拥有所有所需权限
        PermissionUtil.checkPermission(this, PERMISSIONS, REQUEST_CODE_ALL);
    }

    // 用户选择权限结果后会调用该回调方法
    @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("hhh", "所有权限获取成功");
                }else{
                    //部分权限获取失败
                    for (int i = 0; i < grantResults.length; i++) {
                        if(grantResults[i] != PackageManager.PERMISSION_GRANTED){
                            //判断是什么权限获取失败
                            switch (permissions[i]){
                                case Manifest.permission.WRITE_CONTACTS:
                                case Manifest.permission.READ_CONTACTS:
                                    Log.d("hhh", "通讯录获取失败");
                                    jumpToSettings();
                                    break;
                                case Manifest.permission.SEND_SMS:
                                case Manifest.permission.RECEIVE_SMS:
                                    Log.d("hhh", "短信权限获取失败");
                                    jumpToSettings();
                                    break;
                            }
                        }
                    }
                }
                break;
        }
    }

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

(2)使用ContentResolver读写联系人

手机中通讯录的主要表结构有:

raw_contacts表:

生皮鞣制工艺流程,android,android,gradle,android studio

data表:记录了用户的通讯录所有数据,包括手机号,显示名称等,但是里面的mimetype_id表示不同的数据类型,这与表mimetypes表中的id相对应,raw_contact_id 与上面的 raw_contacts表中的 id 相对应。

生皮鞣制工艺流程,android,android,gradle,android studio

mimetypes表:

生皮鞣制工艺流程,android,android,gradle,android studio

所以,插入步骤如下:

  • 首先往raw_contacts表中插入一条数据得到id
  • 接着由于一个联系人有姓名,电话号码,邮箱,因此需要分三次插入data表中,将raw_contact_id和上面得到的id进行关联

下面是往通讯录插入和查询联系人的代码:

public class ContactActivity 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);
        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:
                // 创建一个联系人对象
                Contact contact = new Contact();
                contact.name = et_contact_name.getText().toString().trim();
                contact.phone = et_contact_phone.getText().toString().trim();
                contact.email = et_contact_email.getText().toString().trim();

                // 方式一,使用ContentResolver多次写入,每次一个字段
//                 addContacts(getContentResolver(), contact);

                // 方式二,批处理方式
                // 每一次操作都是一个 ContentProviderOperation,构建一个操作集合,然后一次性执行
                // 好处是,要么全部成功,要么全部失败,保证了事务的一致性
                addFullContacts(getContentResolver(), contact);

                Toast.makeText(this, "添加联系人成功!", Toast.LENGTH_SHORT).show();
                break;

            case R.id.btn_read_contact:
                readPhoneContacts(getContentResolver());
                break;
        }
    }

    //往通讯录添加一个联系人信息(姓名,号码,邮箱)
    private void addContacts(ContentResolver contentResolver, Contact contact) {
        //得到rawContentId
        ContentValues values = new ContentValues();
        //插入记录得到id
        Uri uri = contentResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
        long rawContentId = ContentUris.parseId(uri);

        //插入名字
        ContentValues name = new ContentValues();
        //关联上面得到的联系人id
        name.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //关联联系人姓名的类型
        name.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        //关联联系人姓名
        name.put(ContactsContract.Data.DATA2, contact.name);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, name);

        //插入电话号码
        ContentValues phone = new ContentValues();
        //关联上面得到的联系人id
        phone.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //关联联系人电话号码的类型
        phone.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        //关联联系人电话号码
        phone.put(ContactsContract.Data.DATA1, contact.phone);
        //指定该号码是家庭号码还是工作号码 (家庭)
        phone.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, phone);

        //插入邮箱
        ContentValues email = new ContentValues();
        //关联上面得到的联系人id
        email.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //关联联系人邮箱的类型
        email.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
        //关联联系人邮箱
        email.put(ContactsContract.Data.DATA1, contact.email);
        //指定该号码是家庭邮箱还是工作邮箱
        email.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, email);
    }

    //事务操作,四个插入操作一次性提交
    private void addFullContacts(ContentResolver contentResolver, Contact contact) {
        //创建一个插入联系人主记录的内容操作器
        ContentProviderOperation op_main = ContentProviderOperation
                .newInsert(ContactsContract.RawContacts.CONTENT_URI)
                //没有实际意义,不加这个会报错(不加这个导致没有创建ContentValue,导致报错)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                .build();
        //创建一个插入联系人姓名记录的内容操作器
        ContentProviderOperation op_name = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //将第0个操作的id,即raw_contacts中的id作为data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA2, contact.name)
                .build();
        //创建一个插入联系人电话号码记录的内容操作器
        ContentProviderOperation op_phone = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //将第0个操作的id,即raw_contacts中的id作为data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA1, contact.phone)
                .withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                .build();

        //创建一个插入联系人邮箱记录的内容操作器
        ContentProviderOperation op_email = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //将第0个操作的id,即raw_contacts中的id作为data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA1, contact.email)
                .withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
                .build();

        //全部放在集合中一次性提交
        ArrayList<ContentProviderOperation> operations = new ArrayList<>();
        operations.add(op_main);
        operations.add(op_name);
        operations.add(op_phone);
        operations.add(op_email);

        try {
            //批量提交四个操作
            contentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //读取联系人
    @SuppressLint("Range")
    private void readPhoneContacts(ContentResolver contentResolver) {
        //先查询raw_contacts表,再根据raw_contacts_id表 查询data表
        Cursor cursor = contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null, null, null);
        while(cursor.moveToNext()){
            int rawContactId = cursor.getInt(0);
            Uri uri = Uri.parse("content://com.android.contacts/contacts/" + rawContactId + "/data");
            Cursor dataCursor = contentResolver.query(uri, new String[]{ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.Contacts.Data.DATA1, ContactsContract.Contacts.Data.DATA2}, null, null, null);
            Contact contact = new Contact();
            while (dataCursor.moveToNext()) {
                String data1 = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.DATA1));
                String mimeType = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.MIMETYPE));
                switch (mimeType) {
                    //是姓名
                    case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
                        contact.name = data1;
                        break;

                    //邮箱
                    case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:
                        contact.email = data1;
                        break;

                    //手机
                    case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
                        contact.phone = data1;
                        break;
                }
            }

            dataCursor.close();

            // RawContacts 表中出现的 _id,不一定在 Data 表中都会有对应记录
            if (contact.name != null) {
                Log.d("hhh", contact.toString());
            }
        }
        cursor.close();
    }

}

页面如下:

(3)使用ContentObserver监听短信

ContentResolver获取数据采用的是主动查询方式,有查询就有数据,没查询就没数据。ContentResolver能够实时获取新增的数据,最常见的业务场景是短信验证码。为了替用户省事,App通常会监控手机刚收到的短信验证码,并自动填写验证码输入框。这时就用到了内容观察器ContentObserver,事先给目标内容注册一个观察器,目标内容的数据一旦发生变化,就马上触发观察器的监听事件,从而执行开发者预先定义的代码。

生皮鞣制工艺流程,android,android,gradle,android studio

示例代码如下:(记得在Manifest.xml中开启权限和动态开启权限)文章来源地址https://www.toymoban.com/news/detail-781755.html

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");
        
        // notifyForDescendents:
        // false :表示精确匹配,即只匹配该Uri,true :表示可以同时匹配其派生的Uri
        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);
            // onChange会多次调用,收到一条短信会调用两次onChange
            // mUri===content://sms/raw/20
            // mUri===content://sms/inbox/20
            // 安卓7.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");
            if (cursor.moveToNext()) {
                // 短信的发送号码
                String sender = cursor.getString(cursor.getColumnIndex("address"));
                // 短信内容
                String content = cursor.getString(cursor.getColumnIndex("body"));
                Log.d("ning", String.format("sender:%s,content:%s", sender, content));
            }
            cursor.close();
        }
    }
}

3. 在应用之间共享文件

(1)使用相册图片发送彩信

(2)借助FileProvider发送彩信

(3)借助FileProvider安装应用

到了这里,关于Android基础教程——从入门到精通(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 黑客教程,从零基础入门到精通

    学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解决

    2024年02月01日
    浏览(54)
  • 网络安全入门教程(非常详细)从零基础入门到精通

    1.入行网络安全这是一条坚持的道路,三分钟的热情可以放弃往下看了。 2.多练多想,不要离开了教程什么都不会了,最好看完教程自己独立完成技术方面的开发。 3.有时多百度,我们往往都遇不到好心的大神,谁会无聊天天给你做解答。 4.遇到实在搞不懂的,可以先放放,

    2024年01月18日
    浏览(50)
  • 网络安全入门教程(非常详细)从零基础入门到精通!

    网络安全是一个庞大而不断发展的领域,它包含多个专业领域,如网络防御、网络攻击、数据加密等。介绍网络安全的基本概念、技术和工具,逐步深入,帮助您成为一名合格的网络安全从业人员。 1.计算机基础知识 了解了计算机的硬件、软件、操作系统和网络结构等基础知

    2024年04月13日
    浏览(60)
  • AI教程视频《AI illustrator入门到精通》零基础自学教程教学

    欢迎您关注沉睡者IT,点击上面关注我,↑↑↑ 听说关注我的小伙伴们都发了财,赶紧关注吧 AI教程视频讲座简介: AI教程视频《AI illustrator入门到精通》零基础自学教程教学内容介绍: 一套最适合小白的AI教程,AI课程是从零基础开始讲解的,前面章节学习基本功能工具,后

    2024年02月01日
    浏览(71)
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

    想要成为黑客,却苦于没有方向,不知道从何学起,下面这篇 黑客入门 教程可以帮你实现自己的黑客梦想,如果想学,可以继续看下去,文章有点长,希望你可以耐心看到最后 1、 Web安全相关概念(2周)  ·熟悉基本概念(SQL注入、上传、XSS、 、CSRF、一句话木马等)。 通过

    2024年02月03日
    浏览(45)
  • Flask入门教程(非常详细),从零基础入门到精通,看完这一篇就够了

    目录 Flask入门 运行方式 URL与函数的映射(动态路由) PostMan的使用 查询参数的获取 上传文件 其它参数 url_for 函数 响应-重定向 响应-响应内容 响应-自定义响应 Flask模板 模板介绍 模板的使用 模板-传参 模板使用url_for函数 过滤器介绍 Jinja模板自带过滤器 流程控制-选择结构 流程

    2024年02月05日
    浏览(75)
  • Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

    本文罗列了了python零基础入门到精通的详细教程,内容均以知识目录的形式展开。 Typora软件下载 Typora基本使用 Typora补充说明 编程与编程语言 计算机的本质 计算机五大组成部分 计算机三大核心硬件 操作系统 文件的概念 计算机内部数据原理 编程语言发展史 编程语言的分类

    2023年04月19日
    浏览(61)
  • Spark入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

    文章目录 引言 1. Spark 基础 1.1 Spark 为何物 1.2 Spark VS Hadoop 1.3 Spark 优势及特点 1.3.1 优秀的数据模型和丰富计算抽象 1.3.2 完善的生态圈-fullstack 1.3.3 spark的特点 1.4 Spark 运行模式 2. Spark Core 2.1 RDD详解 2.1.1 RDD概念 2.1.2 RDD属性 2.1.3 RDD API 2.1.3.1 RDD 的创建方式 2.1.3.2 RDD 算子 2.1.4 RDD

    2024年02月04日
    浏览(56)
  • Golang入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

    文章目录 一、golang 简介 1. go 语言特点 2. go 语言应用领域 3. 使用 go 语言的公司有哪些 二、安装 golang 1. golang 下载安装 2. 配置环境变量 三、golang 开发工具 1. 安装 VSCode 2. 下载所需插件 四、第一个 golang 应用 1. main 包的含义 2. 示例 Go 是一个开源的编程语言,它能让构造简单

    2024年02月04日
    浏览(41)
  • 网络安全入门教程(非常详细),从零基础入门到精通,看完这一篇就够了

    “没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。 网络安全行业特点 1、就业薪资非常高,涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万! 2、人才缺口大,就业机会多

    2023年04月10日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包