前言:目录结构还有待优化,可直接ctrl+F搜索有无需要了解的内容
本笔记属于个人初学android过程时总结,如有错误或者图片缺失,还请留言指正,十分感谢!
仅供学习参考,如需转载请注明来自[Durango]: www.durango.cn

第一章 第一行代码

Android系统架构

Linux内核层、系统运行库、应用架构层、应用层

四大组件

活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)、 内容提供者(Content Provider)

APP目录

  • 1.build: 一些编译时自动生成的文件

2.libs: 第三方jar包

3.androidTest: 测试用例,自动化监测

4.java: 放置Java代码

5.res: 资源文件

​ drawable: 图片

​ layout: 布局文件

​ values: 字符串

6.AndroidMainfest.xml: 项目配置文件,四大活动必须在此注册!

​ ……

主活动

image-20200912223928217

引入布局

1
setContentView( R.layout.hello_world_layout)

资源的引用

1.代码中

1
R.string.hello_world

​ R .类型 .名称

2.XML中

1
@.string.hello_world

​ @ .类型 .名称

定义id: @+id/名称

日志工具

(p26)

第二章 探究活动

活动是什么

活动的基本用法

一、手动创建活动

image-20200913172254284

二、创建和加载布局

image-20200913172406723

image-20200913172431922

添加按钮:

1
2
3
4
5
<Button
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"/>

加载布局:

1
setContentView(R.layout.布局文件名)

三、在AndroidManifest文件中注册

image-20200913174606232

四、在活动中使用Toast提醒

image-20200913174713039

五、在活动中使用Menu

1.image-20200913174848093

2.然后在main.xml文件中添加(创建两个菜单项)

1
2
3
4
5
6
<item
android:id="@+id/add_itme"
android:title="Add"/>
<item
android:id="@+id/remove_itme"
android:title="Remove"/>

3.重写onCreateOptionMenu()方法

在Java代码中使用快捷键 ”Ctrl+O“

image-20200913175533827

4.重写onOptionsItemSelected

image-20200913180038921

六、摧毁一个活动

stop();

使用Intent(意图)在活动之间穿梭

一、使用显式的Intent

image-20200913212452461

首先构建一个Intent,传入FirstActivity.this作为上下文,传入SecondActicity.class作为目标活动,然后通过startActivity()方法来执行这个Intent。

二、使用隐式的Intent(意图过滤器intent-filter)

1.image-20200913213406302

image-20200913213734619

2.DEFAULT是一种默认的category,在调用startActicity()方法时会自动将这个category添加到Intent中。

3.每个Intent只能指定一个action,却能指定多个category,这样增加:

intent.addCategory(“com.example.activity.MY_CATEGORY”)

4.然后在活动二的intent-filter中添加声明:

<category android:name=”com.example.activity.MY_CATEGORY”

5.只有<action>和<category>同时匹配上 Intent中指定的 action 和 category 时,这个活动才能响应该Intent

三、更多隐式的Intent

1.使用隐式的Intent不仅能启动自己程序的活动,还可以启动其他程序的活动

image-20200913220158511

(1)这里我们首先指定了Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为 android.intent.action.VIEW。
(2)然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个uri对象 传递进去。
(3)可能你会对setData()部分感觉到陌生,这是我们前面没有讲到的。这个方法其实并不复杂,它接收一个uri对象, 主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传人到Uri.parse()方法中解析产生的。

2.与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地

            指定当前活动能够响应什么类型的数据。\<data>标签中主要可以配置以下内容。
            android:scheme。用于指定数据的协议部分,如上例中的http部分。
            android:host。用于指定数据的主机名部分,如上例中的www.baidu.com部分。
            android:port。用于指定数据的端口部分,一般紧随在主机名之后。
            android:path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
            android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。

3.

这样ThirdActivity就能和浏览器一样响应一个打开网页的Intent了

四、向下一个活动传递数据

1.通过Intent的putExtra()方法携带数据

image-20200915194843260

*putExtra(“extra_data”, data)*第一个参数是“键”,第二个参数是“数据”

2.通过Intent的getStringExtra(键)获取数据

image-20200915200007173

getStringExtra() / getIntExtra() / getBooleanExtra()

五、返回数据给上一个活动

1.通过startActivityForResult()来启动SecondActivity

image-20200915213454637

2.使用setResult()返回数据

image-20200915213859847

3.在FirstActivity中重写 onActivityResult()

image-20200915214529345

  1. 首先通过检查requCode来判断数据来源,(FirstActivity可能调用startActivityForResult()去启动很多不同的活动,每个活动都会回调到活动的onActivityResult()中)。
  2. 然后通过resultCode来判断处理结果是否成功(RESULT_OK|RESULT_CANCELED)
  3. 最后通过 getStringExtra(键)来获取data携带的数据。

六、Bundle与Intent结合传递大量数据

1,传数据

1
2
3
4
5
6
Bundle bundle = new Bundle();
bundle.putString("date_string","some thing you just typed");
bundle.putInt("data_Int",256);
Intent intent = new Intent(...);
intent.putExtras(bundle);
startActivity(intent);

2.取数据

1
2
3
4
Intent  intent = getIntent();
Bundle bundle = indent.getExtres();
String str = bundle.getString("data_string");
int in = bundle.getInt("data_Int");

活动的生命周期

一、返回栈

其实Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集其实Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack )。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中人栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个人栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。

二、活动状态

1.运行状态

当一个活动未与返回栈的栈顶时,这时活动就处于运行状态。

2.暂停状态

当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。

3.停止状态

当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保 存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可 能会被系统回收。

4.摧毁状态

当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证 手机的内存充足。

三、活动的生存期

1.oncCreat(): 活动第一次被创建时调用

2.onStart(): 在活动由不可见变为可见时调用

3.onResume(): 在活动准备好和用户进行交互时调用

4.onPause(): 在系统准备启动或者恢复另一个活动时调用

5.onStop(): 在活动完全不可见时调用

6.onDestory(): 在活动被销毁时调用

7.onRestart(): 在活动由停止状态变为运行状态时调用

image-20200919221411685

​ 完整生存期:onCreate() → onDestory()

​ 可见生存期:onStart() → onStop()

​ 前台生存期:onResume() → onPause()

四、体验活动的生命周期

1.新建两个活动分别为 NormalActicity 和 DialogActivity

2.修改对话框活动的主题:

  1. AppCompatActivity: android:theme=”@style/Theme.AppCompat.Dialog”
  2. Activity:android:theme=”@android:style/Theme.Dialog”

3.在主活动中打印日志(充分利用日志过滤器)

image-20200919222550069

(1)可以看到,MainActivity 第一次被创建时会依次执行 onCreate()、onStart() 和 onResume()方法;
(2)启动NormalActivity,会执行 onPause() 和 onStop(),因为NormalActivity已经完全遮住MainActivity了;
(3)返回MainActivity,会执行 onRestart()、onStart()和onResume();
(4)启动DialogActivity,只会执行onPause()方法,因为此时MainActivity没有完全遮住;
(5)返回MainActivity,只会执行onResume。

五、活动被回收了怎么办

1.问题:当一个活动进入停止状态时,可能被系统回收,再次返回该活动时会通过onCreate()创建一个新的实例,造成数据的丢失。

2.通过onSaveInstanceState()回调方法保存数据

1
2
3
4
5
6
@0verride
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstancestate(outState);
string tempData - "Something you just typed";
outState.putString( "data_key" , tempData) ;
}

3.在onCreate()中取值

1
2
3
4
if (savedInstancestate != null){
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG,tempData);
}

活动的启动模式 (launchMode)

一、standard

1.standard是活动默认的启动模式

​ 每当启动一个新的活动,它就会在返回栈中人栈,并处于栈顶的位置。对于使用standard模式的活动,

​ 系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例

2.image-20200922131226413

二、singleTop

1.当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。

2.image-20200922131614840

三、singleTask

1.当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

2.image-20200922131805919

四、singleInstance

1.指定为singleInstance的活动会启动一个新的返回栈,

在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。

2.image-20200922132137611

活动的最佳实践

第三章 UI开发的点点滴滴

如何编写程序界面

常用控件的使用方法

一、TextView

1.文本对齐方式(gravity)

android: gravity=”center|top|bottom|left|right”

2.文本颜色,大小(textColor、textSize

字体以“sp”为单位

3.文本标签TextView其它的XML文件元素

属性名称 描述
android:autoLink 设置是否当文本为URL链接/email/电话号码/map时,文本显示为可单击的链接。可选值(none/web/email/phone/map/all)
android:autoText 如果设置,将自动执行输入值的拼写纠正。此处无效果,在显示输入法并输入的时候起作用。
android:linksClickable 设置链接是否单击连接,即使设置了autoLink。
android:maxLength 限制显示的文本长度,超出部分不显示。
android:lines 设置文本的行数,设置两行就显示两行,即使第二行没有数据。
android:maxLines 设置文本的最大显示行数,与width或者layout_width结合使用,超出部分自动换行,超出行数将不显示。
android:minLines 设置文本的最小行数,与lines类似。
android:lineSpacingExtra 设置行间距。
android:lineSpacingMultiplier 设置行间距的倍数。如”1.2”
android:textColor 设置文本颜色
android:textColorLink 文字链接的颜色.
android:textStyle 设置字形[bold(粗体) 0, italic(斜体) 1, bolditalic(又粗又斜) 2] 可以设置一个或多个,用“|”隔开
android:maxHeight 设置文本区域的最大高度
android:minHeight 设置文本区域的最小高度
android:minWidth 设置文本区域的宽度,支持度量单位:px(像素)/dp/sp/in/mm(毫米)。
android:maxWidth 设置文本区域的最大宽度

二、Button

1.自动进行大小写转换(textAllCaps)

android: textALlCaps:”false|true”

2. 4种事件监听器的注册方式

image-20200923141303023 image-20200923141336930 image-20200923141506965 image-20200923141521823

三、EditText

1.提示性文字(hint)

2.最大行数(maxLines

3.密码框(android: inputType=”TextPassword”

4.获取文本内容(,,, .getText().toString( )

5.输入框EditText 其它的XML文件元素

属性名称 描述
android:imeOptions 设置软键盘的Enter。有如下值可设置:normal,actionUnspecified,actionNone,actionGo,actionSearch,actionSend,actionNext,actionDone,flagNoExtractUi,flagNoAccessoryAction,flagNoEnterAction。可用’|’设置多个。这里仅设置显示图标之用。
android:imeActionId 设置IME动作ID,在onEditorAction中捕获判断进行逻辑操作。
android:imeActionLabel 设置IME动作标签。但是不能保证一定会使用,猜想在输入法扩展的时候应该有用。
android:singleLine 设置单行显示。如果和layout_width一起使用,当文本不能全部显示时,后面用“…”来表示。如android:text=”test_ singleLine “ android:singleLine=”true” android:layout_width=”20dp” 将只显示“t…”。如果不设置singleLine或者设置为false,文本将自动换行
android:maxlines 指定 EditText 的最大行数,当输入的内容超过指定最大行数时,文本就会向上滚动,而不继续拉伸EditText

四、ImageView

1.android:src

2. android:background

3.setImageResource()方法

五、ProgressBar (进度条)

1.(控件通用属性)visibility=”visiblle|invisible|gone”

2.代码中获取/控制控件可见性

​ 获取:getVisibility()

​ 设置:setVisibility(View.VISIBLE|View.INVISIBLE|View.GONE)

3.水平进度条

(1)style=”?android:attr/progressBarStyleHorizontal” //类别
android:max=”100” //进度条最大值
(2)image-20200922172028074

4.

image-20200923141825784

image-20200923141850913

六、AlterDialog(对话框)

1.AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力,因此AlertDialog 一般都是用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。下面我们来学习一它的用法,修改MainActivity中的代码,如下所示:

2.image-20200922172454707

​ 首先通过AlertDialog.Builder创建一个AlertDialog 的实例,然后可以为这个对话框设置标题、
​ 内容、可否取消等属性,接下来调用setPositiveButton()方法为对话框设置确定按钮的点击
​ 事件,调用setNegativeButton()方法设置取消按钮的点击事件,最后调用show()方法将对话
​ 框显示出来。

七、ProgressDialog(带进度条的提示框)

1.ProgressDialog 和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其他控件的交互能力。不同的是,ProgressDialog 会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心地等待。它的用法和AlertDialog 也比较相似

2.image-20200922173459763

注意,如果在setCancelable()中传人了false,表示ProgressDialog是不能通过Back键
取消掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog 的
dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在。

八、RadioButton(单选按钮) 和 Checkbox(复选框)

九、Spinner (下拉框)

image-20200923142240994 image-20200923142315034 image-20200923142334764

详解4种基本布局

一、线性布局(LinearLayout

1.排列方向orientation

(1)horizontal (2)vertical

2.对齐方式

(1)gravity: 文字在控件中的对齐方式
(2)layout_gravity: 控件在布局中的对齐方式

​ top、bottom、left、right、center_vertical、center_horizontal

!注意:LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时 水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。

3.重要属性 layout_weight

(1)允许我们使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面可以起到非常重要
(2)定义水平/垂直方向的比例时,需要指定layout_width=”0dp”/layout_height=”0dp”

二、相对布局

1.相对于父布局进行定位

属性声明 功能描述
android:layout_alignParentLeft 是否跟父布局左对齐
android:layout_alignParentRight 是否跟父布局右对齐
android:layout_alignParentTop 是否跟父布局顶部对齐
android:layout_alignParentBottom 是否跟父布局底部对齐
Layout_centerHorizontal 在父布局中水平居中
Layout_centerVertical 在父布局中垂直居中
Layout_centerInParent 在父布局的中间位置
image-20200922224343384

2.相对于兄弟组件定位

属性声明 功能描述
android:layout_toRightOf=“控件id” 在指定控件右边
android:layout_toLeftOf=“控件id” 在指定控件左边
android:layout_above=“控件id” 在指定控件上边
android:layout_below=“控件id” 在指定控件下边
android:layout_alignBaseline=“控件id” 与指定控件水平对齐
android:layout_alignLeft=“控件id” 与指定控件左对齐
android:layout_alignRight=“控件id” 与指定控件右对齐
android:layout_alignTop=“控件id” 与指定控件顶部对齐
android:layout_alignBottom=“控件id” 与指定控件底部对齐

3. margin(偏移):设置组件与相对于的边距

属性声明 功能描述
layout_margin 设置组件上下左右的偏移量
layout_marginLeft 设置组件离左边的偏移量
layout_marginRight 设置组件离右边的偏移量
layout_marginTop 设置组件离上边的偏移量
layout_marginBottom 设置组件离下边的偏移量

4.padding(填充):设置组件内部元素间的边距

属性声明 功能描述
padding 设置组件上下左右的偏移量
paddingLeft 设置组件离左边的偏移量
paddingRight 设置组件离右边的偏移量
paddingTop 设置组件离上边的偏移量
paddingBottom 设置组件离下边的偏移量
!margin针对的是容器(布局)中的组件,设置的是偏移,而padding针对的是组件中的元素,设置的是填充。

三、帧布局

四、百分百布局

1.percentFrameLayout: 继承了FrameLayout的全部特性

percentRelativeLayout: 继承了RelativeLayout的全部特性

2.设置组件的宽高

app: layout_widthPercent=”..%”
app: layout_heightPercent=“..%”

3.在项目的build.gradle中添加百分比布局库的依赖,保证百分比布局在Android所有系统版本上的兼容性

image-20200922230757044

4.使用百分比布局

image-20200922230933563

创建自定义控件

1.引入布局

(1)创建一个标题栏布局title.xml文件

(2)在活动中引用 <include layout = “@layout/title”/ >

(3) 隐藏系统自带的标题栏

ActionBar actionbar = getSupportActionBar(

    <font color='red'>if (actionbar !=null) {</font>
        <font color='red'>actionbar.hide( );</font>
    <font color='red'>}</font>

2.创建自定义控件(布局)

(1)引入布局的技巧出现的问题:引人布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够响应事件,我们还是需要在每个活动中为这些控件单独编写–次事件注册的代码。比如说标题栏中的返回按钮,其实不管是在哪一个活动中,这个按钮的功能都是相同的,即销毁当前活动。而如果在每一个活动中都需要重新注册一遍返回按钮的点击事件,无疑会增加很多重复代码,这种情况最好是使用自定义控件的方式来解决。

(2)创建一个自定义布局类 TitleLayout(包含组件的响应事件)

public class TitleLayout extends LinearLayout {

​ public TitleLayout(Context context,AttributeSet attrs) {
​ super(context,attrs);
LayoutInflater.from(context).inflate(R.layout.title, this); //动态加载布局文件

​ //布局id, 父布局

​ Button titleBack = (Button) findViewById(R.id.title_back);
​ Button titleEdit = (Button) findViewById(R.id.title_edit);
​ titleBack.setOnclickListener(new onclickListener(){
​ @override
​ public void onclick(View v) {
( (Activity) getContext()).finish(); //结束此次活动
​ }
​ });
​ titleEdit.setonclickListener(new OnclickListener() {
​ @override
​ public void onclick(View v){
​ Toast.makeText(getContext(),”You clicked Edit button”,
​ Toast.LENGTH_SHORT).show();
​ });

​ }

}

3.在布局文件中添加这个自定义控件

image-20200930140657613

添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,我
们需要指明<font color='red'>控件的完整类名</font>,<font color='red'>包名在这里是不可以省略的</font>。

最常用和最难用的控件——ListView

一、ListView的简单用法

1.在布局中加入ListView控件

image-20201001193319537

2.准备数据(M)

image-20201001193518241

3.准备视图(V)

ListView listView = (ListView) findViewById(R.id.list_view);

4.准备适配器(C)

ArrayAdapter< String > adapter = new ArrayAdapter< String>(MainActivity.this, android.R.layout.simple_list_item_1, data);
  • 参数一:当前活动上下文
  • 参数二:子项布局ID
  • 参数三:数据数组
image-20201001193946230

二、定制ListView的界面

1.新建一个Fruit类,作为适配器的适配类型

​ public class Fruit {
​ private String name;
​ private int imageId;

​ public Fruit(String name,int imageId) {
​ this.name = name;
​ this.imageId = imageId;
​ }
​ public String getName( ) {
​ return name;

​ }

​ public int getImageId( ) {
​ return imageId;

​ }

​ }

2.自定义子项布局fruit_item.xml

image-20201001194813625

3.自定义适配器MyAdapter

image-20201001195642103

image-20201001195931382

image-20201001200025598

4.初始化数据,创建数据列表

image-20201001200303132 image-20201001200312627

四、提升ListView的运行效率

1.重用convertView

image-20201004105237701

2.内部类ViewHolder对控件实例进行缓存

image-20201004105406460

更强大的滚轮控件——RecyclerView

一、基本用法

1.首先需要在项目的build.gradle中添加相应的依赖库

image-20201004130717161

2.在布局中引用

image-20201004130855069

3.准备Fruit类、fruit_item.xml、

4.为RecycleView准备一个适配器FruitAdapter,继承自RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder

image-20201004132129131

5.在活动中使用RecyclerView

image-20201004160217803

二、实现横向滚动和瀑布流布局

1.横向滚动

layoutManager.setOrientation(LineatLayoutManager.HORIZONTAL);

2.瀑布流布局 StaggeredGridLayoutManager

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager (3, StaggeredGridLayoutManager.VERTICAL);
recycleView.setLayoutManager(layoutManager);

​ 小技巧:设置不同长度文本

image-20201004161014946

三、RecyclerView的点击事件

image-20201004161141203

第四章 探究碎片

碎片是什么

碎片的使用方式

一、静态使用Fragment

1. 这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中。

2.实现步骤

(1)创建碎片布局文件 fragment_master.xml
image-20201013153418988
(2)创建碎片类MasterFragment.Java

​ 使用inflater.inflate适配布局。实际应用中,也可以添加动态获取信息,绑定到布局控件中。

image-20201013153554997

​ !补充:(1)(2)也可以通过向导新建来实现

(3)在布局文件中直接用创建好的Fragment控件

​ 要通过android:name 属性来显示的指明要添加的碎片类名

image-20201013153856622

二、动态添加Fragment

1.程序运行时根据具体情况,动态地添加Fragment到Activity活动中。

2.Fragment事务

(1)对Fragment进行添加、移除、替换或执行其他动作,即提交给Activity的每一个变化。
    (2)Android提供了<font color='pink'>FragmentManager类</font>管理Fragment:
    在Activity运行过程中,通过FragmentManager开启事务,通过调用<font color='red'>add(),remove(), replace()</font>实        现动态添加、删除、替换Fragement。
    FragmentManager使用<font color='pink'>FragmentTransaction类</font>来管理事务

3.实现步骤

(1)在上例基础上,新建一个碎片布局 fragment_another.xml及其类AnotherFragment.java
(2)修改MainActivity的布局文件acitivty_main.xml:

​ 将右侧碎片替换成一个FrameLayout布局。
动态加载碎片时需要指定容器,一般使用FrameLayout。
​ 随后可在代码中向FrameLayout中动态地添加碎片,在第一次运行时加载DetailFragement,单击按 钮后加载AnotherFragment

(3)在代码证动态加载碎片控件
image-20201013155307418

​ 或者

image-20201013155506638 image-20201013155515431

image-20201013160047336

4.在碎片中模拟返回栈

addToBackStack()用于将一个事务添加到返回栈

5.碎片和活动之间进行通信

(1)在活动中获取碎片实例:

FramengtManager fm = getSupportFramengtManager();
DetailFragment frag = (DetailFragment)fm.findFragmentById(R.id.fragment_detail);

(2)在碎片中访问活动中的方法:

在碎片中直接通过调用getActivity()方法获得和当前碎片相关联的活动实例,例如:
MainActivity activity = (MainActivity)getActivity();

碎片的生命周期

一、碎片的状态

1.运行状态:

当嵌入该Fragment的Activity是处于运行状态的,并且该Fragment是可见的,那么该Fragment是处于运行状态的。

2.暂停状态:

当嵌入该Fragment的Activity是处于暂停状态时,那么该Fragment也是处于暂停状态的。

3.停止状态:

当嵌入该Fragment的Activity是处于停止状态时,那么该Fragment也会进入停止状态。
或者通过调用FragmentTransation的remove()、replace()方法将Fragment从Activity中移除,但如果在事务提交前调用addToBackStack()方法添加到返回栈,这时的碎片进入到停止状态。进入停止状态的碎片有可能被系统回收。但在回收前如果又被置于前台(从返回栈中返回),将不需要被创建。

4.销毁状态:

当嵌入该Fragment的Activity是被销毁时,该Fragment进入到销毁状态。
或者通过调用FragmentTransation的remove()、replace()方法将Fragment从Activity中移除,但在事务提交前没有调用addToBackStack()方法添加到返回栈,这时的碎片进行到销毁状态。

image-20201014194148649image-20201014194253899

image-20201014194329244

限定符

layout - large 文件夹

layout - sw600dp文件夹

简易版的新闻应用

一、准备一个新闻实体类 News.java

image-20201014194855870

二、接着新建布局文件news_content_frag.xml,用于作为内容碎片类的布局

image-20201014195315804

三、然后再新建一个内容碎片类NewsContentFragment,继承自Fragment

image-20201014195546698

image-20201014195955104

四、这样我们就把新闻内容的碎片和布局都创建好了,但是它们都是在双页模式中使用的,如果想在单页模式中使用的话,我们还需要再创建一个活动。右击 com.example.fragmentbestpractice

包→New→Activity-→Empty Activity,新建一个NewsContentActivity,并将布局名指定成
news_content,然后修改news_content.xml中的代码,如下所示:

image-20201014200437146

五、然后修改NewsContentActivity中的代码,如下所示:

image-20201014201305152

image-20201014201739780

六、接下来还需要再创建一个用于显示新闻列表碎片类的布局,新建news title frag.xml

image-20201014202002701

七、新建news_item.xml作为RecyclerView子项的布局

image-20201014202257283

image-20201014202303375

八、既然新闻列表和子项的布局都已经创建好了,那么接下来我们就需要一个用于展示新闻列表的地方。这里新建NewsTitleFragment作为新闻列表的碎片类,代码如下所示:

image-20201014203519438

九、创建两个模式的主活动布局

image-20201014203644781

image-20201014204051184

十、在NewsTitleFragment中通过RecyclerView将新闻列表展示出来。我们在NewsTitleFragment中新建一个内部类NewsAdapter来作为RecyclerView的适配器,如下所示:

(相当于之前在主活动中创建“适配器内部类”的情况,只不过这里在列表碎片类中添加列表,创建适配器,再然后在主活动布局中使用列表碎片类控件)

image-20201014205746854 image-20201014210158025 image-20201014210210198

十一、现在还剩最后一步收尾工作,就是向RecyclerView中填充数据了。修改NewsTitle-Fragment中的代码,如下所示:

image-20201014210535588 image-20201014210606503

第五章 全局大喇叭——详解广播机制

广播机制简介

image-20201117133937067

image-20201117134653797

一、一种传输信息的机制

广播消息可以是应用程序的数据信息,也可以是Android的系统消息,比如网络连接变化、电池电量变化、接收到的短信信息或系统设置的变化等。

Android中的每个应用程序都可以对自己感兴趣的广播进行注册,便于接收广播内容。

接收广播需要专门的广播接收器(Broadcast Reciever)。

广播接收器通过设置好的过滤器监听特定的广播消息然后进行响应。

二、广播的类型

1.标准广播

​ 异步执行。在广播发出之后,所有的广播接收器都会在同一时刻接收到这条广播消息。

2.有序广播

同步执行。在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。
广播接收器是有先后顺序,优先级高的广播接收器就可以先收到广播消息。
优先级高的广播接收器可以截断正在传递的广播,因此优先级低的广播接收器将无法收到广播信息。

BroadcastReceiver介绍

一、BroadcastReceiver

1.本质上是一种全局监听器,用于监听系统全局的广播消息,因此它可以非常方便地实现系统中不同组件之间的通信。

2. BroadcastReceiver用于接收指定的广播,通过设置过滤器监听感兴趣的广播消息后进行响应,例如:

启动Activity作为响应,或者通过NotificationManager提醒用户,或者启动Service等等。
自身并不实现图形用户界面。

二、Broadcast开发过程

1.定义广播接收器

继承BroadcastReceiver基类,实现onReceiver方法:

image-20201117135128320

image-20201117135631266

2.注册广播接收器

(1)静态注册

image-20201117135302391

image-20201117140345059

(2)动态注册:(可定义为内部类)

image-20201117135729313

image-20201117140330496

3.BroadcastReceiver响应

(1)注册完成后,即可接收相应的广播消息。一旦广播(Broadcast)事件发生后,系统就会创建对应的BroadcastReceiver实例,并自动触发它的onReceive()方法,onReceive()方法执行完后,BroadcastReceiver的实例就会被销毁
(2) 如果BroadcastReceiver的onReceive()方法不能在10秒内执行完成,Android会认为该程序无响应。所以不要在广播接收者的onReceive()方法里执行一些耗时的操作,否则会弹出ANR(Application No Response)对话框。

发送自定义广播

一、标准广播:

1.是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高。但缺点是接收者不能将处理结果传递给下一个接收者,并且无法终止Broadcast Intent的传播。

2. Intent intent = new Intent(“com.example.broadcasttest.MY_BROADCAST”);

sendBroadcast(intent);

二、有序广播:

1.该广播的接收者将按预先声明的优先级依次接收广播。有序广播接收者可以终止广播的传播(通过调用abortBroadcast()方法),广播的传播一旦终止,后面的接收者就无法接收到广播。另外,广播的接收者可以将数据传递给下一个接收者(通过setResultExtras(Bundle bundle)方法)。

2. Intent intent=new Intent(“iet.jxufe.cn.android.OrderedBroadcastTest”);

        <font color='red'>endOrderedBroadcast(intent,null);</font>
    3.优先级设置

image-20201117140951277

使用本地广播

一、Android本地广播机制:

广播只能在应用程序内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播

方法:使用LocalBroadcastManager对广播进行管理,对发送广播和注册广播接收器进行管理。

image-20201117141401456

第六章 数据存储全方案–持久化技术

持久化技术简介

1.数据存储方式

  • 文件存储
  • SharedPreferences
  • SQLite数据库

2.Android内部存储和外部存储

(1)内存(Memory):

​ RAM,类似于PC的内存条

(2)内部存储(Internal Storage):

​ 机身固有存储,类似于PC的硬盘

(3)外部存储(External Storage):

​ 早期的Android设备(4.4之前) 指SDCard(扩展卡),类似于PC上的U盘。

​ 之后随着技术的进步,机身存储得到了快速增大(8G以上),此时的外部存储是概念级别的“外部存储”,机 身固有的存储有一部分被划分为“内部存储”,另一部分被划分为“外部存储”,但其实都是机身固有存储。 后期的外部存储也包括了SDCard。
​ 所以不管Android手机是否有可移动的SDCard,他们总是有外部存储和内部存储。

3.几个重要文件夹

打开DDMS(Dalvik Debug Monitor Service),有一个Device File Explorer:data文件夹、mnt文件夹、sdcard 文件夹、storage文件夹

4.内部存储data

(1)data文件夹就是我们常说的内部存储,打开data文件夹,有两个文件夹:

  • 文件夹app:存放着我们所有安装的app的apk文件
  • 文件夹data:一些包名,展开后,

    data/data/包名/shared_prefs(SharedPreference存储方式)

    data/data/包名/<font color='orange'>databases</font>(数据库存储方式)
    data/data/包名/<font color='orange'>files</font>(文件存储方式)
    data/data/包名/<font color='orange'>cache</font>
    

(2)路径的访问:Environment.getDataDirectory()获取内部存储根路径。/data

(3)几点:

  1. 系统默认只在内部存储中创建cache目录,并且此时cache目录是空的

  2. 此外,还可以根据需要自动创建:shared_prefs,databases,files目录

    但files可以手动创建,而shared_prefs,databases不能

  3. 别的App几乎无法访问内部存储中的数据(都是私有的),除了用非法手段或者我们主动暴露内部存储目录下的文件夹,文件会随着App的卸载而被系统自动删除。

(4)内部存储的API

image-20201028153632199

5.外部存储storage

(1)一般是/storage/emulated/0也有可能是其它文件夹,不同厂家不同版本有可能不一样。

/storage/sdcard0,/sdcard,/mnt/sdcard,/storage/emulated/0,/storage/emulated/legacy

  • 公有目录:10大类,比如DCIM,Download等这些系统创建的文件夹。storage/emulated/0/Download
  • 私有目录:Android文件夹,打开后有data文件夹。storage/emulated/0/Android/data/包名 文件夹:里面包含了许多包名组成的文件夹。

    (2)Google官方建议我们App的数据应该存储在外部存储的私有目录(data)中该App的包名下,这样当用户卸载掉App之后,相关的数据会一并删除

    如果直接在根目录/storage/emulated/0)下创建一个应用的文件夹,这样当该应用被卸载后,这些数据还保留在外部存储中,留下垃圾数据

(3)路径的访问:Environment.getExternalStorageDirectory():获取机身存储的外部存储根路径。

(4)默认情况下,Android系统不会为我们的App在外部存储中创建私有目录,需要手动创建私有目录

context.getExternalFilesDir(…):在storage/emulated/0/Android/data/包名/files 中创建文件

​ context.getExternalCacheDir() :在storage/emulated/0/Android/data/包名/cache 中创建文件

image-20201028154738508

6.内部存储与外部存储比较

​ 外部存储目录空间较大,而内部存储空间有限
​ 部分目录不会被自动创建,需要手动创建
​ 两者都不需要权限
​ 两者都会随着App的卸载而会自动被删除
​ 对于内部存储,只有本App才可以访问
​ 对于外部私有目录,本App可以直接访问,而其它App在自 Android 7.0 开始,系统对应用私有目录的访问权限进一步限制。其他 App 无法通过 file:// 这种形式的 Uri 直接读写该目录下的文件内容,而是通过 FileProvider 访问。
​ 需要注意的是:由于用户可以直接查看并操作外部私有存储目录,那么也就意味着我们在操作这个目录下的文件的时候一定要做好异常和判空处理。

7.数据存储与清除

(1)数据存储:

  • 默认情况下,存储在内部存储
    data/app:用户APP安装目录,存放apk;

    data/data/包名/:存放应用程序的数据

  • 数据可以存放到指定的外部存储

    /storage/sdcard(共有目录)

    /storage/sdcard0/Android/data/包名/(私有目录)

    (2)清除数据:

删除对应包中的cache,files,lib,shared_prefs,datbBases等等

​ (清除data/data/packagename中的cache,files,lib, databases,shared_prefs等等)

​ (清除storage/emulated/0/Android/data/packagenam中的文件)

(3) 清除缓存:

删除的是App运行过程中所产生的临时数据,比如读入程序,计算,输入输出等等,这些过程中肯定会产生很多的数据,它们在内存中。

文件存储

1.内部存储

(1)存:

方法1:使用 context.getFilesDir() 获取内部存储data/data/包名/files目录,然后创建文件,最后利用文件(全路径)新建FileOutputStream对象进行文件写入。
FileOutputStream fileOutputStream = new FileOutputStream(filePathName);

image-20201028160019136

方法2:直接使用Context.openFileOutput()方法(只需要文件名)获取FileOutputStream对象,进行文件写入。

image-20201028160258040 image-20201028160348975

(2)取:

方法1:使用context.getFilesDir() 获取内部存储data/data/包名/files目录,然后利用文件(全路径)创建FileInputStream对象,进行内容读取。
FileInputStream fileInputStream = new FileInputStream(filePathName);

image-20201028160834999

方法2:直接使用Context. openFileInput()方法(只需要文件名)获取FileInputStream对象,进行文件内容读取。
FileInputStream fileInputStream = openFileInput(fileName);

image-20201028160853113

image-20210531224957336

2.外部存储

(1)外部根目录:

使用 Environment.getExternalStorageDirectory() 获取

一般是/storage/emulated/0

(2)外部公有目录:

使用Environment.getExternalStoragePublicDirectory获取

/storage/emulated/0/Music

针对外部共有目录、根目录等位置的存取需要添加权限,Android 6.0 之后还需要做运行时权限申请。针对外部私有目录的存取则不需要添加权限
默认情况下,Android系统不会为我们的App在外部存储中创建私有路径,需要手动创建
外部私有目录在App卸载时同步删除。

(3)获取外部私有目录

image-20201028162002693

(4)存:

image-20201028162420940

(5)取:

image-20201028162532386

SharedPreferences存储

1.获取SharedPreferences对象

(1) Context . getSharedPreferences()方法 

​ 此方法接受两个参数,第一个参数用于指定 SharedPreferences 文件的名称,如果指定的文件不存在则会创建一个。SharedPreferences 文件都是存放在 /data/data/<包名>/shared_prefs/ 目录下的。
​ 第二个参数用于指定操作模式,主要为 MODE_PRIVATE(默认操作模式) 和 MODE_MULTI_PROCESS(多进程读写) 。

(2)Activity 类中的 getPreferences() 方法

这个方法和 Context 中的 getSharedPreferences() 方法类似,不过它只接收一个操作模式参数,使用这个方法时会自动将当前活动的类名作为 SharedPreferences 的文件名

(3) PreferenceManager 类中的 getDefaultSharedPreferences() 方法

这是一个静态方法,它接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件。

2.通过SharedPreferences对象获取到Editor对象存数据

(1)SharePreferences.Editor editor = shared.edit();

调用 SharedPreferences 对象的 edit() 方法来获取一个 SharePreferences.Editor 对象。

(2)editor.putString( )

向 SharedPreferences.Editor 对象中添加数据,如果添加一个字符串则使用 editor.putString() 方法,以此类推。

(3)editor.commit() editor.apply()

调用 editor.commit() 方法或者editor.apply()方法将添加的数据提交,从而完成数据存储操作。

image-20201028164007127

image-20201028164138241

3.SharePreferences读取与删除数据

image-20201028164251857

image-20201028164334426

SQLite数据库存储

一、创建数据库

1. SQLiteOpenHelper 帮助类

  • 重写onCreate() 和 onUpgrade() 方法,分别在这两个方法实现创建、更新数据库的逻辑;

  • 两个重要的实例方法:getReadableDatabase() 和 getWritableDatabese() ,这两个方法都可以创建或打开一个现有数据库(不存在则创建),并且返回一个可对数据库进行读/写操作的SQLiteDatabase对象

  • SQLiteOpenHelper构造方法(context, name, factory, version)

    ​ 参数一:Context; 参数二:数据库名; 参数三:Cursor游标(null) 参数四:版本号

    2. 新建 MyDatabaseHelper 继承自SQLiteOpenHelper

    重写oncreate()

image-20201031132951195

image-20201031133055726

3. 创建数据库

image-20201031133544744

二、升级数据库

1.在初始化时新执行一条SQL语句,新建一个Category表格

image-20201031134002931

2.重写onUpgrade()

image-20201031134120838

​ 升级时,如果表格存在,先删除掉再调用onCreate()重写创建

3. 更新数据库

image-20201031134454199

三、添加数据 insert()

1.参数

  • 参数一:表名
  • 参数二:用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可
  • 参数三:ContentValues对象,它提供了一系列的put()方法重载,用于向Contentvalues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。

2.添加数据

image-20201031140101787

四、更新数据 updata()

1.参数

  • 参数一:表名
  • 参数二:ContentValues对象,组装更新的数据
  • 参数三、参数四:约束更新某一行或某几行的数据

2.更新数据

image-20201031140634673

更新Book表格中,name为“The Da Vinci Code” 的价格为10.99

五、删除数据 delete()

1.参数

  • 参数一:表名
  • 参数二、参数三:约束删除某一行或某几行的数据

2.删除数据

image-20201031141159367

删除Book表格中,Pages大于500页的数据

六、查询数据 query()

1.参数

image-20201031141347771

2.查询数据

image-20201031141717750

通过返回的Cursor对象来获取数据,moveToFirst():将游标移动到第一行位置; getColunmIndex():获取某一列的位置索引

第七章 跨程序共享数据——探究内容提供器

内容提供器简介

运行时权限

一、危险权限

image-20201109144301759

危险权限需要进行运行时权限处理,普通权限只要在AndroidMainfest.xml清单文件中添加一下权限声明就行

二、运行时权限处理步骤

1. 第一步:判断用户是否已经授权

(1)ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)
(2)参数一:Context; 参数二:权限名
(3)将其返回值与PackageManager.PERMISSION_GRANTED做比较,如果相等,说明用户已经授权,不相等表示用户没有授权。

2.第二步:如果未授权,申请授权

(1)ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission. READ_CONTACTS }, 1);
(2)参数一:Activity的实例; 参数二:权限名的String数组;
权限三:请求码,唯一即可
(3)调用该方法后,系统会弹出一个权限申请的对话框,用户可以选择同意或者拒绝我们的权限申请。用户做出选择后将回调到onRequestPermissionsResult()方法,对授权结果进行处理。

3.第三步:处理用户授权结果

(1) onRequestPermissionsResult()方法中,用户授权的结果封装在grantResults参数当中
(2)grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED说明用户允许授权

image-20201111130523831

image-20201111130532512

访问其他程序中的数据

一、应用程序间的数据共享

1.三个重要类

(1)ContentProvider:内容提供者。

内容提供者的用法一般有两种:
一种是使用现有的内容提供器来读取和操作相应程序中的数据。
另一种是创建自己的内容提供器对其数据提供外部访问接口。

(2)ContentResolver:内容访问者

提供了一系列的方法对ContentProvider中共享的数据进行CRUD操作。

(3)Uri

不同于SQLiteDatabase, ContentResolver中增删改查方法不接收表名参数,而是使用一个Uri参数代替,这个参数称为内容Uri。

格式:scheme:/ /Authority / path / id

  • scheme: content://是一个标准的前缀,表明了这个数据被内容提供者管理,它不会修改。
  • authority:用于唯一标识一个ContentProvider,外部调用者可以根据此标识访问该ContentProvider。通常可将Authority设置为包名和类名的全称,以保证唯一性。例如包名是com.example.app,则authority可以命名为com.example.app.provider。
  • path:用于对同一应用程序的不同的数据进行区分,通常添加到authority的后面,例如/table1, /table2。
  • id:数据集中的每一条记录都有一个唯一的id。如果Uri中包含需要获取的记录的id,则只对该记录进行操作;如果Uri中没有id,则表示操作数据集中的所有记录。
  • image-20201111132317186

2.使用ContentResolver访问共享数据

ContentResolver提供了一系列的方法对ContentProvider中共享的数据进行CRUD操作

image-20201111132443856

image-20201111132412264

3. 查询(获取)内容提供者中指定的数据

(1)首先,确定访问目标
Uri uri = Uri.parse(“content://com.example.app.provider/table1”);

(2)然后,查询:
获取ContentResolver,将Uri对象作为参数,调用query()方法得到一个cursor对象

Cursor cursor = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);

image-20201111132642375

(3)最后,结果处理:对返回的Cursor对象进行处理,将数据从cursor对象中逐个读取出来。

image-20201111133026107

4. 向内容提供者共享的table1中添加数据

将待添加的数据组装到ContentValues,然后调用ContentResolver类insert()方法,将uri和ContentValues作为参数传入。

image-20201111133121157

5. 向内容提供者共享的table1中更新数据

image-20201111133151906

6.向内容提供者共享的table1中删除数据

image-20201111133224468

创建自己的内容提供器*(P260)

一、重写其中6个抽象方法

1.public boolean onCreate():ContentProvider创建时调用。

2. public int delete(…):根据传入的Uri删除指定条件下的数据。

3.public Uri insert(…):根据传入的Uri插入数据。

4. public Cursor query(…):根据传入的Uri查询指定的数据。

5.public int update(…):根据传入的Uri更新指定的数据。

6.getType()

二、Uri参数:确定调用方希望访问的数据或表

表:content://com.example.app.privder/table1/

数据:content://com.example.app.privder/table1/#

三、通过UriMatcher类解析Uri参数,确定调用的数据或表

首先将需要共享的数据或数据表的内容URI用addURI()方法添加到自定义的内容提供器中

然后,在query()方法中调用UriMatcher的match方法对传入的Uri对象进行解析,返回某个能匹配这个Uri对象的自定义代码,利用这个代码,自定义的内容提供器就可以判断出调用方期望访问的是哪张表中的数据了。

image-20201117132442004

四、getType()方法

一个内容URI所对应的MIME字符串由3部分组成:

  • 以vnd开头
  • 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/
  • 最后接上vnd..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String getType(Uri uri) {//教材265页
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
}
return null;
}

内容观察者ContentObersver

一、ContentObserver(内容观察者)

1.目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。

2.触发器分为表触发器、行触发器,相应地ContentObserver也分为“表”ContentObserver、“行”ContentObserver,当然这是与它所监听的Uri MIME Type有关的。

image-20201117133208124

3.当A应用程序的数据发生变化时,A应用程序调用notifyChange()方法向消息中心发送消息,然后C应用程序观察到数据变化时,就会触发ContentObserver的onChange()方法。

二、ContentObserver的几个常用的方法

1.构造方法 public void ContentObserver(Handler handler)

​ 所有ContentObserver的派生类都需要调用该构造方法.
参数:handler,Handler对象。可以是主线程Handler(这时候可以更新UI ),也可以是任何Handler对象。

2.void onChange(boolean selfChange)

​ 观察到的Uri发生变化时,回调该方法去处理。
所有ContentObserver的派生类都需要重载该方法去处理逻辑。

第八章 丰富你的程序——运用手机多媒体

播放多媒体文件

一、播放音频

1.MediaPlayer

image-20201124130455169

2.工作流程

  • 创建MediaPlayer对象;
  • 初始化设置音频文件路径:setDataSource()
  • 使MediaPlayer进入准备状态:pepare()
  • 开始播放音频:start()
  • 暂停播放音频:pause()
  • 停止播放音频:reset(), setDataSource(), pepare()

第九章 看看精彩的技术——使用网络技术

Android常见的网络通信方式

一、基于TCP协议

(1)针对TCP的 Socket、ServerSocket
(2)针对UDP的DatagramSocket、DatagramPackage
(3)Apache Mina框架

二、基于Http协议

HttpURLConnection、HttpClient、AsyncHttpClient框架等

三、直接使用 WebKit 访问网络

四、使用网络通信框架

OkHttp,Volley,Retrofit

网络编程基础

一、通信协议

1.计算机网络实现通信必须遵循守一些约定。核心要素包括:

(1)语义:用于决定双方对话的类型,即规定通信双方要发出何种控制信息、完成何种动作及作出何种应答;
(2)语法:用于决定双方对话的格式,即规定数据与控制信息的结构和格式;
(3)时序:用于决定通信双方的实现顺序,即确定通信状态的变化和过程,如通信双发的应答关系。

2.常见的通信协议

  • TCP/IP, IPX/SPX, NetBEUI, RS-232-C等
  • TCP/IP 是最基本的通信协议。
  • TCP/IP是一组协议的集合,是一组协议集合的简称。

二、TCP/IP 协议集

image-20201214220514420

1.以QQ数据传输为例

image-20201214220704145

2.TCP与UDP,传输层

1、TCP面向连接(如打电话要先拨号建立连接); UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达; UDP尽最大努力交付,即不保证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
4、每一条TCP连接只能是点到点的; UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节。
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

3.HTTP,应用层

(1)Http是一个基于请求与响应,无状态的,应用层的协议,常基于TCP协议传输数据。
(2)用于定义Web浏览器与Web服务器之间交换数据的过程。
(3)四个基于:请求与响应、无状态、应用层、TCP

基于TCP协议的网络通信

一、IP地址、端口

1.IP地址

IP地址用来标记唯一的计算机位置,IP地址是32为二进制,例如:192.168.0.0.1。

2.端口

端口号用来标记一台电脑中的不同应用程序。
端口号范围是0-65535,其中0~1023是系统专用

二、Socket编程

基于Http协议的网络通信

一、HTTP协议工作原理

1.建立连接

客户机与服务器建立连接。输入网址、打开网页或单击超级链接。

2.发送请求

客户机向服务器发送请求,格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。

3.响应请求

服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。

4.接收响应

客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后与服务器断开连接。

二、HTTP请求报文

image-20201215125047712

image-20201215124600803

1.请求行

(1)请求方法

HTTP/1.1 定义的请求方法有8种,最常的两种GET和POST。

(2)请求地址

URL:统一资源定位符,是一种资源位置的抽象唯一识别方法。
组成:<协议>://<主机>:<端口>/<路径>
端口和路径有时可以省略(HTTP默认端口号是80),有时会带参数,GET请求

(3)版本协议

格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

2.请求头部

(1)由键值对组成,每行一对,关键字和值用英文冒号“:”分隔。
(2)请求头部通知服务器关于客户端请求的信息,典型的请求头有:

image-20201215125315165

(3)请求头部的最后会有一个空行,表示请求头部结束,接下来为请求数据,这一行非常重要,必不可少。

3.请求数据

(1)可选部分,比如GET请求就没有请求数据。
(2)HTTP的请求方式包括GET,POST, PUT ,HEAD, DELETE, TRACE, CONNECT, OPTIONS等,最常用的发送请求的方式主要有 get 和 post两种

4. GET与POST

(1)get

image-20201215130633494

(2)post

image-20201216111436244

(3)注意
  • GET 和 POST 两种请求方式都可以发送数据的,只是发送机制font>不一样。
  • 另外GET安全性非常低,Post安全性较高, 但是GET执行效率却比Post方法好。
  • 一般查询的时候我们用GET,数据增删改的时候用POST
(4)其他区别

image-20201216111723498

三、HTTP响应报文

image-20201216111852290

image-20201216111859563

1.状态行

(1)由3部分组成,分别为:协议版本,状态码,状态码描述)
(2)其中协议版本与请求报文一致,状态码描述是对状态码的简单描述
(3)常见状态码

image-20201216112057996

2.响应头部

image-20201216112237974

image-20201216112254577

四、基于HTTP的网络编程

(HyperText Transfer protocol)超文本传输协议

1.通过URL获取网络资源

(1)URL(Unifrom Resource Locator)统一资源定位器

可以定位到互联网的资源上。如果用户已经知道网络上某个资源的URL(如图片、音乐和视频文件等),那么就可以直接通过使用URL来进行网络连接,获得资源

(2)资源获取过程:
  1. 创建URL对象。
  2. 调用常用的方法来获取对应的资源。例如,使用openStream()方法,打开与此URL的连接,并返回读取到的数据流。
  3. 将获得的数据流进行处理。例如,显示到ImageView上。

说明:openStream() 实际是通过openConnection()方法获取URLConnection对象,然后调用getInputStream()方法,这个方法会隐式的调用connect() 方法发送连接请求。

URL url = new URL( “ …”);

HttpURLConnection connection = (HttpURLConnection) url.openConnection();

InputStream in = connection.getInputStream();

等加于

URL url = new URL( “ …”);

InputStream in = url.openStream();

(3)URL类常用的方法
image-20201216113912830
(4)案例:图片资源获取并显示

1.布局文件:新建项目URLDemo,在布局文件中,准备一个ImageView控件。

2.初始化并准备异步任务类。

image-20201216114237925

image-20201216114246080

3.获取网络资源:在doInBackground()方法中执行联网以获取网络资源,获取到的图片信息作为返回结果,传给onPostExecute()中的参数

image-20201216120334545

4.资源显示:在onPostExecute()方法中进行图片显示。

image-20201216120421728

5.增加上网权限。

<uses-permission android:”android.permission.INTERNET”/>

2.通过URLConnection获取网络资源

(1)URL 对象提供了openConnection()方法返回一个URLConnection对象。通过URLConnection,建立应用程序和URL之间的连接,借助于该URLConnection桥梁,可以向URL发送请求,读取URL资源。
(2)实现步骤
  1. 创建URL对象
  2. 建立与URL的连接:由于URLConnection为抽象类,其对象不能直接实例化,通常通过openConnection方法获得。
  3. 获取返回的InputStream
  4. 将InputStream进行处理:例如,显示到相应的控件上。
  5. 关闭流操作

image-20201217124051779

(3)URLConnection常用方法
方法 功能描述
public int getContentLength() 获得文件的长度
public String getContentType() 获得文件的类型
public long getDate() 获得文件创建的时间
public long getLastModified() 获得文件最后修改的时间
public InputStream getInputStream() 获得输入流,以便读取文件的数据
public OutputStream getOutputStream() 获得输出流,以便输出数据
public void setRequestProperty(String key,String value) 设置请求属性值
connect() 打开到此URL引用的资源的通信连接(如果尚未建立连接),如果已打开连接(此时connected()为true),则忽略该调用

3.使用HttpURLConnection获取网络资源

(1)HttpURLConnection常用方法

HttpURLConnection是URLConnection的子类

方法 功能描述
InputStream getInputStream() 返回从此处打开的连接读取的输入流
OutputStream getOutputStream() 返回写入到此连接的输出流
String getRequestMethod() 获取请求方法
int getResponseCode() 获取状态码
void setRequestMethod(String method) 设置URL请求的方法
void setDoInput(boolean doinput) 设置输入流
void setDoOutput(boolean dooutput) 设置输出流
void setUseCaches(boolean usecaches) 设置连接是否使用任何可用的缓存
void disconnect() 关闭连接
(2)GET方式:将参数放在url后一起传递

image-20201217125144443

(2)POST方法

image-20201217125444529

image-20201217125635416

使用WebView控件

1.Android内置webkit内核的高性能浏览器,而WebView则是在这个基础上进行封装后的一个 控件

2.WebView(网页视图)常用方法

方法 功能描述
loadUrl(String url) 打开一个指定的Web资源页面
loadData(String data, StringmimeType,String encoding) 显示HTML格式的网页内容
getSettings() 获取WebView的设置对象
addJavascriptInterface() 将一个对象添加到JavaScript的全局对象Window中
clearCache() 清除缓存
destory() 销毁WebView

3.在应用中嵌入浏览器展示网页,并进行解析

image-20201217130452896

image-20201217130553473

使用OKHttp获取网络资源

1.GET请求

(1)添加依赖

implementation ‘com.squareup.okhttp3:okhttp:3.10.0’

(2)声明访问Internet的权限

在application标签中添加 android:usesCleartextTraffic=”true”

(3)创建 OkHttpClient实例

OkHttpClient okHttpClient = new OkHttpClient();

(4)构造Request对象。

final Request request = new Request.Builder() .url(url).get() //默认

就是GET请求,可以不写 .build();

(5)构建Call对象

Call call = okHttpClient.newCall(request);

(6) 发送请求并获取服务器返回的数据。

  • 同步Get方法:
通过Call.execute()方法提交请求,放在子线程中执行,或者使用异步任务的方式

image-20201217131925515

  • 异步Get方法:
通过Call.enqueue(Callback)方法来提交异步请求

image-20201217132051946

2.POST请求

(1)与上述GET请求相比,需要先构造一个RequestBody对象用来存放待提交的数据,例如:

image-20201217132351988

(2)然后在Request.Builder中调用一下post()方法,并将RequestBody对象传入:

image-20201217132423939

(3)登录案例

image-20201217133135117

image-20201217133213142

image-20201217133219867

JSON

一、JSON数据

1.JSON(JavaScript Object Notation) 是JavaScript的一个子集,是一种轻量级的数据交换格式

2.JSON的结构基于以下两点:

(1)”名称/值”对的集合 :不同语言中,它被理解为对象(object),记录(record),结构(struct),字典(dictionary),哈希表(hash table),键列表(keyed list)等。
(2)值的有序列表: 多数语言中被理解为数组(array)。

3.JSON的形式主要有以下两种:

(1)对象

image-20201219093521448

(2)数组

image-20201219093539871

二、使用JSONObject解析JSON数据

1. 案例:CityCodeDemo

通过Spinner控件,读取中国的城市名称,并通过选择获得该城市对应的编码值。

(1)准备JSON数据,创建assets目录

image-20201219093921547

image-20201219094342080

(2)布局文件:分别准备id为cityCode和cityList的TextView和Spinner控件,以便Activity调用。
(3)启动异步任务,进行文件读取

image-20201219094525406

image-20201219095314825

(4)定义实体类。装载JSON的解析结果

image-20201219094831461

(5)解析JSON内容。将字符串转换为对应的CityCode实体对象列表

image-20201219095531426

(6)绑定界面控件。将解析得到的List通过适配器,绑定到spinner控件上,并设置子项监听

image-20201219100105438

image-20201219100112597

2.安卓的资源文件assets

(1)Android资源文件的存放位置有三种:
  • res目录下存放的可编译的资源文件
  • assets目录下存放的原生资源文件
  • res/raw目录下存放的原生资源文件
(2)assets与res/raw相同之处
  1. 都可以用于放置APP所需的固定文件
  2. 该文件被打包到APK中时,不会被编码到二进制文件
(3)assets与res/raw不同之处
  1. assets目录不会被映射到R中,因此,资源无法通过R.id方式获取,必须要通过AssetManager进行操作与获取;res/raw目录下的资源会被映射到R中,可以通过getResource()方法获取资源。
  2. 多级目录:assets下可以有多级目录,res/raw下不可以有多级目录。

三、使用Goole Gson解析JSON数据

1.Gson概述

(1)Gson的解析非常简单:必须有一个JavaBean文件,这个JavaBean文件的内容跟JSON数据类型是一一对应的。
(2)Gson有两个重要的方法

toJson():序列化Java对象成JSON字符串

fromJson():反序列化对象成Java对象

2.解析Json数据

(1)准备一个Person类

image-20201219101538843

(2))toJson()方法用于将bean对象换为Json数据

image-20201219101558372

(3)fromJson()方法用于将Json数据转换为bean对象

image-20201219101620318

网络通信框架Volley

一、Volley概述

1.Volley特别适合数据量不大但通信频繁的场景

2.不使用Volley,从网上下载资源的步骤大致如下:

(1)首先,在AsyncTask的doInBackground()中,使用HttpURLConnection从服务器获取相关资源。
(2)然后,将下载的文字或图片资源在AsyncTask类的onPostExecute()里设置到相应的控件中。
而在Volley下,上述只需要一个函数就可以完成上述步骤

二、Volley的使用步骤

1.声明RequestQueue

2.为了获得请求的响应,我们需要根据响应的结果,调用不同的Request对象

image-20201219102328576

三、通过Volley获取字符串

image-20201219102423614

image-20201219102436859

四、通过Volley获取JSON数据

image-20201219102603231

五、通过Volley获取图片资源

1.ImageRequest

image-20201219102644623

2.ImageLoader

image-20201219102720895

image-20201219102741316

image-20201219102801828

第十章 后台默默劳动者——服务

Android多线程编程

一、线程概述

1.Android系统中,App运行后默认创建一个线程,即主线程

​ ——Activity、Service和BroadcastReceiver都是工作在主线程上。

2.主线程负责处理与UI相关的事件,并把相关的事件分发到对应的组件进行处理,通常又被叫做UI线程

 主线程中任何耗时的操作都会的降低用户界面的响应速度,甚至导致用户界面失去响应。

例如,发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求

​ 较好的解决方法是将耗时的处理过程转移到子线程上,这样可以避免负责界面更新的主线程(UI线程)无法处理界面事件,从而避免用户界面长时间失去响应。

二、主线程和子线程

1.主线程

  • UI线程,负责处理与UI相关的事件,并把事件分发到对应的组件进行处理。

  • 应用首次启动时,Android会启动一个Linux进程和一个主线程。

  • Android UI操作必须在UI线程中执行。由于Android的UI是单线程(Single-threaded)的,当其任务繁重时,则需要其他线程来进行配合工作。

2.子线程

  • 非UI线程即为子线程,子线程一般都是后台线程
  • 运用子线程的场合:进行数据、系统等其他非UI的操作或者把所有运行慢的、耗时的操作移出主线程,放到子线程中。
  • 通常,子线程需要开发人员对其进行定义、启动、终止等操作控制。

三、同步与异步

1.同步:需要等待放回结果

异步:不需要等待返回结果

2.无论同步异步,如果事件处理可能比较耗时,就需要放到其他线程中处理。如果处理的结果需要刷新界面,那么需要线程间通讯的方法来实现在其他线程中发消息给主线程处理(更新UI)。

四、线程间通讯

1) Activity.runOnUiThread(Runnable)

2) View.post(Runnable) ;View.postDelay(Runnable , long)

3) Handler

post, postDelay

sendMessage, handleMessage

4) AsyncTask

5) 广播

五、线程的状态和生命周期

  1. 创建状态

    用new运算符创建一个Thread类或子类的实例对象,但此时还未对这个线程分配任何资源

  2. 就绪状态

    分配系统资源,由start()启动方法来完成

  3. 运行状态

    当可运行状态的线程被调度并获得CPU等资源。进入run()方法。

  4. 阻塞状态

    由于人为或系统的原因,线程必须停止运行,以后还可以恢复运行的状态称为阻塞状态。

  5. 终止状态

    run()方法完成或调用stop()destroy()方法,不能继续运行。

六、线程的基本用法

1. 创建线程的方法

(1)方法一:通过继承Thread类创建线程

image-20201124132913675

(2)方法二:通过实现Runnable接口来创建线程

image-20201124133310441

(3)方法三:不实现Runnable接口,直接使用匿名类的方式
(方式二的简化)

image-20201124133439513

2.线程的运行与停止

(1)myThread. interrupt ();

​ 线程启动后并获得资源后,即可进入运行状态,执行run()方法中的业务逻辑。
在run()方法返回后,线程自动终止

或者由主线程通知子线程终止,一般调用interrupt()方法通告线程准备终止

(2)Thread.interrupted()

​ interrupt()方法改变了线程内部的一个布尔值,可在run()方法检测到这个布尔值的改变,从而在适当的时候释放资源和终止线程

1
2
3
4
5
6
7
run(){
...
while(!Thread.interrupted()){
... //未调用myThread.interrupt()时循环执行
}
...
}

七、异步消息处理机制(Handler + Message)

image-20201124140620842

1.异步消息处理流程

(1)主线程:创建Handler对象,重写handleMessage方法
(2)子线程:创建Message对象,发送消息

​ 当子线程需要进行UI操作时,就创建Message对象,并通过Handler的 sendMessage()方法(参数是Message对象),将将这条消息发送出去。 这条待处理的消息会被 添加到主线程的MessageQueue中。

(3)主线程:处理消息,回调handleMessage

​ 主线程通过Looper管理MessageQueue,不断地尝试从消息队列中取出 待处理消息进行处理,取出消息时就会回调 Handler的handlerMessage()方法

2.Hander

使用Hnadler类的sendMessage()方法把一个包含消息数据的Message对象压入到消息队列。其它可选方法还包括:
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)

3.Message

(1)Message对象:

推荐使用Message.obtain() 静态方法从消息池中获取一个Message对象。
如果消息池为空, 将使用构造方法实例化一个新Message,以利于消息资源的利用。
一般并不推荐直接使用它的构造方法

(2)用来封装所发送消息的值:

int arg1:参数一,用于传递不复杂的数据,复杂数据使用setData()传递。
int arg2:参数二,用于传递不复杂的数据,复杂数据使用setData()传递。
Object obj:传递一个任意的对象。
int what:定义的消息码,一般用于设定消息的标志。

(3)传递复杂数据 setData()

​ –void setData( Bundle bundle)

​ –Bundle getData()

2.案例实现:幸运大抽奖(方式一)

image-20201124140746244

image-20201124140752579

image-20201124140759685

八、异步消息处理机制(Handler + Post)

1. Handler也可以把一个Runnable对象压入到消息队列中,进而在UI线程的消息队列获取并执行Runnable对象。(在主线程中执行)

(1)把一个Runnable对象入队到消息队列,方法有:
  • post(Runnable)

  • postAtTime(Runnable,long)

  • postDelayed(Runnable,long)

(2)从消息队列中移除一个Runnable对象

​ void removeCallbacks(Runnable r)

2.对于Handler的Post方式来说,它传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作

image-20201130202655320

3. 案例实现:幸运大抽奖(方式二)

image-20201130203252201

服务的原理和用途

一、启动服务的两种状态

1.启动状态:通过Context的startService()启动service

一旦启动,服务即可在后台(无UI)无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用stopService()或者自己停止(stopSelf)才能停止服务。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如通过网络上传,下载文件,操作一旦完成,服务应该自动销毁

2.绑定状态:通过Context的bindService()绑定Service

绑定服务提供了一个客户端-服务器接口,允许调用方与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个调用方可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁

二、在清单文件中的声明

image-20201125094605395

四、使用Service

1.首先要创建服务,必须创建 Service 的子类(或使用它的一个现有子类如IntentService)。

2.类似Activity,Service有自己的生命周期,因此在实现中需要重写一些回调方法,以处理服务生命周期的某些关键过程。

3.随后,组件(例如活动)以启动或者绑定方式启动服务,服务开始其生命过程

五、Service中常用回调方法

1.abstract IBinder onBind(Intent intent)

该方法是一个抽象方法,所有Service子类必须实现该方法。该方法将返回一个IBinder对象,应用程序可通过该对象与Service组件通信

2.void onCreate()

当Service第一次被创建时,将立即回调该方法;

3.void onDestroy()

当Service被关闭之前,将回调该方法

4.void onStartCommand(Intent intent,int flags,int startId)

每次客户端调用startService(Intent intent)方法启动该Service时都会回调onStartCommand()方法

5.boolean onUnbind(Intent intent)

当该Service上绑定的所有客户端都断开连接时将会回调该方法。

六、活动的生命周期

image-20201125095321429

启动方式启动服务

一、StartService生命周期及进程相关

1.对于同一类型的Service,Service实例永远只存在一个,而不管Client是否是相同的组件,也不管Client是否处于相同的进程中。

2.以启动方式运行的服务,其生命周期是独立的,与Client本身的生命周期没有任何关系,只有Client调用stopService(..)或服务本身调用stopSelf(..)才能停止。或者,当用户强制kill掉服务所在进程,或系统因内存不足也可能kill掉此Service

3.Client A 通过startService(..)启动Service后,可以在其他Client(如Client B、Client C)通过调用stopService(..)结束此Service

4.Client调用stopService(..)时,如果当前Service没有启动,也不会出现任何报错或问题,也就是说,stopService(..)无需做当前Service是否有效的判断。

5.startService(Intent serviceIntent),其中的intent既可以是显式Intent,也可以是隐式Intent,当Client与Service同处于一个App时,一般推荐使用显示Intent。当处于不同App时,只能使用隐式Intent。

image-20201214111515792

二、Client与Service通信相关

1.启动Service时

当Client调用startService(Intent serviceIntent)启动Service时,Client可以将参数通过Intent直接传递给Service。

2.Service执行过程中

Service执行过程中,如果需要将参数传递给Client,一般可以通过借助于发送广播的方式(此时,Client需要注册此广播)。

绑定方式启动服务

一、三个基本特征

1.C-S模式:绑定状态的服务代表着客户端-服务器接口中的服务器

2.交互与通信:当其他组件(如 Activity,代表了客户端Client)绑定到服务时,组件(如Activity)可以向Service(也就是服务端)发送请求,或者调用Service(服务端)的方法,此时被绑定的Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 。

3.生命周期:与启动方式启动的服务不同,绑定方式启动的服务的生命周期通常只在为其他应用组件(如Activity)服务时处于活动状态,不会无限期在后台运行,也就是说所有宿主(如Activity)解除绑定后,绑定服务就会被销毁

二、BindService生命周期

1.onBind方法只被调用一次。(当调用bindService()方法是)Service第一次被创建时会执行onCreate方法,之后自动调用onBind()方法。

2.当调用unBindService()方法时,Service会依次执行onUnbind()onDestroy()方法结束生命周期

3.如果绑定服务的Client(Activity)被销毁,则Service也会被销毁,不管是否Client主动调用unbindService()解除绑定。

img

三、一般流程!!!

image-20201214111833380

1.服务端,Service中

(1)创建Binder子类,返回服务实例对象

image-20201214110207275

(2)在onBind()中返回Mybinder的实例对象iBinder

image-20201214110333487

2.客户端,Activity中

(1)创建ServiceConnection实例,在onServiceConnected 中接收返回的Binder 实例,通过Binder实例的getService()方法获取服务实例myService

image-20201214110911643

(2)绑定之后可以通过myService方法调用服务中的方法

image-20201214111553877

(3)解除绑定

image-20201214111631223

注:也可以将服务中的方法写在内部类mybinder中,在活动的ServiceConnect实例的onServiceConnected()中获取myBinder实例,通过这个实例调用服务中的方法。(不推荐)

混合使用启动方式和绑定方式

一、混合开启服务

既然start开启的服务不能调用方法,bind方式开启的服务生命力又很弱,那么能否两种方式的有点么?答案当然是肯定的,否则服务的应用能力就太弱了。

  1. 通过startService()方式开启服务(只能通过调用stopService停止)

  2. 通过bindService进行绑定,以进行服务的方法调用(当需要的时候)

  3. 通过unbindService进行解绑(不需要调用方法了,在UI线程被销毁之前解绑)

  4. 通过stopService停止服务

这样我们就可以灵活的使用Service,当需要的时候可以随时进行调用,不要的时候又可以长期运行于后台而不用保留UI线程。这也是服务最常见的用法。

二、可以同时使用两种方式启动服务,但是服务的onCreate方法只会被调用一次。

三、如果想停止服务,必须调用stopService,并且所有调用 bindService 的 Context调用unbindService解绑,或者之前调用 bindService 的 Context 不存在了。

服务的更多使用技巧

一、使用前台服务

1.服务运行在后台,优先级低,系统内存不足时,可能会被回收

如果希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。

2.与普通服务相比:在系统的状态栏显示一个正在运行的图标。下拉状态栏后可以看到更加详细的信息,类似于通知的效果。

3.startForeground()stopForeground()方法

startForeground(1,notification);

二、使用IntentService

(未完待续……)