《第一行代码》学习笔记

Android的三种存储方式

这其实是三篇博客,是我2020年写的,重新整理下,把三篇整合成一篇。

File存储

引文: 数据存储是实现数据持久化的方法之一

原理: 把数据存储在手机内存里,以便软件对重要数据进行保留。

假使某个软件关闭后,就什么记录也没有了,那也必然会引起用户的极度不满把!

最常见的数据存储,像我们的QQ的记住密码和一些本地的聊天记录,就是储存在手机内存里的

接下来我们就开始学习超级简单的File存储把

数据存储方法:

一、File存储(本章将要讲解的内容)

二、SharedPreferences存储

三、SQLite存储

不要觉得有三种文件存储便觉得困难,那么我告诉你,File存储和SharedPreferences存储极度类似,所以我们只要熟练知晓File存储的原理 就可以很快的学会另一个,这便是触类旁通。另外SQLite是数据库存储,学完他你又会对SQL数据库领域有所涉足,不仅仅止步于JAVA的代码,怎么样?听完后你的兴趣是不是浓厚起来了?

那么接下来我们先认识一下File存储中的 数据的实现方法 写,当然是把数据写出去,那自然应该是“Output”

FileOutputStream fos=openFileOutput("data",Context.MODE_PRIVATE);

openFileOutput参数说明: 第一个参数name:文件名(不含路径) 第二个参数mode:存储模式(这里有两种模式供选择,MODE_PRIVATE(同名文件覆盖<如果有同名文件,直接覆盖原文件中的内容>) MODE_APPEND(同名文件追加<如果有同名文件,向原文件里追加内容>))

openFileOutput要用一个文件字节输出流FileOutputStream来接收

接下来我们要新建一个字符缓冲输出流,通过它来将一串文本写出到文件里

BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(out));
writer.write("6666666666");

至此,我们新建了一个文件名为"data"的文件来存储数据,并对其写入了"6666666666"这个数据

接下来我们来看一下完整的源代码

public void write(String inputText){
		FileOutputStream fos;
        BufferedWriter writer=null;//这里要初始化一个null
        try{
            fos=openFileOutput("data", Context.MODE_PRIVATE);
            writer=new BufferedWriter(new OutputStreamWriter(fos));
            writer.write(inputText);//这里写入参数
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(writer != null){
                    writer.close();//关闭字符缓冲输出流
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

上面短短几行代码,我们便将数据写入到了手机内存中,此时你们一定会好奇,数据存在哪里了呢? 路径:文件默认存储到data/data/< packname >/files/目录下

在这里插入图片描述 如果你是安卓模拟器,可以打开Android Studio里面的Device File Explorer 找到相关目录

在这里插入图片描述 如果你是用自己的手机进行调试,那么直接用手机自带的文件管理找到相关目录即可

最后打开data文件 就能看到我们写入的字符串数据了

写完了数据,接下来就是读取了,代码和上面很相似

写入的时候用Output 那我们读取 自然应该是Input

FileInputStream fis=OpenFileInput("data");//这里只有一个参数,便是读取的文件名
BufferedReader=new BufferedReader(new InputStreamReader(fis));//字符缓冲输入流

Output变成了Input,Writer变成了Reader,完全相反,很容易记忆

public String read(){
        FileInputStream fis;
        BufferedReader reader=null;//这里要初始化null
        StringBuilder builder=new StringBuilder();
        try{
            fis=openFileInput("data");
            reader=new BufferedReader(new InputStreamReader(fis));
            String lines="";
            while((lines=reader.readLine())!=null){
                builder.append(lines);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                reader.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return builder.toString();
    }

值得注意的是使用readline进行读取BufferedReader的内容

接下来我们实战一下 写一个记住账号的案例 新建一个AndroidStudio项目

布局如下:在这里插入图片描述在这里插入图片描述

然后我们进行JAVA后端代码编写

public class MainActivity extends AppCompatActivity {
EditText account;
Button write,read;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        account=findViewById(R.id.account);
        write=findViewById(R.id.write);
        read=findViewById(R.id.read);
        write.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                write(account.getText().toString());
            }
        });
        read.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                account.setText(read());
            }
        });
    }
    public void write(String inputText){
        FileOutputStream fos;
        BufferedWriter writer=null;
        try{
            fos=openFileOutput("data", Context.MODE_PRIVATE);
            writer=new BufferedWriter(new OutputStreamWriter(fos));
            writer.write(inputText);
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            try{
                if(writer != null){
                    writer.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    public String read(){
        FileInputStream fis;
        BufferedReader reader=null;//这里要初始化null
        StringBuilder builder=new StringBuilder();
        try{
            fis=openFileInput("data");
            reader=new BufferedReader(new InputStreamReader(fis));
            String lines="";
            while((lines=reader.readLine())!=null){
                builder.append(lines);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                reader.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return builder.toString();
    }
}

到此为止,我们的读写功能就全部实现了,来运行一下看看效果 在这里插入图片描述我们输入123456,并点击写入数据,随后删掉EditText的内容后,点击读取数据,发现123456又回来了!

以上就是File存储的基本用法了,希望各位学习愉快,如有不足希望指正

SharedPreferences存储

我喜欢循序渐进的学习,这样不至于自己的学习路线混淆紊乱,潜移默化中影响学习兴致。在学习SharedPreferences之前,我建议先学会File存储。

引文:对于SharedPreferences的阐述,这里引用《第一行代码》第二版 中的一句话,防止篇幅过大,我简略摘抄几句:

SharedPreferences是使用键值对的方式来存储数据。

也就是说,保存一条数据的时候需要给数据提供一个对应的,这样读取数据的时候就可以通过这个把数据读取出来

听起来键很抽象,其实就是给数据取一个字符串形式的名字而已,因此简单至极,话不多说,直接上文章!

这里提供了三种方法获取到SharedPreferences对象

1、Context类中的getSharedPreferences()方法 参数说明: 第一个参数name:文件名(不含路径) 第二个参数mode:存储模式(这里只有一种MODE_PRIVATE模式可以用,其他模式均已被谷歌废除)

2、Activity类中的getPreferences()方法 只接收一个操作模式参数,因为这个方法会自动将当前活动的类名作为SharedPreferences的文件名

3、PreferenceManager类中的getDefaultSharedPreferences()方法 它接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件

以上任意选取一种方法获取到SharedPreferences对象,然后我们就可以对SharedPreferences文件中存储数据,为所欲为了。

存储数据分为三步 (1)调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象 (2)向SharedPreferences.Editor中添加数据,添加字符串就是putString(),添加整数就是putInt(),以此类推 (3)调用**apply()**方法提交数据。

了解了理论,我们直接上代码把,学不会请把我打死。即使你不尊重本帅哥,没去认真阅读上面的理论,我保证你看完下面的代码一样可以学会!

这里我用的是第一种方法获取SharedPreferences对象。

//获取对象
SharedPreferences.Editor editor=getSharedPreferences("data", MODE_PRIVATE).edit();
//添加数据
editor.putString("name","tom");//键是name,值为tom
editor.putInt("age",28);//键是age,值为28
editor.putBoolean("married",false);//键是married,值为false
//提交数据
editor.apply();

同File存储一样,我们去/data/data/< packname >/files/目录下就可以找到写入的数据,这里就不再展示了

学会了存储,我们就要开始读取我们存储的内容了。他的简单程度让写文章的我已经尴尬了,为了避免进一步尴尬,我就简而言之

我们写数据用的是SharedPreferences.Editor对象 我们数据其实更简单,用的就是SharedPreferences对象。因为Editor英文中就有写的意思,去掉Editor自然就变成了读的意思

//获取对象
SharedPreferences pref=getSharedPreferences("data",MODE_PRIVATE);
//根据**键**读取数据
String name=pref.getString("name","");//第二个参数是读取数据类型的默认值
int age=pref.getInt("age",0);//第二个参数是读取数据类型的默认值
Boolean married=pref.getBoolean("married",false);//第二个参数是读取数据类型的默认值

4行代码我们就把读取到的内容分别放在了3个变量里,啊,真的好简单,我没骗你们吧!

分别了解了的写法,我们来做个小案例把!为了不增加陌生感,导致学习难度的增长。这里我使用和File存储中类似的案例

老样子,建一个XML布局 在这里插入图片描述在这里插入图片描述

继续编写JAVA代码,其实学完了读和写你们已经可以结束本章内容了,这里的案例是为了加深巩固印象

public class MainActivity extends AppCompatActivity {
    EditText data;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        data=findViewById(R.id.data);
        Button save=findViewById(R.id.save);
        Button read=findViewById(R.id.read);
        save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences.Editor editor=getSharedPreferences("data", MODE_PRIVATE).edit();
                editor.putString("name","tom");
                editor.putInt("age",28);
                editor.putBoolean("married",false);
                editor.apply();
            }
        });
        read.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences pref=getSharedPreferences("data",MODE_PRIVATE);
                String name=pref.getString("name","");
                int age=pref.getInt("age",0);
                Boolean married=pref.getBoolean("married",false);
                String str="name:"+name+" age:"+age+" married:"+married;
                Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();//消息提示框
                data.setText(str);//给EditText赋值
            }
        });
    }
}

这里我们点一下存储数据,然后再点一下读取数据,结果如下: 在这里插入图片描述 啊,终于结束了!这篇博客又写了接近一小时,还记得昨天第一次写博客的时候,莫名其妙会紧张无比,今天写的时候明显洒脱多了,语言也不那么拘束了。

博客真的能特别加深自己的学习印象,可以说,写过一次,基本是忘不掉了,这可能就是大名鼎鼎的费曼学习法把!把自己输入的知识,通过输出的形式教会、教懂大部分人,这才是真正的理解透,否则只会成为提笔忘字,或是手放在键盘上,在代码前不知所措。

不管技术高低,我觉得都应该尝试着去写博客,抱着写给自己看的心态,既能提升文笔,又能巩固技术,何乐而不为。

写过才知道,博客是真正的能跻身于自己所在领域中,提高自己水平和学习动力的催化剂,因为博主中大佬真的数不胜数,相信每一个程序员都梦寐以求成为他们的样子把!我也在追逐他们的路上,加油!

SQLite存储

SQLite数据库存储

无论来自哪个领域,应该都听说过大名鼎鼎的MySQL数据库。

SQLite就是轻量级关系型数据库,可以理解为MySQL数据库的轻便版

阐述:

!!! SQLite是轻量级的数据库,体积小巧,使用简便,主要用于数据量小的移动端设备

!!! Android为了方便我们管理数据库,提供了一个SQLiteOpenHelper帮助类, !!! 借助这个类就可以非常简单的对数据库进行操作, !!!我们在编写代码时只需要继承这个,就可以调用相关的数据库操作命令。

在SQL数据库操作中,应分为以下步骤,这里我们以书籍管理为例:

1、我们应该先创建一个数据库(格式为.db)例如 BookStore.db

2、在数据库中建立一张表,表中有 id(主键)、作者、价格、页数、书名 等列

3、在每一列中添加相应的数据,除了添加数据,还有删除升级(修改)、查询数据,统称增删改查

这里先假使我们执行完了第一步,创建完了名为BookStore.db的数据库(文章后面会讲到)

我们直接进行第二步,SQL中创建表的命令为

create the Book(id integer primary key autoincrement, name text, author text, price real, pages integer)

这样我们就创建了表名为Book的表,以下是Book表所含有的列

主键id<自增Integer整数型> 书名name<text文本型> 作者author<text文本型> 价格price<real浮点型> 页数pages<Integer整数型>

接下来我们就往Book表中增删改查就达到了操作数据库的目的了

理论就讲到这里,来代码实操一下,再不见一眼我们的Android Studio,让他治愈一下我们人类浮躁的内心,就忍不住要关闭博客,开始打网络游戏了

先建表,我们新建一个类MyDatabaseHelper ,让他去继承SQLiteOpenHelper,并且重写父类中的构造函数

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version){
        super(context,name,factory,version);
        //name就是数据库名,version就是数据库版本
    }
}

写到这里,代码是报错的,因为我们还必须要重写父类中的两个方法。

我们用鼠标单击一下SQLiteOpenHelper,再按ALT+ENTER,就会弹出以下界面 在这里插入图片描述 选择第一个,然后回车。 在这里插入图片描述 这里我们直接默认让他添加方法,点击OK,这样父类中必须要重写的两个方法onCreate和onUpgrade,AndroidStudio就帮我们重写好了! 在这里插入图片描述

我们也可以自己手动去重写这两个方法,工具只是在熟练掌握的前提下,提高工作效率的方式,不要过于依赖!不然只会成为工具的傀儡,离开了工具啥也做不了。

我们要在onCreate里面创建我们的表,用之前讲到的创建表的命令

public class MyDatabaseHelper extends SQLiteOpenHelper {
	//创建表的命令
    private static final String CREATE_BOOK="create table Book(id integer primary key autoincrement,name text,author text,price real,pages integer)";
    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version){
        super(context,name,factory,version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
    	//执行数据库命令,创建表
        db.execSQL(CREATE_BOOK);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    	//当version更新时,删除原来的表的数据,并重新创建一个新表
    	db.execSQL("drop table if exists Book");
    	onCreate(db)
    }
}

这样写完的话,我们只要实例化一个MyDatabaseHelper类,就会自动创建出一个Book表。

MyDatabaseHelper dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,1);
//第二个参数为数据库名,第四个参数为数据库版本

这句代码就相当于创建了一个名为BookStore.db的数据库,数据库版本为1,并且在数据库中自动创建了Book表

这样数据库和表就一起创建完毕了,仅仅一行代码!

接下来我们对增删改查进行分别的编写

这四种操作的第一步都是一样的,通过SQLiteOpenHelper中的getWritableDatabase方法获取到SQLiteDatabase对象,然后就可以对SQLiteDatabase进行数据的修改操作了

MyDatabaseHelper dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,1);
SQLiteDatabase db=dbHelper.getWritableDatabase();

Ok,我们先进行增加数据的编写

public void add(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        //用ContentValues来添加数据
        ContentValues values=new ContentValues();
        values.put("name","My loved ugly girl");
        values.put("author","War");
        values.put("pages",100);
        values.put("price",135);
        db.insert("Book",null,values);
        //第一个参数为表名
        //第二个参数为在空处插入数据,这里写null即可
        //第三个参数为要插入的数据
    }

然后是删除数据

public void delete(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        db.delete("Book","pages>?",new String[]{"50"});
        //第一个参数为表名
        //第二个参数为要判断的删除条件
        //第三个参数为条件对应的值
        //也就是说删掉Book表中pages>50的数据
    }

然后是升级(修改)数据

public void update(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("price",999);
        db.update("Book",values,"name=?",new String[]{"War"});
        //这句代码的意思是将Book表中name=My loved ugly girl的数据给改为values
    }

最后,也是最难的查询数据,只要试着写两遍,就记住了,其实也没啥难度

public void query(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        Cursor cursor=db.query("Book",null,null,null,null,null,null);
        if(cursor.moveToFirst()){
            do{
                //遍历cursor对象
                String name=cursor.getString(cursor.getColumnIndex("name"));//根据列名取出数据
                String author=cursor.getString(cursor.getColumnIndex("author"));
                int pages=cursor.getInt(cursor.getColumnIndex("pages"));
                int prices=cursor.getInt(cursor.getColumnIndex("price"));
                Toast.makeText(MainActivity.this,"name:"+name+" author:"+author+" pages:"+pages+" prices:"+prices,Toast.LENGTH_SHORT).show();
            }while (cursor.moveToNext());
        }
    }

分别掌握了所有的操作方法,老样子,我们写一个案例来巩固下

简单朴素的布局一下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/createDatabase"
        android:text="CreateDatabase"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
    <Button
        android:id="@+id/addData"
        android:text="AddData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
    <Button
        android:id="@+id/deleteData"
        android:text="DeleteData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
    <Button
        android:id="@+id/updateData"
        android:text="UpdateData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
    <Button
        android:id="@+id/queryData"
        android:text="QueryData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
</LinearLayout>

再枯燥无味的写一下代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    MyDatabaseHelper dbHelper;
    Button addData,deleteData,updateData,queryData;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        addData=findViewById(R.id.addData);
        deleteData=findViewById(R.id.deleteData);
        updateData=findViewById(R.id.updateData);
        queryData=findViewById(R.id.queryData);
        addData.setOnClickListener(this);
        deleteData.setOnClickListener(this);
        updateData.setOnClickListener(this);
        queryData.setOnClickListener(this);
        dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,1);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.createDatabase:
                dbHelper.getWritableDatabase();
                break;
            case R.id.addData:
                add();
                break;
            case R.id.deleteData:
                delete();
                break;
            case R.id.updateData:
                update();
                break;
            case R.id.queryData:
                query();
                break;

        }
    }
    public void add(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        //用ContentValues来添加数据
        ContentValues values=new ContentValues();
        values.put("name","My loved ugly girl");
        values.put("author","War");
        values.put("pages",100);
        values.put("price",135);
        db.insert("Book",null,values);
        //第一个参数为表名
        //第二个参数为在空处插入数据,这里写null即可
        //第三个参数为要插入的数据
    }
    public void delete(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        db.delete("Book","pages>?",new String[]{"50"});
        //第一个参数为表名
        //第二个参数为要判断的删除条件
        //第三个参数为条件对应的值
        //也就是说删掉Book表中pages>50的数据
    }
    public void update(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("price",999);
        db.update("Book",values,"name=?",new String[]{"My loved ugly girl"});
        //这句代码的意思是将Book表中name==My loved ugly girl的数据给改为values
    }
    public void query(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        Cursor cursor=db.query("Book",null,null,null,null,null,null);
        if(cursor.moveToFirst()){
            do{
                //遍历cursor对象
                String name=cursor.getString(cursor.getColumnIndex("name"));//根据列名取出数据
                String author=cursor.getString(cursor.getColumnIndex("author"));
                int pages=cursor.getInt(cursor.getColumnIndex("pages"));
                int prices=cursor.getInt(cursor.getColumnIndex("price"));
                Log.v("MainActivity","name:"+name+" author:"+author+" pages:"+pages+" prices:"+prices);
            }while (cursor.moveToNext());
        }
    }
}

运行APP,我们来测试一下

在这里插入图片描述 我们首先点击CreateDatabase 创建一个数据库 然后我们点AddData添加一条数据 随后我们点击QueryData查询一下,注意Logcat窗口 在这里插入图片描述 这样我们就把添加的数据给读取出来了。

然后我们点击UpdateData修改一下数据,然后再查询,可以看到prices已经被修改了 在这里插入图片描述 最后我们点击DeleteData删除数据,这样我们就查询不到任何数据了

到这里数据库的操作就结束了,你们一定会好奇,新建出来的BookStore.db在哪里呢?

其实和File存储和SharedPreferences存储一样,他们都在data/data/< packname >/里面

不过我们的SQLite存储的数据库还有一个子目录databases

在这里插入图片描述 这个便是我们的数据库文件了,但不同于File和SharedPreferences,AndroidStudio不能直接访问里面的数据。

那怎么办呢?我们这里又要学习一个新的知识了,坚持下去,不然找不到数据库,即使会用数据库,那也是个半吊子

这里AndroidStudio内置了一个数据库访问工具,我们需要手动将他添加出来

从AndroidStudio导航栏中打开File->Settings->Plugins->Installed就可以进入插件管理界面 在这里插入图片描述 在输入框中搜索“Database Navigator” 就可以找到我们的插件,把他下载即可。下载完成后记得重启AndroidStudio插件就可以正常工作了!不出意外,他应该出现在AndroidStudio的左侧边栏,多出了一个DB Browser工具

我们先将数据库导出 在这里插入图片描述 这里我导出到D盘根目录下

然后我们打开DB Browser 然后添加一个SQLite类型文件 在这里插入图片描述 点击右边箭头指向的按钮,我们就可以进去选择数据库文件了 在这里插入图片描述在这里插入图片描述 之后我们找到Book表的位置,双击打开它 在这里插入图片描述

然后我们点击No Filter 在这里插入图片描述 我们就可以看到数据库的内容了 在这里插入图片描述 这里数据不止一条,因为我点了好几次AddData,你们可以自行测试。

今天是在读大一(大专)的第35天,一天到晚除了敲代码啥也不会,不说了,我是废物

Android读取系统联系人

Android中如何读取系统联系人?借助内容提供器!

安卓四大组件之一的内容提供器,是我学习安卓的噩梦,我觉得这是安卓初期最为困难的一步

一个程序可以通过内容提供器,提供给其他程序访问自身内容的接口,那么任何程序就可以通过接口来获取我们提供的内容。 . . 自己的内容可以提供给别的程序,那别的程序一定也可以提供内容给我们。 . . 诸如系统本身的电话簿,短信,媒体库,就提供了相应的接口供我们读取内容。

可见内容提供器的重要,如果现在市面上所有的APP不能读取短信,电话簿等,相信功能一定会大打折扣的!

接下来我们先简单认识一下内容提供器,不然压根不知道代码为何意

对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver类,通过Context类中的getContentResolver()方法来获取到该类的实例,它提供了一系列方法来对数据进行CRUD操作,insert、delete、query、update。和SQLiteDatabase简直一模一样!

不过ContentResolver不接收表名参数,而是用一个Uri参数代替,这个参数被称为内容Uri,Uri给内容提供器中的数据建立了唯一标识符,它主要由两部分组成,authority和path

authority 是对于不同的应用程序做区分的 path 是对于同一应用程序中不同的表做区分的,通常添加到authority的后面

我们来举个例子,假如程序的包名为com.example.app,那这个程序对应的authority就可以命名为com.example.app.provider,再假设这个程序的数据库离存在一张表table,那么内容URI就可以写成com.example.app.provider/table

其实这样的内容URI还是不算标准的,我们应该在头部加上协议声明 完整的URI为:content://com.example.app.provider/table

因此可以看到URI可以很清楚的表达出我们要访问的哪个程序的哪张表里的数据,而如果只用表名,我们就很难知道是具体哪个应用程序里的表,ContentProvider真好!

彻底理解了URI,再写代码就会很容易了,先来写一个读取系统联系人的方法

public void readContacts(){
        ArrayList<String>contactsList=new ArrayList<>();
        Cursor cursor=null;
        try {
            cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
            if (cursor!=null){
                while(cursor.moveToNext()){
                    //获取联系人姓名
                    String displayName=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    //获取联系人手机号
                    String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add("名字:"+displayName+"手机号:"+number);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (cursor !=null){
                cursor.close();
            }
        }
    }

getContentResolver.query()的第一个参数便是URI,我们填写通讯录提供的内容URI:ContactsContract.CommonDataKinds.Phone.CONTENT_URI 就可以接收到系统联系人的数据了

ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME就是联系人名的值 ContactsContract.CommonDataKinds.Phone.NUMBER就是联系人手机号的值 我们就可以通过值来取出相应的数据,学过上一章的SQL数据库,绝对可以理解,没学过建议先去学SQL,不然很难迈过内容提供器这道坎!

代码不要急着抄,分开讲解完,文章最后会有汇总的代码

好了,进行下一部分。写完获取联系人,其实是没用的,因为在获取联系人的时候,必须先向系统索取读取联系人的权限:

我们先在Manifest中声明权限

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

这样一句也是远远不够的,因为用户还需要手动去设置里开启应用的权限,这样的话,用户肯定对我们的软件极其不满意!我们要使用动态获取权限

public void requestPermission(){
		//判断是否有权限
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
            //索取权限
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_CONTACTS},1);
        }else {
            //说明已经有权限了,无需再获取
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode==1){
            if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                //用户点了同意获取权限
            }else{
                //用户点了拒绝获取权限
            }
        }
    }

一个方法用来获取权限,一个方法是获取权限的回调方法,来判断用户是否同意获取权限 值得注意的是,回调方法里的requestCode参数就是我们ActivityCompat.requestPermissions获取权限的时候传入的第三个参数的请求码

有了获取权限和获取联系人的知识储备,我们来进行正式的代码编写 由于联系人数量不确定,当数量多到一个页面呈现不完全的时候,TextView就显的适用性不是很好,所以我们使用ListView来进行显示

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
    ListView listView;
    ArrayList<String>contactsList=new ArrayList<>();
    ArrayAdapter<String>adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView=findViewById(R.id.contacts_view);
        adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactsList);
        listView.setAdapter(adapter);
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
            //索取权限
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_CONTACTS},1);
        }else {
            //有权限了,直接读取
            readContacts();
        }
    }
    public void readContacts(){
        Cursor cursor=null;
        try {
            cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
            if (cursor!=null){
                while(cursor.moveToNext()){
                    //获取联系人姓名
                    String displayName=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    //获取联系人手机号
                    String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    //添加数据
                    contactsList.add("名字:"+displayName+"  手机号:"+number);
                }
                adapter.notifyDataSetChanged();//更新适配器的数据
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (cursor !=null){
                cursor.close();
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode==1){
            if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                //用户点了同意获取权限,直接读取
                readContacts();
            }else{
                //用户点了拒绝获取权限
                Toast.makeText(MainActivity.this,"您拒绝了权限,读取失败",Toast.LENGTH_SHORT).show();
            }
        }
    }
}

运行一波,看看咱们的成果!

在这里插入图片描述点一下拒绝在这里插入图片描述我们重新打开,再点一次允许在这里插入图片描述OK,成功了!本人的通讯录里只有父母2个人,一看就是大大的孝子!

俗话说,站在风口上,猪都能飞起来。安卓是个不折不扣的风口,当下时代人人都有手机,买菜、购物要用手机,上课打卡、饭卡洗澡,也要手机,甚至路边要饭的,也有二维码支付。这虽然显得荒谬,但的确也印证了安卓的时代的崛起,随着安卓对windows的取代权重的增加,这也同时增加了我愿耗费精力去精通安卓的决心,因此我放弃了曾经引以为傲的C++、易语言、游戏外挂。看CSDN里老程序员说,几年前连中文的安卓资料都没有,学起来十分困难,有问题也不知道与谁来交流,而现在人人皆知的github、CSDN、和我们最爱的百度,等等,无疑都给了我们这些最有利的学习条件,且行且珍惜,大学是我最好的学习技术的年纪,有幸这般年纪撞上了这最好的时代,争取早日成为一个不算菜的菜鸟

Android实现一键拨打电话功能

引文:今天写个简单的,篇幅较小,因为今晚还要腾出时间去深入多线程的学习,就当偷个懒吧!

拨打电话涉及到用户的手机费用,那自然要被谷歌列入危险权限范围内,所以我们在调用拨打电话前,肯定要先声明和申请拨打电话的CALL_PHONE权限

先在AndroidManifest.xml声明要申请的权限

<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>

声明了权限后,再进行动态权限的申请

public void getPermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.CALL_PHONE}, 1);
        } else {
            //拨打电话
            call();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == 1) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //同意权限
                call();//拨打电话
            } else {
                //拒绝权限
                Toast.makeText(MainActivity.this, "您拒绝了权限的申请,无法使用!", Toast.LENGTH_SHORT).show();
            }
        }
    }

其实我到现在都不是很理解为啥动态申请权限要这么写,反正书上和网上都这么写,仿佛千篇一律,跟着写多了,自然就行云流水了。

接下来写一下拨打电话call()方法的实现

public void call() {
        try{
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

这样基本的分步实现就完成了,下面我来整合一下。

首先要不费吹灰之力的设计出一个可以与高级UI工程师媲美的APP界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/call"
        android:text="拨打电话"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
</LinearLayout>

在这里插入图片描述 好了,这个界面设计水平,没有月薪50K的UI水准,都不敢说自己有百分百的把握能做出来

继续编写后端JAVA代码。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=findViewById(R.id.call);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getPermission();
            }
        });
    }

    public void call() {
        try{
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

    public void getPermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.CALL_PHONE}, 1);
        } else {
            //拨打电话
            call();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == 1) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //同意权限
                call();//拨打电话
            } else {
                //拒绝权限
                Toast.makeText(MainActivity.this, "您拒绝了权限的申请,无法使用!", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

运行起来,在申请权限的时候点一波拒绝在这里插入图片描述在这里插入图片描述

接下来再点一下,这次点同意在这里插入图片描述在这里插入图片描述 到此为止,拨打电话功能就实现了,Very Easy,如果你也拨打成功了,快把这个振奋人心的消息告诉10086的客服吧!

今天是2020年11月16日,希望2120年的今天,我还能活着见到这篇博客

Android发送短信功能

引文:Android发送短信我目前接触到的有两种方式 一种是直接发送短信 一种是填写好收信人和发送的内容,让用户自己去点击发送 我决定把这两种方法都写出来,技多不压身!

发送短信直接会对用户扣费,我觉得是极度危险的权限。

该权限为:

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

先写间接发送短信

public void sendSMS1(String phoneNumber,String message){
    //判断是否为电话号码
    if(PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber)){
        //创建发送短信的通讯intent,将手机号Uri解析后写入
        Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:"+phoneNumber));
        //给intent插入发送的内容
        intent.putExtra("sms_body",message);
        //启动
        startActivity(intent);
    }
}

间接发送还是很简单的,主要还是搞一下直接发送吧,直接发送短信对权限的要求更严格,需要动态申请权限

public void requestPermission() {
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.SEND_SMS}, 1);
    } else {
        sendSMS2(phoneNumber,message);//发送短信
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if(requestCode==1){
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            sendSMS2(phoneNumber,message);//发送短信
        } else {
            Toast.makeText(MainActivity.this, "您拒绝了权限的申请", Toast.LENGTH_LONG).show();
        }
    }
}

有了权限,然后继续编写直接发送短信的方法

public void sendSMS2(String phoneNumber,String message) {

    if (!phoneNumber.isEmpty() && !message.isEmpty()) {
        SmsManager smsManager = SmsManager.getDefault();
        ArrayList<String> strings = smsManager.divideMessage(message);
        for (int i = 0; i < strings.size(); i++) {
            smsManager.sendTextMessage(phoneNumber, null, message, null, null);
        }
        Toast.makeText(MainActivity.this, "发送成功", Toast.LENGTH_LONG).show();
    } else {
        Toast.makeText(MainActivity.this, "手机号或内容不能为空", Toast.LENGTH_LONG).show();
        return;
    }
}

发送短信算是Android中很简单的功能了,我觉得没必要写太多文字,因为这个功能确实不实用

本以为可以做成无限发送短信,让对面手机直接欠费,果然还是我太天真了,Android对于这方面的保护还是很到位的,当发送的时候手机会询问是否发送,发送次数多了也会再次询问,所以坏事是不能搞了,个人感觉这个功能就没啥意思了。

此时的我还在上专业课,JAVA老师正在认真的教我们if-else这种困难无比的知识点,我却在不务正业的写博客,既然已经写完了,我要去学if去了,告辞!

Android中Broadcast广播的使用详解

注:本章内容多数摘自郭霖大神的《第一行代码-Android》,因为书上这一章写的很完美了,我自己再另写就有些多余了,但由于第二版《第一行代码》书籍年代久远,书上这一章有一小部分细节对于新版安卓系统有不适用、需要改动的地方,本文会将这些会踩的坑指出来

1、动态注册监听网络变化

接收系统广播必须先创建广播接收器,就是新建一个类,让他继承自BroadcastReceiver,并重写父类的onReceiver()就行了,这样当有广播到来时,onReceiver()方法就会执行

新建一个BroadcastTest项目

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        IntentFilter intentFilter=new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver=new NetworkChangeReceiver();
        //注册广播
        registerReceiver(networkChangeReceiver,intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //动态注册的广播需要取消注册
        unregisterReceiver(networkChangeReceiver);
    }
    class NetworkChangeReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            //收到广播
            Toast.makeText(context,"network chages",Toast.LENGTH_LONG).show();
        }
    }
}

系统网络状态变化时,系统发出的就是android.net.conn.CONNECTIVITY_CHANGE这条广播,因此intentFilter需要添加这个值,因此每次网络状态变化时都会触发广播接收器的onReceive()方法

这样的话,只能得到网络状态改变的提示,但不知道具体是连接了网络还是断开了网络。我们对NetworkChangeReceiver类的代码改进一下

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        IntentFilter intentFilter=new IntentFilter();
        //系统网络状态变化时,系统发出的就是android.net.conn.CONNECTIVITY_CHANGE这条广播
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver=new NetworkChangeReceiver();
        //注册广播
        registerReceiver(networkChangeReceiver,intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }
    class NetworkChangeReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            //收到广播
            ConnectivityManager connectivityManager=(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo();
            if(networkInfo!=null && networkInfo.isAvailable()){
                Toast.makeText(context,"network is available",Toast.LENGTH_LONG).show();
            }else {
                Toast.makeText(context,"network is unavailabe",Toast.LENGTH_LONG).show();
            }
        }
    }
}

ConnectivityManager是一个系统服务类,专门管理网络连接的,然后调用getActivityNetworkInfo()方法就可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable()方法,就可以判断出是否有网络了

这里的访问网络状态是需要声明权限的

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>

这样动态注册就结束了,具体的效果可以自己测试

2、静态注册实现开机注册

先创建一个广播接收器(package->New->Other->Broadcast Receiver),命名为BootCompleteReceiver,并修改BootCompleteReceiver中的代码

public class BootCompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"Boot Complete",Toast.LENGTH_LONG).show();
    }
}

静态注册的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用,不过由于我们是使用AndroidStudio的快捷方式创建的广播接收器,因此注册这一步已经被自动完成了。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true"></receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

可以看到,<application>标签内出现了一个新的标签<receiver>,所有静态的广播接收器都是在这里进行注册的,它的用法其实和<activity>标签非常相似,通过android:name来指定具体注册哪一个广播接收器

有了BootCompleteReceiver这个广播接收器还是不能接收到开机广播的,需要声明监听开机广播权限,并且修改<receiver>标签

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>

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

这样就实现了接收开机广播了,将模拟器关闭并重新启动,在启动完成后就会接收到开机广播

总结:在静态注册广播中,需要将想要接收的广播填写到<receiver>标签下!相应的触发事件写在广播接收器的onReceive()里面

提示:以上代码在模拟器上测试是有效的,但可能到了真机会失效,具体原因见链接开机广播接收不到的原因open in new window 解决不了也不要紧,因为重点不在开机广播,而在于静态广播的原理的学习

3、自定义广播

(1)发送标准广播 我们先新建一个广播接收器来接收广播

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_LONG).show();
    }
}

然后在AndroidManifest.xml中对这个广播接收器的<receiver>标签进行修改

		<receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"></action>
            </intent-filter>
        </receiver>

我们定义一个按钮来发送广播

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/button"
        android:text="Send Broadcast"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </Button>
</LinearLayout>

接着编写按钮事件

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent("com.example.broadcasttest.MY_BROADCAST");
                //重点!必须设置包名!不然发送不出去
                intent.setPackage(getPackageName());
                sendBroadcast(intent);
            }
        });

    }
}

发送广播的时候一定要给intent设置包名,这是新版安卓的特性,不加不给用,老版本安卓可以不加

现在点击按钮后,就会触发广播了 在这里插入图片描述 (2)发送广播给别的程序 步骤同(1),但要注意将setPackage()的参数填成要发送的程序的包名!!

4、使用本地广播

本地广播顾名思义,就是防止全局广播引起的安全性问题,这种广播只有自己的程序可以接收 这里直接修改JAVA源码,和普通的动态注册差不多

public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取实例
        localBroadcastManager=LocalBroadcastManager.getInstance(this);
        Button button=findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                //本地广播可以不设置包名
                //intent.setPackage(getPackageName());
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter=new IntentFilter();
        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
        localReceiver=new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver,intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context,"received local broadcast",Toast.LENGTH_SHORT).show();
        }
    }
}

到这里文章就结束了,写了足足3个小时,期间吃了一桶泡面,2包干脆面,看了20分钟电影,又聊了会QQ,在快乐的氛围中我已经把广播的内容从接近遗忘到烂熟于心,写博客的用处真的非同凡响!一日一更,加油加油加油

Android中Fragment碎片的使用

碎片(Fragment)是一种可以嵌入在活动当中的UI片段,可以理解为,碎片就是一个活动,我们可以把这几个活动进行拼接,就好似碎片拼接在一起,所以这种UI片段被称之为碎片

碎片加载方式有两种,分为:1、静态碎片 2、动态碎片

1、静态加载碎片

我们在layout里面建立两个新的活动,分别命名为left_fragment和right_fragment,这样就相当于建立了两个碎片的布局了 left_fragment:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#00ff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button">
    </Button>
</LinearLayout>

right_fragment:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:textSize="20sp"
    android:text="This is right fragment">
</TextView>
</LinearLayout>

然后分别建两个类LeftFragment和RightFragment来继承Fragment

LeftFragment:

public class LeftFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.left_fragment,container,false);
        return view;
    }
}

RightFragment:

public class RightFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.right_fragment,container,false);
        return view;
    }
}

在方法里要重写onCreateView()方法,然后在这个方法里通过Inflate()方法将刚才定义的left_fragment和right_fragment布局加载进来

这样两个静态碎片都建立完毕了,只需要在主布局activity_main.xml里加载出来就行了

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">
    <fragment
        android:id="@+id/left_fragment"
        android:name="com.example.fragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent">
    </fragment>
    <fragment
        android:id="@+id/right_fragment"
        android:name="com.example.fragment.RightFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent">
    </fragment>
</LinearLayout>

给碎片的name填写上刚刚建立的两个碎片的类名即可 注意:一定要给fragment定义id,不然会闪退,这里是个坑

运行起来就能看到效果了 在这里插入图片描述

2、动态加载碎片

来继续动态碎片的加载 我们删掉右侧的碎片,这样就只剩下左侧的碎片了,然后用动态加载来加载右侧的碎片,这样可观性很强

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">
    <fragment
        android:id="@+id/left_fragment"
        android:name="com.example.fragment.LeftFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent">
    </fragment>
    <FrameLayout
        android:id="@+id/right_layout"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent">
    </FrameLayout>
</LinearLayout>

FrameLayout是帧布局,所有的控件默认会摆放在布局的左上角,所以我们在帧布局里放一个碎片,碎片位置就被定好了

然后修改MainActivity中的代码

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager=getSupportFragmentManager();
                FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
                //在帧布局里加入右侧碎片
                fragmentTransaction.replace(R.id.right_layout,new RightFragment());
                fragmentTransaction.commit();
            }
        });
    }
}

我们先给左侧碎片里的按钮注册了点击事件,然后对碎片进行了动态添加,动态添加分为以下步骤 (1)创建待添加的碎片实例 (1)获取FragmentManager,在活动中可以直接调用getSupportFragmentManager()方法得到 (2)开启一个事务,通过调用beginTransaction()方法开启 (3)向容器中添加/替换碎片,一般用replace()方法实现,需要传入容器的id和待添加的碎片实例 (4)提交事务,调用commit()方法来完成

3、在碎片中模拟返回栈

我们只需要在动态加载布局的时候,在事务里调用addToBackStack()方法,就可以将加载的布局加入栈中,此时我们手机点击BACK返回键,不会直接退出程序,而是碎片先还原,然后再按一次才会退出。

	FragmentManager fragmentManager=getSupportFragmentManager();
    FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
    //在帧布局里加入右侧碎片
    fragmentTransaction.replace(R.id.right_layout,new RightFragment());
    //碎片入栈
    fragmentTransaction.addToBackStack(null);
    fragmentTransaction.commit();

4、碎片和活动之间进行通信

我们想在活动中调用碎片里的方法,或者在碎片里调用活动中的方法,该如何实现呢?

(1)活动中调用碎片中的方法 FragmentManager提供了一个类似于findViewById()的方法,专门从布局文件中获取碎片的实例,代码如下:

 RightFragment rightFragment=(RightFragment) getSupportFragmentManager().findFragmentById(R.id.right_layout);

然后就可以轻松调用碎片里的方法了 诸如 rightFragment.*****(); (2)碎片中调用活动中的方法 在每个碎片中都可以通过调用getActivity()方法来得到与这个碎片相关联的活动对象

MainActivity activity=(MainActivity)getActivity();

诸如 activity.*****();

碎片到这里也就是浅层的学习了下,算是挺简单的一个内容了,因为很容易理解。

到今天为止,书上只剩下两个项目地图定位和天气预报了,争取在12月初结束掉当前的任务,再自主开发两个小项目,其实现在技术储蓄已经足够了,内心早已蠢蠢欲动,不过还是打算先学习更多的新知识再去实战,之后打算用半年多的时间来专攻《疯狂Android》和《疯狂Java》这两本书,加起来一共有1678页,任务量还是很大的,希望自己能坚持下去。

注:文章内容摘自书籍《第一行代码》,本文仅用来自己学习Android的复习,不存在抄袭的意图

服务和活动进行通信

本文给自己简单复习一下,不多说废话

新建一个Service服务,命名MyService 其实就是一个MyService类继承了Service,并且在AndroidManifest.xml的application中注册了service

 <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>

编写MyService代码

public class MyService extends Service {

    private  DownloadBinder mBinder=new DownloadBinder();

    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d("MyService","startDownload executed");
        }
        public int getProgress(){
            Log.d("MyService","getProgress executed");
            return 0;
        }
    }

    public MyService() {
    }

    @Override
    public void onCreate() {
        Log.d("MyService","onCreate executed");
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Log.d("MyService","onDestroy executed");
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService","onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

当一个活动绑定了服务后,就可以调用该服务器里的Binder提供的方法了 DownloadBinder类继承了Binder类 onBind()方法要返回要绑定的的Binder,即返回DownloadBinder类即可

接着编写MainActivity中调用服务的代码

 private MyService.DownloadBinder downloadBinder;

    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder=(MyService.DownloadBinder)service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

接下来进行绑定服务就可以了

Intent bindIntent=new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);

取消绑定:

unbindService(connection);

绑定完成后会触发ServiceConnection中的onServiceConnected()方法 取消绑定后会触发ServiceConnection中的onServiceDisconnected()方法

现在已经凌晨3点了,发完博客准备睡觉了。。。。。。

Android中启动子线程的方法

法1

class MyThread extends Thread{
            @Override
            public void run() {
                //
            }
        }
        new MyThread().start();

法2

        class MyThread implements Runnable{
            @Override
            public void run() {
				//
            }
        }
        MyThread myThread= new MyThread();
        new Thread(myThread).start();

法3 最为常用的写法

new Thread(new Runnable() {
            @Override
            public void run() {
                //
            }
        }).start();

注意:在子线程中更新UI是会出现异常的,程序会出现闪退。对此,Android提供了一套异步消息处理机制,完美解决了在子线程中操作UI的问题

private Handler handler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    textView.setText("Nice To meet you");
                    break;
                default:
                    break;
            }
        }
    };

首先新增一个Handler对象,重写父类的handleMessage()方法,然后在这里对具体的Message进行处理,在handleMessage()里面,我们就可以放心大胆的对UI进行操作而不至于崩溃了。这里判断Message的what为1的时候就执行操作

接下来我们在线程中调用Handler

new Thread(new Runnable() {
            @Override
            public void run() {
                Message message=new Message();
                message.what=1;
                handler.sendMessage(message);
            }
        }).start();

新建一个Message,将其what定为1,然后传递信息就Ok了,当Handler收到信息后,会进行相应的操作

法4 使用AsyncTask

AsyncTask是一个抽象类,使用AsyncTask很简单,只需要创建一个子类去继承他,然后重写几个父类方法即可,先枚举下要重写的父类方法

1、onPreExecute() 在后台任务开始执行之前调用,用于执行一些初始化操作,例如显示一个进度条对话框 2、doInBackground(Params...) 这个方法里的代码都会在子线程进行,所以不能在里面进行UI操作,可以在这里处理所有耗时的任务,例如加载进度条的 进度。如果需要更新UI,可以调用publishProgress(Progress...)来实现 3、onProgressUpdate(Progress...) 如果调用了publishProgress(Progress...)方法后,onProgressUpdate(Progress...)会很快被调用,这里的参数就是publishProgress里传过来的 4、onPostExecute(Result) 当后台任务执行完毕,并且通过return返回后,这个方法会很快得到调用,我们可以在这里进行一些UI操作,例如关闭进度条对话框等

我们新建一个类DownloadTask来继承AsyncTask,然后用其模拟下载的过程

class DownloadTask extends AsyncTask<Void,Integer,Boolean>{

        @Override
        protected void onPreExecute() {
            progressDialog=new ProgressDialog(MainActivity.this);
            progressDialog.setMax(100);
            progressDialog.setIcon(R.mipmap.ic_launcher);
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressDialog.setTitle("标题");
            progressDialog.setMessage("任务正在执行中,请稍后");
            progressDialog.show();
            progressDialog.setCancelable(false);
            super.onPreExecute();
        }

        @Override
        protected Boolean doInBackground(Void... voids) {
            int i=0;
            while (i<100){
                i++;
                publishProgress(i);
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            return true;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            progressDialog.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            progressDialog.dismiss();
        }
    }

AsyncTask有三个参数,第一个是将传给后台任务的参数类型,第二个是泛型参数,是onProgressUpdate的参数的数据类型,第三个是任务执行完毕后要返回的值的类型

注: doInBackground()方法结束后会返回一个值给onPostExecute的参数, publishProgress()方法会传值给onProgressUpdate的参数

想要调用这个AsyncTask,让进度条对话框进行执行,只需要很简单的两行代码

 public void startTask(){
        DownloadTask downloadTask=new DownloadTask();
        downloadTask.execute();
    }

值得注意的是AsyncTask在Android11中已经被废除了,不过还是有学习的必要

Last Updated:
Contributors: wqby