《第一行代码》学习笔记
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()里面
提示:以上代码在模拟器上测试是有效的,但可能到了真机会失效,具体原因见链接开机广播接收不到的原因 解决不了也不要紧,因为重点不在开机广播,而在于静态广播的原理的学习
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中已经被废除了,不过还是有学习的必要