导览
1.Android UI
a)Layout (CommonLayout,Adapter Layout)
b)InputControls(Buttons,TextFileds,Bars)
c)InputEvents(onClick,onKey,onTouch,onChecked)
d)UI Components(Menu,ActionBar,Dialog,Notification,Toast,Setting)
e)自定义view (直接或间接继承view,重写onDraw,自定义属性,资源回收)
f) 动画(属性动画,帧动画,补间动画)
g)绘制(Bitmap,Canvas,Paint)
2.Android Data Store
1)外部存储(外置sdcard)
2)内部存储(内置sdcard)
3)偏好设置(data/data/项目/share_prefs/.xml)
4)SQLite存储(DBMS,DB,table,view,sql,adb指令)
3.Android Thread,Message
a)Android 中的线程基础(Java中线程应用)
b)Android 中的线程同步(Java中的线程同步)
c)Android 中的消息模型(Message,MessageQueue,handle,looper,)
d)Android 中的异步任务(AsyncTask)
e)Android 中的线程池(Executor,Executors)
案例:加载大图片(异步,压缩,缓存)
4.Android Components
a) Content Provider (跨进程私有数据共享)
b) Service (长时间的后台耗时操作)
c) BroadCastReceiver(实现一对多的跨进程数据传递)
d) Activity(呈现view,实现与用户交互)
e) 关联组件(Fragment,Intent,Application)
Day01-1 Android 提高阶段
--------------------------------------------------
1.Android 中的UI(User Interface)
2.Android 中的数据存储(DataStore)
3.Android 中的多线程应用(Thread,....)
4.Android 中的核心组件应用(ContentProvider,Service,...)
---------------------------------------------------
1.Android UI
UI (Android 中的)Day01-2
1.UI?(what)
1)User Interface (用户接口)
2)Android view
3)H5+CSS3+JS
2.UI(when,why)
1)呈现数据
2)与用户进行交互。
3.UI(分类)
1)View(android原生UI父类对象,它分为基本view,容器view)
2)ViewGroup(view容器,布局基本view组件)
Android项目结构
---------------------------------------
src:Java源代码
gen:自动生成的Java源代码,不可修改
assets:(无视)外部文件
bin:(无视)运行的目标文件,例如.apk文件
libs:使用的库或者jar包
res:资源文件
AndroidManifest.xml:项目清单文件
project.properties:属性配置,例如编译版本
图片尺寸自适应
--------------------------------
图片尺寸自适应:系统根据设备的屏幕尺寸和分辨率,自动优先选取某drawable-???下的图片。
系统会根据设备的尺寸和分辨率得到设备的显示密度,不同的密度值则对应不同的?dpi,例如密度值在160则为ldpi,密度值在240时则为mdpi,密度值为320时为hdpi……
实现过程:为不同的显示密度设计不同分辨率的图片,并把相同的图片使用相同的文件名存放在不同的drawable文件夹下即可,当APP运行时,系统会自动优先选取。
RGB颜色与ARGB颜色
---------------------------------------
RGB颜色是以#作为前缀,使用6位十六进制数表示的颜色,例如#FF0000、#00FF00、#0000FF,在表示颜色时,字母不区分大小写
ARGB颜色是在RGB的基础上,最左侧多使用了2位十六进制数表示的颜色,A表示alpha,即透明度,alpha位置取值为ff时表示完全不透明,取值为00时表示完全透明。
隐藏默认的标题栏
---------------------------------------
打开AndroidManifest.xml文件,找到需要去掉标题栏的<activity>节点,配置android:theme属性,然后,为Activity配置带有NoTitleBar或NoActionBar字样的主题,例如android:theme="@android:style/Theme.Holo.Light.NoTitleBar"
语言国际化自适应
--------------------------------
语言国际化自适应:当设备的使用者切换设备的语言环境时,APP上显示的字符串也会自动更换语言种类。即当用户设置为中文环境时,APP显示中文,当用户设置被英文环境时,APP显示英文。
语言国际化自适应的原理是:系统会根据语言环境,优先选取某些values-???文件夹下的string资源的配置,例如中文环境时,优先选取values-zh文件夹下的string资源,而英文环境时则优先选取values-en文件夹下的string资源。
实现语言国际化自适应的values文件夹名称的格式是:
values-小写语言简称-r大写地区简称
其中,“-r大写地区简称”可以忽略。
例如:values-zh-rCN
Eclipse的常用快捷键
----------------------------------
alt + / -> 自动提示,如果代码有错误,则可能无效
Ctrl + Shift + F -> 代码格式化(排版),如果使用中文输入法,则可能无效
Ctrl + Shift + O -> 整理import代码
alt + 向上/向下 -> 光标所在行的代码向上/向下移动1行
Ctrl + alt + 向上/向下 -> 将光标所在行的代码向上/向下复制
Ctrl + D -> 删除光标所在行的代码
Ctrl + 2, R -> 批量重命名
Shift + 回车 -> 向下产生新的空白行,并且光标移动到该行
控件
TextView:文本显示控件
---------------------------------------
特征:
用于显示字符串的控件
属性:
1. android:text -> 控件上显示的文本,取值可以使字符串,或者@string/???
2. android:gravity -> 文本在控件内部的对齐方式,取值参考android:layout_gravity属性
3. android:textSize -> 文本尺寸,取值为以sp为单位的数值,例如14sp,数值建议不小于10,也可以取值为@dimen/???
4. android:textColor -> 文本颜色,取值为RGB颜色值,或者@color/???
5. android:singleLine -> 单行显示,即当文字在控件内1行显示不下时,应该如何处理,取值为true或者false,取值为true时,将不显示超出的部分,取值为false时,将在控件可用范围内继续显示。
ellipsize 省略模式
EditText:文本输入框控件
---------------------------------------
特征:
可以使用该控件输入字符
EditText是TextView的子类
属性:
1. android:inputType -> 输入的字符类型,取值为枚举类型,例如text、textPassword、number、phone
2. android:hint -> 提示文字,当该控件内没有输入任何内容时的提示问题,取值参考android:text属性
3. android:textColorHint -> 提示文字的颜色,取值参考android:textColor属性
Ps:
1. 如果某个EditText控件存在子级节点<requestFocus />则表示该EditText默认获取到焦点,即光标定位在该EditText控件上
ImageView:图片显示控件
----------------------------------
ImageView是用于显示图片的控件
属性:
android:src -> 需要显示的图片的来源,取值通常为 @drawable/???
android:contentDescription -> 使用文字对图片进行描述,如果不想设置,可以取值为 @null
android:scaleType -> 图片的缩放模式,当图片的尺寸与ImageView的尺寸不符时有效,取值为枚举类型fitXY xy适应
android:adjustViewBounds 是否保持宽高比。需要与maxWidth、MaxHeight一起使用,否则单独使用没有效果。
android:cropToPadding 是否截取指定区域用空白代替。单独设置无效果,需要与scrollY一起使用,效果如下,实现代码见代码部分:
android:maxHeight 设置View的最大高度,单独使用无效,需要与setAdjustViewBounds一起使用。如果想设置图片固定大小,又想保持图片宽高比,需要如下设置:
1) 设置setAdjustViewBounds为true;
2) 设置maxWidth、MaxHeight;
3) 设置设置layout_width和layout_height为wrap_content。
android:maxWidth 设置View的最大宽度。同上。
android:scaleType 设置图片的填充方式。
android:tint 将图片渲染成指定的颜色。见下图: 左边为原图,右边为设置后的效果,见后面代码
网络上下载下来的图片自适应: android:adjustViewBounds="true"
Layout(CommonLayout,Adapter Layout)
CommonLayout普通布局
布局视图(ViewGroup Android UI中的)Day01-3
Common Layout一般布局
padding 内边距margin 外边距scrollbars 滚动条
1)RelativeLayout (相对布局)
特征:
1. 每个控件都可以使用其它控件作为参考点,从而决定自身的尺寸或者位置
2. 必须首先确定1个不以其它控件作为参考点,而是可以直接确定自身的尺寸和位置的控件
3. 当控件没有使用参考点控件时,默认以根布局作为参考点,且靠左对齐,靠上对齐
4. 相对布局中的子级布局控件可以重叠
属性:特有属性: align系列 --> 将控件设置为与相应控件的左右上下对齐
(没有)
to 系列 --> 将控件设置在相应控件的 上下左右
相对布局的子级控件的属性:
1. android:layout_alignParentLeft -> 对齐父级控件的左侧边缘,取值为true或者false
2. android:layout_alignParentRight -> 对齐父级控件的右侧边缘,取值同上
3. android:layout_alignParentTop -> 对齐父级控件的顶部边缘,取值同上
4. android:layout_alignParentBottom -> 对齐父级控件的底部边缘,取值同上
5. android:layout_centerHorizontal -> 在父级控件范围内水平方向居中对齐,取值同上
6. android:layout_centerVertical -> 在父级控件范围内容垂直方向居中对齐,取值同上
7. android:layout_centerInParent -> 在父级控件范围内水平方向和垂直方向均居中对齐,取值同上
8. android:layout_alignTop -> 与其它某个控件的顶部在垂直方向对齐,取值为其它控件的id
9. android:layout_alignBottom -> 与其它某个控件的底部在垂直方向对齐,取值同上
8. android:layout_alignLeft -> 与其它某个控件的左侧在水平方向对齐,取值同上
9. android:layout_alignRight -> 与其它某个控件的右侧在水平方向对齐,取值同上
10. android:layout_toLeftOf -> 设置控件在水平方向的位置在其它某个控件的左侧,取值为其它控件的id
11. android:layout_toRightOf -> 设置控件在水平方向的位置在其它某个控件的右侧,取值同上
12. android:layout_above -> 设置控件在垂直方向的位置在其它某个控件的上方,取值同上
13. android:layout_below -> 设置控件在垂直方向的位置在其它某个控件的下方,取值同上
Ps:
1. 当以其它控件作为参考点,其它控件必须与自身是同级(拥有相同的直接父级控件)控件
2. 不可以互相参考
1.RelativeLayout(相对布局)
定义的每个组件都相对与其它组件进行布局(也就说
每个组件都有一个相对位置),一般用于与比较复杂
的布局页面。
说明:
1)所有的布局对象在应用时要么在Activity中直
接new,要么定义在res/layout目录的xml文件中。
2)在使用所有的布局对象时需重点掌握的是布局
方式以及常用属性。
2)LinearLayout (线性布局)
特征:
1. 直接子级布局的控件按照代码的顺序先后排列
2. 直接子级布局的控件可以按照比例分配尺寸
属性:特有属性: weight --> 将宽(高)设置为0dp,则设置weight可按照比例进行分配空间
orientation --> 设置内部空间按垂直(水平)排布
android:orientation -> 布局方向,取值为vertical或者horizontal,前者表示子级布局的控件垂直方向(从上至下)排列,后者表示子级布局的控件水平方向(从左至右)排列
Ps:线性布局的直接子级布局的控件可以配置如下属性:
android:layout_weight -> 权重/比例,取值为整数
android:layout_gravity -> 对齐方式,取值为left、right、top、bottom、center,或者其中2个不冲突的值组合起来,例如top|left
3)GridLayout (网格布局)
android:layout_row=”0”表示从第一行开始,
android:layout_column=”0”表示从第一列开始
column --> 跨列
rowSpan --> 跨行
4)FrameLayout(帧布局)
5) AbsoluteLayout绝对布局
AbsoluteLayout绝对布局犹如div指定了absolute属性,用X,Y坐标来指定元素的位置!
6)......
2.Adapter Layout
1)ListView (列表视图)
2)GridView(网格视图)
3)Spinner(下拉列表)
4)ViewPager(视图分页)
5).......
Day02-1 (Common Layout)内容回顾
--------------------------------------------------
1.RelativeLayout
2.LinearLayout
3.GridLayout
4.FrameLayout
重点掌握
1)布局方式
2)常用属性(共性,特性)
Common Layout 优化Day02-2
1.优化继承体系(减少层次结构)
例如:借助merge标签实现相同布局的合并
<merge/>标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。<merge/>多用于替换FrameLayout或者当一个布局包含另一个时,<merge/>标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,引入了一个垂直布局的include,这是如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用<merge/>标签优化。
meger运行程序后在Emulator中显示的效果是一样的,可是通过hierarchyviewer查看的UI结构是有变化的,当初多余的Layout节点被合并在一起了,或者可以理解为将merge标签中的子集直接加到Activity的Layout跟节点下(这里需要提醒大家注意:所有的Activity视图的根节点都是frameLayout)。如果你所创建的Layout并不是用framLayout作为根节点(而是应用LinerLayout等定义root标签),就不能应用上边的例子通过merge来优化UI结构。
除了上边的例子外,meger还有另外一个用法,当应用Include或者ViewStub标签从外部导入xml结构时,可以将被导入的xml用merge作为根节点表示,这样当被嵌入父级结构中后可以很好的将它所包含的子集融合到父级结构中,而不会出现冗余的节点。
<merge />标签极其有用。然而它也有以下两个限制:
1.<merge />只能作为XML布局的根标签使用
2当需要扩充的xml layout本身是由merge作为根节点的话.(当Inflate以<merge />开头的布局文件时),必须指定一个父ViewGroup,并且必须设定attachToRoot为true(参看inflate(int, android.view.ViewGroup, Boolean)方法)。
2. 重用layout对象(include标签)
例如:借助include标签导入共用布局
include标签内不能设置RelativeLayout属性,如android:layout_alignParentBottom,因为不起作用
include标签内设置id属性后(android:id),其引用的布局layout内的id属性就不起作用了,怀疑是其引用的layout外层包裹了一层include标签
或者是覆盖了其内的属性id
why如果没有include标签,所有布局代码都写在一个xml文件中,界面会显得很冗余,可读性很差。而且界面加载的时候是按照顺序加载的,前面的布局不能
调用其后面的布局id。而采用include后,一个include中可以引用其后的include中的布局id属性
布局中可以包含两个相同的include标签,引用时可以使用如下方法解决(参考):
View bookmarks_container_2 = findViewById(R.id.bookmarks_favourite);
bookmarks_container_2.findViewById(R.id.bookmarks_list);
3.实现对象的延迟加载(借助ViewStub)
<ViewStub />标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。各种不常用的布局想进度条、显示错误消息等可以使用<ViewStub />标签,以减少内存使用量,加快渲染速度。<ViewStub />是一个不可见的,大小为0的View。
ViewStub的一些特点:
1. ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
2. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。
基于以上的特点,那么可以考虑使用ViewStub的情况有:
1. 在程序的运行期间,某个布局在Inflate后,就不会有变化,除非重新启动。
因为ViewStub只能Inflate一次,之后会被置空,所以无法指望后面接着使用ViewStub来控制布局。所以当需要在运行时不止一次的显示和隐藏某个布局,那么ViewStub是做不到的。这时就只能使用View的可见性来控制了。
2. 想要控制显示与隐藏的是一个布局文件,而非某个View。
因为设置给ViewStub的只能是某个布局文件的Id,所以无法让它来控制某个View。
所以,如果想要控制某个View(如Button或TextView)的显示与隐藏,或者想要在运行时不断的显示与隐藏某个布局或View,只能使用View的可见性来控制。
当你想加载布局时,可以使用下面其中一种方法:
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
---------------------------------------------------
Adapter layout
Adapter layout Day02-3
所有的Adapter Layout都直接或间接的继承了
ViewGroup,并借助一个Adapter对象加载数
据,构建item对象,然后以某种方式呈现给用户。
常用的Adapter Layout如下:
1.ListView (以列表形式显示数据)
2.GridView ( 以网格形式显示数据)
3.Spinner (以下拉列表的形式显示数据)
4.ViewPager(以分页形式显示数据)
5....
基本构成及实现原理参考:包饺子.png
----------------------------------------------------
ListView(以列表形式显示数据)
(What)ListView是一个经常用到的控件,ListView里面的每个子项Item可以使一个字符串,也可以是一个组合控件。
(How)先说说ListView的实现:
1.准备ListView要显示的数据 ;
2.使用 一维或多维 动态数组 保存数据;
3.构建适配器 , 简单地来说, 适配器就是 Item数组 , 动态数组 有多少元素就生成多少个Item;
4.把 适配器 添加到ListView,并显示出来。
ListView 的简单应用Day02-3
ListView是以列表的形式呈现数据的控件,表现为控件内部的数据呈“横排”排列,且每条数据都比较相似。
显示ListView的4个要素
--------------------------------
1. ListView控件,容器,用于承载若干个列表项,展示列表的View。
2. 数据,通常是List集合,用于确定需要显示在列表中的数据内容,具体的将被映射的字符串,图片,或者基本组件。
3. 模板,即res\layout下的xml文件,用于约定每一条数据呈现的样式
4. Adapter,适配器,作用是“组装工人”,用于组装每一条数据与模板,得到需要显示的内容,最后这些内容将显示在ListView中。用来把数据映射到ListView上的中介
根据列表的适配器类型,列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter
其中以ArrayAdapter最为简单,只能展示一行字。SimpleAdapter有最好的扩充性,可以自定义出各种效果。SimpleCursorAdapter可以认为是SimpleAdapter对数据库的简单结合,可以方面的把数据库的内容以列表的形式展示出来。
1.ListView 应用实现的基本步骤
1)构建ListView对象
ListView lsv=(ListView)findViewById(R.id.lsvId);
2)准备item view,item数据
Layout文件夹构建layout以及相应的view,还有data或构建或引入
3)构建Adapter,组装数据,构建item.
ArrayAdapter<String> adapter=
//new ArrayAdapter<String>(this,R.layout.list_item_res_1, data);
new ArrayAdapter<String>(Context, //环境一般为this
layout,//R.layout.xx
item view,//R.id.xx
data);//数据
4)listview 关联adapter.
lsv.setAdapter(adapter);
开发ListView的流程
--------------------------------
1. 在activity_main.xml中,添加<ListView>节点,并配置android:id、android:layout_width、android:layout_height属性
2. 声明并初始化ListView
3. 声明List集合,用于保存需要显示的数据,并创建对象,添加数据
4. 在res\layout下,创建新的xml文件,如果使用ArrayAdapter,则根节点选择TextView,如果使用其它Adapter,则自由设计
5.
如果使用ArrayAdapter,声明ArrayAdapter,并创建对象,构造方法的第1个参数为MainActivity.this,第2个参数为R.layout.???(步骤4创建的文件),第3个参数为List集合(步骤3创建的对象)
如果使用SimpleAdapter,则声明,并通过构造方法创建对象,其中,构造方法的第1个参数为MainActivity.this,第2个参数为数据(步骤3创建的对象),第3个参数为R.layout.???(步骤4创建的文件),第4个参数为String[],值为Map中各个key,例如{ "name", "number" },第5个参数为int[],值为模板中显示数据的控件的ID,例如{ R.id.tv_contact_item_name, R.id.tv_contact_item_number }
6. 调用ListView对象的setAdapter()方法,为ListView配置Adapter,该方法的参数为步骤5创建的对象
Listview特殊属性
首先是stackFromBottom属性,这只该属性之后你做好的列表就会显示你列表的最下面,值为true和false
android:stackFromBottom="true"
第二是transciptMode属性,需要用ListView或者其它显示大量Items的控件实时跟踪或者查看信息,并且希望最新的条目可以自动滚动到可视范围内。通过设置的控件transcriptMode属性可以将Android平台的控件(支持ScrollBar)自动滑动到最底部。
android:transcriptMode="alwaysScroll"
第三cacheColorHint属性,很多人希望能够改变一下它的背景,使他能够符合整体的UI设计,改变背景背很简单只需要准备一张图片然后指定属性 android:background="@drawable/bg",不过不要高兴地太早,当你这么做以后,发现背景是变了,但是当你拖动,或者点击list空白位置的时候发现ListItem都变成黑色的了,破坏了整体效果。
如果你只是换背景的颜色的话,可以直接指定android:cacheColorHint为你所要的颜色,如果你是用图片做背景的话,那也只要将android:cacheColorHint指定为透明(#00000000)就可以了
第四divider属性,该属性作用是每一项之间需要设置一个图片做为间隔,或是去掉item之间的分割线
android:divider="@drawable/list_driver" 其中 @drawable/list_driver 是一个图片资源,如果不想显示分割线则只要设置为android:divider=“@null" 就可以了
第五fadingEdge属性,上边和下边有黑色的阴影
android:fadingEdge="none" 设置后没有阴影了~
第六scrollbars属性,作用是隐藏listView的滚动条,
android:scrollbars="none"与setVerticalScrollBarEnabled(true);的效果是一样的,不活动的时候隐藏,活动的时候也隐藏
第七fadeScrollbars属性,android:fadeScrollbars="true" 配置ListView布局的时候,设置这个属性为true就可以实现滚动条的自动隐藏和显示。
第八fastScrollEnabled属性 ,
很多开发者不知道ListView列表控件的快速滚动滑块是如何启用的,这里Android开发网告诉大家,辅助滚动滑块只需要一行代码就可以搞定,如果你使用XML布局只需要在ListView节点中加入 android:fastScrollEnabled="true" 这个属性即可,而对于Java代码可以通过myListView.setFastScrollEnabled(true); 来控制启用,参数false为隐藏。 还有一点就是当你的滚动内容较小,不到当前ListView的3个屏幕高度时则不会出现这个快速滚动滑块,同时该方法仍然是AbsListView的基础方法,可以在ListView或GridView等子类中使用快速滚动辅助。
第九drawSelectorOnTop属性
When set to true, the selector will be drawn over the selecteditem. Otherwise the selector is drawn behind the selected item. Thedefault value is false.
android:drawSelectorOnTop="true" 点击某一条记录,颜色会显示在最上面,记录上的文字被遮住,所以点击文字不放,文字就看不到
android:drawSelectorOnTop="false"点击某条记录不放,颜色会在记录的后面,成为背景色,但是记录内容的文字是可见的
----------------------------------------------------
作业
1.总结
2.完成课堂案例(考试系统-新闻列表)
3.尝试完成一个GridView的案例
点击,排序,选择Listview 其它?Day03-4
1.Listview 的item中button元素的点击事件?
在此应用中需要借助item view的android:
descendantFocusability属性覆盖子元素
事件,这样既可以点击listview的item,又可以
点击item中的button元素。
2.ListView中item对象的选择?
一般会在item对象中添加一个CheckedTextView
或者用一个CheckBox.然后调用listview的
setChoiceMode设置选择模式。
实现ListView中元素的排序?
用Collections.sort方法对list排序有两种方法
第一种是list中的对象实现Comparable接口
Collections.sort(list);
第二种方法是根据Collections.sort重载方法来实现
Collections.sort(list,new Comparator (){
public int compare(User arg0, User arg1) {
return arg0.getOrder().compareTo(arg1.getOrder());
}
});
分块显示(ListView 中数据的)Day04-1
分块显示的应用场合:
1.要求数据有序
2.数据两不大,适合一次加载。
postion section ASCII
0 A 65
1 B 66
2 B 66
3 C 67
4 C 67
getSectionForPostion(1); 66
getPostionForSection(67); 3
------------------------------------------------
右侧导航(ListView 中数据的)Day04-2
右侧导航一般建立在分块显示的基础上,
同时会再添加一个listview显示对应的字母,
点击字母时,定位其它listview中item的位置。
-----------------------------------------------
Day03-1 内容回顾
---------------------------------------------------
1.Common Layout 优化?
1)优化树状层次(注意merge标签的使用)
2)实现布局重用(参考include标签的应用)
3)实现延迟加载(借助ViewStub标签)
2.Adapter Layout 概要?
3.Adapter layout 中ListView 的简单应用?
--------------------------------------------------
常见Adapter (构建adapter layout需要的item) Day03-2
ArrayAdapter (数据集比较简单,可以是数组或list集合)
ArrayAdapter是BaseAdapter的派生类,在BaseAdapter的基础上,添加了一项重大的功能:可以直接使用泛型构造。
优点:
简单
缺点:
模板单一,控件单一,只能显示数据单一的列表
// 当然listview 也可以是在layout里写好,然后findViewById()获取出来,这样的话后面就不需setContentView(listview);
ListView listview = new ListView(this);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1);
adapter.add("string1");
adapter.add("haha");
adapter.add("heihei");
listview.setAdapter(adapter);
setContentView(listview);
1. 适配器的作用是数据和视图之间的桥梁
2. 这个小例子是要显示一个数组,我们就用ArrayAdapter,数组适配器,数据的数据类型<>是String类型的,数据的数据类型还可以是其他的包括对象类型的
3. ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
ArrayListDemo.this, android.R.layout.simple_list_item_1,
adapterData);
这段代码是创建一个数组适配器的代码,里面有三个参数,
ArrayAdapter(Context context, int textViewResourceId, List<T> objects)
第一个参数(Context context)是上下文,就是当前的Activity,
第二个参数(int textViewResourceId)是android sdk中自己内置的一个布局,它里面只有一个TextView,这个参数是表明我们数组中每一条数据的布局是这个view,就是将每一条数据都显示在这个view上面;
第三个参数(List<T> objects)就是我们要显示的数据。listView会根据这三个参数,遍历adapterData里面的每一条数据,读出一条,显示到第二个参数对应的布局中,这样就形成了我们看到的listView.
SimpleAdapter (数据集为List<? extends Map<String,?>>)
SimpleAdapter是扩展性最好的适配器,可以定义各种你想要的布局,而且使用很方便
SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
参数context:上下文,比如this。关联SimpleAdapter运行的视图上下文
参数data:Map列表,列表要显示的数据,这部分需要自己实现,如例子中的getData(),类型要与上面的一致,每条项目要与from中指定条目一致
参数resource:ListView单项布局文件的Id,这个布局就是你自定义的布局了,你想显示什么样子的布局都在这个布局中。这个布局中必须包括了to中定义的控件id
参数 from:一个被添加到Map上关联每一个项目列名称的列表,数组里面是列名称
参数 to:是一个int数组,数组里面的id是自定义布局中各个控件的id,需要与上面的from对应
SimpleAdapter可以使用自定义的ListView,然后setContentView即可。也可以直接使用系统自带的ListAcitivity,该ListActivity实现了ListView,显示ListView的时候做了很多优化。
ListActivity直接extends ListActivity即可,不需要在setContentView了
ListItem无法点击是因为button抢占了ListItem的焦点, 对LinearLayout加上一个属性就可解决问题
android:descendantFocusability="blocksDescendants"
BaseAdapter(抽象类):自己写adapter可继承此类
适配器的作用主要是用来给诸如(Spinner、ListView、GridView)来填充数据的。而(Spinner、ListView、GridView)都有自己的适配器(记起来麻烦)。但是BaseAdapter(一招鲜)对他们来说却是通用的
这些可以用来将不同的数据映射到View上。写适配器主要是得重写四个方法:getCount,getItem,getItemId,getView。
首先进去的getCount()这个方法,这返回的是list里面的个数,即你要显示在View上的item,
紧接着就是根据你的item如何来绘制一个view了,即getView可以引用事先定义好的xml来确定显示的效果并返回一个View对象作为一个Item显示出来。也正是在这个过程中完成了适配器的主要转换功能,把数据和资源以开发者想要的效果显示出来。即有多少个Item就调用多少次ListView,使得ListView的使用更为简单和灵活。
而getItem和getItemId方法将会在调用ListView的响应方法的时候被调用到。所以要保证ListView的各个方法有效的话,这两个方法也得重写
SimpleCursorAdapter(数据集为一个Cursor对象)
用于将Cursor中的columns与XML文件中定义的TextView或者ImageView进行匹配的Adapter。
扩展:Cursor 是每行的集合。
使用 moveToFirst() 定位第一行。你必须知道每一列的名称。你必须知道每一列的数据类型。Cursor 是一个随机的数据源。所有的数据都是通过下标取得。
关于 Cursor 的重要方法:
close()
关闭游标,释放资源
copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
在缓冲区中检索请求的列的文本,将将其存储
getColumnCount()
返回所有列的总数
getColumnIndex(String columnName)
返回指定列的名称,如果不存在返回-1
getColumnIndexOrThrow(String columnName)
从零开始返回指定列名称,如果不存在将抛出IllegalArgumentException 异常。
getColumnName(int columnIndex)
从给定的索引返回列名
getColumnNames()
返回一个字符串数组的列名
getCount()
返回Cursor 中的行数
moveToFirst()
移动光标到第一行
moveToLast()
移动光标到最后一行
moveToNext()
移动光标到下一行
moveToPosition(int position)
移动光标到一个绝对的位置
moveToPrevious()
如果你喜欢用 for 循环而不想用While 循环可以使用Google 提供的几下方法:
isBeforeFirst()
返回游标是否指向之前第一行的位置
isAfterLast()
返回游标是否指向第最后一行的位置
isClosed()
如果返回 true 即表示该游戏标己关闭
5........
***Adapter 优化Day03-3
1.减少item view对象的构建次数(重用adapter对象
中getView方法中的convertView对象)。
2.减少item view对象中子view的查找次数(
减少getView方法内部findViewById方法的调用
次数-借助viewHolder实现)
--------------------------------------------------
扩展:setTag和getTag方法
view的setTag和getTag方法其实很简单,在实际编写代码的时候一个view不仅仅是为了显示一些字符串、图片,有时我们还需要他们携带一些其他的数据以便我们对该view的识别或者其他操作。于是android 的设计者们就创造了setTag(Object)方法来存放一些数据和view绑定,我们可以理解为这个是view 的标签也可以理解为view 作为一个容器存放了一些数据。而这些数据也可以通过getTag() 方法来取出来。
扩展:URI与Uri
名称如此相像的两个类是有什么区别和联系?
•1.所属的包不同。
URI位置在java.net.URI,显然是Java提供的一个类。而Uri位置在android.net.Uri,是由Android提供的一个类。所以初步可以判断,
Uri是URI的“扩展”以适应Android系统的需要。
•2.作用的不同。
URI类代表了一个URI(这个URI不是类,而是其本来的意义:通用资源标志符——Uniform Resource Identifier)实例。Uri类是一个不可改变的URI引用,包括一个URI和一些碎片,URI跟在“#”后面。建立并且转换URI引用。而且Uri类对无效的行为不敏感,对于无效的输入没有定义相应的行为,如果没有另外制定,它将返回垃圾而不是抛出一个异常。
Uri是Android开发的,扩展了JAVA中URI的一些功能来特定的适用于Android开发,所以大家在开发时,只使用Android 提供的Uri即可;
Uri通用资源标志符(Universal Resource Identifier, 简称"URI")。Uri代表要操作的数据,Android上可用的每种资源 - 图像、视频片段等都可以用Uri来表示。
扩展:Inflate()findViewById()
findViewById()函数其实有两种
一种是Activity类中findViewById()函数
另外一种是View类中定义的findViewById()函数
一般我们在oncreate()方法中使用的(**view)findViewById(R.id.**)既是调用的Activity中的findViewById()函数
而在其他情况写出的***view.findViewById()中调用的是view类中的findViewById()
inflate就相当于将一个xml中定义的布局找出来.
因为如果在一个Activity文件里直接用findViewById()这个方法的话,
那么它所对应的是setConentView()中调用的那个layout里的组件.
因此如果在同样的Activity里用到别的layout的话,
而且你还要设置这个layout里的组件(比如:ImageView,TextView)上的内容,
那么你就必须用inflate()先将这个layout找出来, 然后再用这个layout对象去找到它上面的组件 然后进行一系列的操作
inflate()方法中参数:
1.想要用的布局文件的id
2.持有选项卡的内容,获取FrameLayout
3.true:将此处解析的xml文件做为根视图View
Null:找不到父类;
Inflate()作用就是将xml定义的一个布局找出来,但仅仅是找出来而且隐藏的,没有找到的同时并显示功能。最近做的一个项目就是这一点让我迷茫了好几天。
android上还有一个与Inflate()类似功能的方法叫findViewById(),二者有时均可使用,但也有区别
区别在于:
如果你的Activity里用到别的layout,比如对话框layout,你还要设置这个layout上的其他组件的内容,你就必须用inflate()方法先将对话框的layout找出来,然后再用findViewById()找到它上面的其它组件
不同点:
LayoutInflater是用来找layout下xml布局文件的,而且它会实例化
findViewById()是找具体xml布局文件下的具体widget控件,比如:Button按钮
----------------------------------------------------
作业
1.总结
2.完成
1)Day03_layout_7(homework)
2)尝试一个Spinner应用
扩展:列表ExpandableListView 的应用Day04-3
ExpandableListView是一个特殊的ListView
,能够实现展开或收起的功能(例如qq的我的好
友,我的同学,。。。。),具体实现步骤参考
Day04_layout_2(expandablelistview) 案例。
ExpandableListView是又多个childList组成的。
当展开的childList过长,又需要打开其他的list时,用户只能先滚动到最上面关掉这个childList,才可能打开其他的childlist!父控件一定要为FrameLayout。因为需要添加在ExpandableListView上层的小导航条!
当我们需要默认展开或收起某个分组时,可
参考如下两个方法:
1)expandGroup
2)collapseGroup
ExpandableListView 应用中涉及的API:
1)ExpandableListActivity
2)ExpandableListAdapter
3)BaseExpandableListAdapter
4).......
1。如何替换ExpandableListView的默认箭头
解决方法:在配置文件中有个android:groupIndicator属性,将其设置为:你的selector,例如:android:groupIndicator="@drawable/lt_expandablelistviewselector"
lt_expandablelistviewselector为
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_expanded="true" android:drawable="@drawable/lt_open2" />
<item android:drawable="@drawable/lt_norml2" />
</selector>
lt_open2和lt_norml2分别是两张不同状态的图片
当自己替换图片被拉伸无法保持原状可将图片转为9-path的,问题迎刃而解
给ListView设置分割线,只需设置如下两个属性:
android:divider="#000" //设置分割线显示颜色
android:dividerHeight="1px" //此处非0,否则无效
打开group标题置顶
//只展开一个group的实现方法
mListView.setOnGroupClickListener(new OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
// TODO Auto-generated method stub
if (sign== -1) {
// 展开被选的group
mListView.expandGroup(groupPosition);
// 设置被选中的group置于顶端
mListView.setSelectedGroup(groupPosition);
sign= groupPosition;
} else if (sign== groupPosition) {
mListView.collapseGroup(sign);
sign= -1;
} else {
mListView.collapseGroup(sign);
// 展开被选的group
mListView.expandGroup(groupPosition);
// 设置被选中的group置于顶端
mListView.setSelectedGroup(groupPosition);
sign= groupPosition;
}
return true;
}
});
GridView ( 以网格形式显示数据)
Getview构建adapter layout要显示的item(包饺子)每显示一个item都会调用一次此方法
1.item layout对象(准备饺子皮对象)
1)借助context找到resource id对应的xml资源
2)借助xmlpullparser解析xml资源
3)借助反射构建xml元素对象
View v=View.inflate(getContext(),resource,null);
4)inflate方法的返回值为resource资源对应的根元素对象
2.item data (饺子馅)
News news=getItem(position);
3.set item data to item layout(将饺子馅放到饺子皮上包起来)
TextView titleTv=(TextView) v.findViewById(R.id.titleId);
TextView dateTv=(TextView) v.findViewById(R.id.dateId);
img.setImageURI(Uri.fromFile(new File(news.getLogo())));
titleTv.setText(news.getTitle());
return v;包好的饺子
网格GridView 的应用Day04-4
GridView是一个以网格形式显示数据的Adapter
Layout对象。
GridView 常用属性:
1)numColumns (列的数量)auto_fit,GridView的列数设置为自动
2)horizontalSpacing (水平间隙) 两列之间的边距
3)verticalSpacing (垂直间隙) 两行之间的边距
4) android:stretchMode="columnWidth",缩放与列宽大小同步 (列宽剩余空间的应用模式,需结合columnWidth属性一起使用,缩放与列宽大小同步)
5)columnWidth(列的宽度) 每列的宽度,也就是Item的宽度
GridView上添加的监听器与ListView类似。
android:stretchMode="columnWidth"缩放与列宽大小同步
?Spinner (以下拉列表的形式显示数据)
下拉列表Spinner Day04-5
Spinner 是以下拉列表的形式显示数据的一个(与ExpandableListView,spinner点击后原item为灰色,选择栏弹出)
Adapter Layout对象。
Spinner对象常用方法:
1)getSelectedItem
2)......
在Values-->Strings建
<span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="spinnername">
<item>北京</item>
<item>上海 </item>
<item>广州</item>
<item>深圳</item>
</string-array>
</resources></span>
---------------------------------------------------
?ViewPager(以分页形式显示数据)
分页对象ViewPager Day04-6
ViewPager 是以分页形式显示item的一个Adapter
layout对象,属于android.support.v4.view.ViewPager包中的
一个类。
关联API:
1)PagerAdapter
2)OnPageChangeListener
---------------------------------------------------
作业
1.总结
2.尝试完成viewpager案例
Day04-1 内容回顾
-------------------------------------------------
1.常见Adapter
1)ArrayAdapter
2)SimpleAdapter
3)BaseAdapter(自定适配器时可以用)
4).....
2.适配器的优化
1)减少item view对象的构建次数 (重用convertview)
2)减少item view中子元素的查找次数(构建 viewholder)
3.如何处理list item 中子元素的点击事件
4.如何实现Listview中元素的排序,选择。
Day05-1总结Adapter Layout
1.Adater Layout 特性
所有的Adapter Layout在显示数据时,都需要
借助Adapter对象构建item,然后再交给Adapter
Layout以某种形式显示。
2.Adapter Layout 对象
1)ListView,ExpandableListView
2)GridView
3)Spinner
4)ViewPager
5)...............
3.Adatper 对象API
1)ListAdapter
2)BaseAdapter
3)ArrayAdapter,SimpleAdapter,自定义adapter
4)PagerAdapter
5)..............
4.FAQ?
1)Listener (监听)
2)Attributes(属性)
---------------------------------------------------
?滚动视图(继承FrameLayout) Day05-2
1.ScrollView (垂直滚动)
2.HorizontalScrollView(水平滚动)
以上两个对象在使用时,需要嵌套一个LinearLayout
然后在此Layout中再去填充其它组件。
?selector:背景选择器
--------------------------------
selector可以让控件在不同的状态(按下了、选中了、启用了……)下显示不同的效果。
selector是在res\drawable下配置的XML文件。
应用selector时,需要将它设置在控件的图形相关属性上,例如背景属性。
【注意】在配置selector时,没有显式的定义state的<item>能匹配所有状态,基于Android系统解析XML是从上之下,且懒汉式的解析,如果把这样的<item>放在代码靠前的位置,则会导致后续配置的<item>节点无效。所以,没有定义state的<item>必须位于最后!
----------------------------------------------------
InputControls(Buttons,TextFileds,Bars)
输入控制 (InputControlls) Day05-3
1.?Buttons (按钮)
1)Button
特征:
用于用户点击的按钮
Button是TextView的子类
属性:
(无)
Ps:
1. Button控件默认存在android:minWidth和android:minHeight属性
2)ImageButton
3)Switch
4)RadioButton (单选按钮,结合RadioGroup使用)
RadioButton用于在一组选项中能够选取有且仅有其中1项。
RadioButton是CompoundButton的子类,也是TextView的间接子类。
RadioGroup是RadioButton的容器,是LinearLayout的子类。
只有同一个RadioGroup的若干个子级RadioButton才是互斥的。
每一个RadioButton不可以通过反复点击取消选中,只能通过选中其它的互斥项才可以取消选中。
属性:
android:checked -> 默认是否勾选,取值为true/false
5)CheckBox(多选按钮)
CheckBox是可以在一组CheckBox勾选其中的若干项的控件。
CheckBox是TextView的间接子类。
属性:
android:checked -> 默认是否勾选,取值为true/false
监听器:
CompoundButton.OnCheckedChangedListener
扩展:Button的选择器(selector),样式的设置(style)
新建xml—>drawable—>Shape(drawable里)形状设置 (solid 形状背景色-->color,corners角—>radius半径范围,stroke图形边框设置width宽度、color颜色)
新建xml—>darwable—>selector选择器<item/>在里面设置color颜色,state_pressed按键状态执行(true按键,false不按)
2.TextFields
1)EditText (selector,style,OnEditorActionListener)
2)AutoCompleteTextView (自动提示文本,实现过程类似adapter layout)
扩展:SearchView (查询view)
3.Bars
ProgressBar (进度条)
--------------------------
【属性】
1. style -> 进度条的样式
2. android:max -> 进度的最大值
3. android:progress -> 当前进度
【方法】
void setMax(int)
int getMax()
void setProgress(int)
int getProgress()
2)SeekBar (可拖拽的进度条,播放音乐,。。)
--------------------------
SeekBar是ProgressBar的子类,使用方法可以完全参考ProgressBar的使用方法。
3)RatingBar (评价条)
------------------------------------------------
c)InputEvents(onClick,onKey,onTouch,onChecked)
?输入事件(InputEvents) Day05-4
1.onClick
2.onKey
3.onTouch
4.onFocus
5.......
注意:事件的传播特性(事件可以从内层容器
向外层传递)
--------------------------------------------------
项目案例(elts新手指导页面) Day05-5
1.功能需求
1)全屏,分页显示
2)页面右上角有跳过按钮
3)最后一页有按钮,点击可进入系统主页
4)每个页面都有一个页面指示器。(selector)
2.Coding 实现
-------------------------------------------------
项目案例(elts系统主页面) Day05-6
d)UI Components(Menu,ActionBar,Dialog,Notification,Toast,Setting)
Day06-1UI Commons (呈现view)
---------------------------------------------------
1.Menus (菜单)
2.ActionBar(动作栏)
3.Dialog(对话框)
4.Notification(通知)
5.Toast(吐丝通知)
---------------------------------------------------
Menu
Menu 菜单API Day06-2
选项菜单 (Option Menu):
通常会结合actionBar使用
1)onCreateOptionsMenu (此方法中创建菜单)
2)onOptionsItemSelected(此方法中处理菜单的点击操作)当客户点击菜单当中的某一个选项时,会调用该方法
上下文菜单 (Context Menu):
基于某个view的长按事件
1)创建:onCreateContextMenu
2)注册(在view上注册):registerForContextMenu
3)监听:onContextItemSelected
弹出式菜单 (Popu Menu):
基于某个view的点击事件
子菜单(subMenu以上每种菜单都可以创建子菜单)
说明:实现菜单的分组(便于统一显示或隐藏)
学习方式?
1)应用场合
2)实现方式(创建,监听)
---------------------------------------------------
作业
1.总结
2.完成驾校通(报名页面的驾校列表)
3.预习actionbar,notification知识点
Day07-1 内容回顾(菜单):Menu(呈现view)
--------------------------------------------------
1.选项菜单 (基于actiionBar)
2.上下文菜单(基于view的长按事件)
3.弹出式菜单(基于view的点击事件)
4.子菜单(在原有菜单的基础上再添加菜单)
ActionBar
?ActionBar(动作栏,工具栏(toolbar)) Day07-2
ActionBar 为android3.0以后推出的一个动作栏
对象,在使用前activity的主题(Theme)不能是
noTitleBar,noActionBar.
具体在使用actionBar通常会先通过activity的
getActionBar方法获得actionBar,然后再调用
actionBar相关的方法执行一系列操作,例如
设置home返回,设置tab导航等。
重点掌握:设置actionBar的样式的过程,一般是
修改主题。
Dialog,Notification,Toast,Setting
通知Day07-3
1.Notification (通知组件)通知栏
2.Toast(吐丝通知)
-------------------------------------------------
Day08-1 内容回顾
---------------------------------------------------
1.ActionBar (动作栏)
1)actionbar&optionmenu
2)actionBar tab导航
3)actionBar 样式(style)
4)......
?Dialog
?Notification(显示在状态栏的一个对象)
1)Notification (通知对象)
2)NotificationCompat(通知对象的兼容对象)
3)Builder(借助此对象构建通知)
4)NotificationManager (发送通知)
5)PendingIntent(延迟意图),ActivityOptions(Acitivity选项)
6)RemoteViews (实现Notification中view的自定义)
setLatestEventInfo(状态栏通知,定义当展开通知列表的时候,如何呈现通知,定义单击通知的时候,如何处理单击)不推荐使用
?Toast (Toast 通知)
1)对象构建: 两种方式(构造方法,makeText)
2)设置view及其显示位置
?Setting
e)自定义view (直接或间接继承view,重写onDraw,自定义属性,资源回收)
?自定义view (官方给定的view不能完全满足需求)
1)直接或间接的继承view
a)class MyView extends View{}
b)class MyEdit extends EditText{}
2)重写onDraw方法实现图形的绘制
3)自定义view在xml中属性的配置。
---------------------------------------------------
Android Canvas and Drawables Android
f) 动画(属性动画,帧动画,补间动画)
Android 中的动画Day08-2
相关API
Property Animation故名思议就是通过动画的方式改变对象的属性了,我们首先需要了解几个属性:
Duration动画的持续时间,默认300ms。
Time interpolation:时间差值,乍一看不知道是什么,但是我说LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定义动画的变化率。
Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。
Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。
Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。
相关的类
ObjectAnimator 动画的执行类,后面详细介绍
ValueAnimator 动画的执行类,后面详细介绍
AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
AnimatorInflater 用户加载属性动画的xml文件
TypeEvaluator 类型估值,主要用于设置动画操作属性的值。
TimeInterpolator 时间插值,上面已经介绍。
总的来说,属性动画就是,动画的执行类来设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性。
在android中动画的实现方式有两种类型:
1.view Animation (应用在view中的一种动画)
1)tween animation补间动画 (在res/anim目录中创建:四种+set)
Alpha实现view对象的淡入淡出
Scale实现view对象的缩放功能
Translate实现view对象的平移
Rotate实现view对象的旋转
Background背景Duration持续时间Target目标
Rotation旋转Translation平移Scale比例
onWindowFocusChanged窗口焦点发生变化时执行此方法
Tweened Animations的分类
1、Alpha:淡入淡出效果
2、Scale:缩放效果
3、Rotate:旋转效果
4、Translate:移动效果
Animation的四个子类:
AlphaAnimation、TranslateAnimation、ScaleAnimation、RotateAnimation
使用TweenedAnimations的步骤:
1.创建一个AnimationSet对象(Animation子类);
2.增加需要创建相应的Animation对象;
3.更加项目的需求,为Animation对象设置相应的数据;
4.将Animatin对象添加到AnimationSet对象当中;
5.使用控件对象开始执行AnimationSet。
代码:
//创建一个AnimationSet对象,参数为Boolean型,
//true表示使用Animation的interpolator,false则是使用自己的
AnimationSet animationSet = new AnimationSet(true);
//创建一个AlphaAnimation对象,参数从完全的透明度,到完全的不透明
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
//设置动画执行的时间
alphaAnimation.setDuration(500);
//将alphaAnimation对象添加到AnimationSet当中
animationSet.addAnimation(alphaAnimation);
//使用ImageView的startAnimation方法执行动画
image.startAnimation(animationSet);
Tween Animations的通用方法
1、setDuration(long durationMills)
设置动画持续时间(单位:毫秒)
2、setFillAfter(Boolean fillAfter)
如果fillAfter的值为true,则动画执行后,控件将停留在执行结束的状态
3、setFillBefore(Boolean fillBefore)
如果fillBefore的值为true,则动画执行后,控件将回到动画执行之前的状态
4、setStartOffSet(long startOffSet)
设置动画执行之前的等待时间
5、setRepeatCount(int repeatCount)
设置动画重复执行的次数
在代码中使用Animations可以很方便的调试、运行,但是代码的可重用性差,重复代码多。同样可以在xml文件中配置Animations,这样做可维护性变高了,只不过不容易进行调试。
在xml中使用Animations步骤
1.在res文件夹下建立一个anim文件夹;
2.创建xml文件,并首先加入set标签,更改标签如下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
</set>
3.在该标签当中加入rotate,alpha,scale或者translate标签;
4.在代码当中使用AnimationUtils当中装载xml文件,并生成Animation对象。因为Animation是AnimationSet的子类,所以向上转型,用Animation对象接收
AnimationSet的具体使用方法
1.AnimationSet是Animation的子类;
2.一个AnimationSet包含了一系列的Animation;
3.针对AnimationSet设置一些Animation的常见属性(如startOffset,duration等),可以被包含在AnimationSet当中的Animation集成;
例:一个AnimationSet中有两个Animation,效果叠加
方法一:
classDoubleButtonListener implements OnClickListener {
public void onClick(View v) {
// 使用AnimationUtils装载动画配置文件
Animation animation = AnimationUtils.loadAnimation(
Animation2Activity.this, R.anim. doubleani);
// 启动动画
image.startAnimation(animation);
}
}
方法二:
classDoubleButtonListener implements OnClickListener {
public void onClick(View v) {
AnimationSet animationSet = new AnimationSet(true);
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
RotateAnimation rotateAnimation = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f);
rotateAnimation.setDuration(1000);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(alphaAnimation);
image.startAnimation(animationSet);
}
}
扩展:Interpolator的具体使用方法
Interpolator定义了动画变化的速率,在Animations框架当中定义了一下几种Interpolator
AccelerateDecelerateInterpolator:在动画开始与结束的地方速率改变比较慢,在中间的时候速率快。
AccelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始加速
CycleInterpolator:动画循环播放特定的次数,速率改变沿着正弦曲线
DecelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始减速
LinearInterpolator:动画以均匀的速率改变
ActivityOptions活动的选择(动画过度用)
makeCustomAnimation定制动画
(Context context所在环境,int enterResId进入界面id, int exitResId退出id)
~.start()动画执行
2)frame animation逐帧动画 (res/drawable目录下创建的一个animation-list元素)
是一帧一帧的格式显示动画效果。类似于电影胶片拍摄的手法。
用<item/>
必须以<animation-list>为根元素,以<item>表示要轮换显示的图片,duration属性表示各项显示的时间。XML文件要放在/res/drawable/目录下。
注意:
1.要在代码中调用Imageview的setBackgroundResource方法,如果直接在XML布局文件中设置其src属性当触发动画时会FC。
2.在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次。
3.最后一点是SDK中提到的,不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()。
3)Property Animation 属性动画(3.0,应用在任意对象上)
(解释:属性动画,这个是在Android 3.0中才引进的,以前学WPF时里面的动画机制好像就是这个,它更改的是对象的实际属性,在View Animation(Tween Animation)中,其改变的是View的绘制效果,真正的View的属性保持不变,比如无论你在对话中如何缩放Button的大小,Button的有效点击区域还是没有应用动画时的区域,其位置与大小都不变。而在Property Animation中,改变的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。)
应用在对象属性上的动画必须有对应的set方法
1)资源对象: ValueAnimator,ObjectAnimator,AnimatorSet
a)ValueAnimator 对象不能直接作用于对象属性上,可以监听动画值的变化
主要用于对值进行了一个平滑的动画过渡
调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例
b)ObjectAnimator 可以直接作用于对象属性上
ObjectAnimator会更加常用一些,但是它其实是继承自ValueAnimator的
ObjectAnimator.ofFloat(textview, "活动属性", 起始, 结束);
c)AnimatorSet 实现多个属性动画的顺序或一起操作
after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行
anim.addListener接口方法实现
onAnimationStart()方法会在动画开始的时候调用,
onAnimationRepeat()方法会在动画重复执行的时候调用,
onAnimationEnd()方法会在动画结束的时候调用,
onAnimationCancel()方法会在动画被取消的时候调用。
在XML文件中我们一共可以使用如下三种标签:
<animator> 对应代码中的ValueAnimator
<objectAnimator> 对应代码中的ObjectAnimator
<set> 对应代码中的AnimatorSet
在Property Animation中,可以对动画应用以下属性:
•Duration:动画的持续时间
•TimeInterpolation:属性值的计算方式,如先快后慢
•TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值
•Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
•Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
•Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响
2)资源文件:res/animator/xxx.xml
资源文件的加载要借助AnimatorInflater对象的loadAnimator方法
**Property Animation的工作方式
对于下图的动画,这个对象的X坐标在40ms内从0移动到40 pixel.按默认的10ms刷新一次,这个对象会移动4次,每次移动40/4=10pixel。
也可以改变属性值的改变方法,即设置不同的interpolation,在下图中运动速度先逐渐增大再逐渐减小
下图显示了与上述动画相关的关键对象
ValueAnimator 表示一个动画,包含动画的开始值,结束值,持续时间等属性。
ValueAnimator封装了一个TimeInterpolator,TimeInterpolator定义了属性值在开始值与结束值之间的插值方法。
ValueAnimator还封装了一个TypeAnimator,根据开始、结束值与TimeIniterpolator计算得到的值计算出属性值。
ValueAnimator根据动画已进行的时间跟动画总时间(duration)的比计算出一个时间因子(0~1),然后根据TimeInterpolator计算出另一个因子,最后TypeAnimator通过这个因子计算出属性值
ValueAnimator
ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。应用Property Animation有两个步聚:
计算属性值
根据属性值执行相应的动作,如改变对象的某一属性。
ValuAnimiator只完成了第一步工作,如果要完成第二步,需要实现ValueAnimator.onUpdateListener接口,这个接口只有一个函数onAnimationUpdate(),在这个函数中会传入ValueAnimator对象做为参数,通过这个ValueAnimator对象的getAnimatedValue()函数可以得到当前的属性值
ValueAnimator.AnimatorUpdateListener
onAnimationUpdate() //通过监听这个事件在属性的值更新时执行相应的操作,对于ValueAnimator一般要监听此事件执行相应的动作,不然Animation没意义,在ObjectAnimator(继承自ValueAnimator)中会自动更新属性,如无必要不必监听。在函数中会传递一个ValueAnimator参数,通过此参数的getAnimatedValue()取得当前动画属性值。
可以继承AnimatorListenerAdapter而不是实现AnimatorListener接口来简化操作,这个类对AnimatorListener中的函数都定义了一个空函数体,这样我们就只用定义想监听的事件而不用实现每个函数却只定义一空函数体
ObjectAnimator
继承自ValueAnimator,要指定一个对象及该对象的一个属性,当属性值计算完成时自动设置为该对象的相应属性,即完成了Property Animation的全部两步操作。实际应用中一般都会用ObjectAnimator来改变某一对象的某一属性,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:
对象应该有一个setter函数:set<PropertyName>(驼峰命名法)
如上面的例子中,像ofFloat之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的getter方法:get<PropertyName>
如果有getter方法,其应返回值类型应与相应的setter方法的参数类型一致。
如果上述条件不满足,则不能用ObjectAnimator,应用ValueAnimator代替。
TypeEvalutors
根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值,android提供了以下几个evalutor:
IntEvaluator:属性的值类型为int;
FloatEvaluator:属性的值类型为float;
ArgbEvaluator:属性的值类型为十六进制颜色值;
TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。
TimeInterplator
Time interplator定义了属性值变化的方式,如线性均匀改变,开始慢然后逐渐快等。在Property Animation中是TimeInterplator,在View Animation中是Interplator,这两个是一样的,在3.0之前只有Interplator,3.0之后实现代码转移至了TimeInterplator。Interplator继承自TimeInterplator,内部没有任何其他代码。
AccelerateInterpolator 加速,开始时慢中间加速
DecelerateInterpolator 减速,开始时快然后减速
AccelerateDecelerateInterolator 先加速后减速,开始结束时慢,中间加速
AnticipateInterpolator 反向 ,先向相反方向改变一段再加速播放
AnticipateOvershootInterpolator 反向加回弹,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
CycleIinterpolator 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 * mCycles * Math.PI * input)
LinearInterpolator 线性,线性均匀改变
OvershottInterpolator 回弹,最后超出目的值然后缓慢改变到目的值
TimeInterpolator 一个接口,允许你自定义interpolator,以上几个都是实现了这个接口
当Layout改变时应用动画
ViewGroup中的子元素可以通过setVisibility使其Visible、Invisible或Gone,当有子元素可见性改变时(VISIBLE、GONE),可以向其应用动画,通过LayoutTransition类应用此类动画:
transition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);
通过setAnimator应用动画,第一个参数表示应用的情境,可以以下4种类型:
APPEARING 当一个元素在其父元素中变为Visible时对这个元素应用动画
CHANGE_APPEARING 当一个元素在其父元素中变为Visible时,因系统要重新布局有一些元素需要移动,对这些要移动的元素应用动画
DISAPPEARING 当一个元素在其父元素中变为GONE时对其应用动画
CHANGE_DISAPPEARING 当一个元素在其父元素中变为GONE时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用动画.
第二个参数为一Animator。
mTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
此函数设置动画延迟时间,参数分别为类型与时间。
playTogether
使用playTogether两个动画同时执行,当然还有playSequentially依次执行
过渡的类型一共有四种:
LayoutTransition.APPEARING 当一个View在ViewGroup中出现时,对此View设置的动画
LayoutTransition.CHANGE_APPEARING 当一个View在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画
LayoutTransition.DISAPPEARING 当一个View在ViewGroup中消失时,对此View设置的动画
LayoutTransition.CHANGE_DISAPPEARING 当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画
LayoutTransition.CHANGE 不是由于View出现或消失造成对其他View位置造成影响,然后对其他View设置的动画。
注意动画到底设置在谁身上,此View还是其他View。
扩展:LayoutAnimationsController
1、什么是LayoutAnimationsController
LayoutAnimationsController可以用于实现使多个控件按顺序一个一个的显示。
1)LayoutAnimationsController用于为一个layout里面的控件,或者是一个ViewGroup里面的控件设置统一的动画效果。
2)每一个控件都有相同的动画效果。
3)控件的动画效果可以在不同的时间显示出来。
4)LayoutAnimationsController可以在xml文件当中设置,以可以在代码当中进行设置。
2、在xml当中使用LayoutAnimationController
1)在res/anim文件夹下创建一个名为list_anim_layout.xml文件:
android:delay - 动画间隔时间;子类动画时间间隔 (延迟) 70% 也可以是一个浮点数 如“1.2”等
android:animationOrder - 动画执行的循序(normal:顺序,random:随机,reverse:反向显示)
android:animation – 引用动画效果文件
备注:要将整个动画效果设置到LinerLayout中,可以这样设置:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layoutAnimation="@anim/list_anim_layout"
>
3、在代码当中使用LayoutAnimationController
1)去掉main.xml中的android:layoutAnimation="@anim/list_anim_layout"/>
2)创建一个Animation对象:可以通过装载xml文件,或者是直接使用Animation的构造方法创建Animation对象
Animation animation = (Animation) AnimationUtils.loadAnimation(
Animation2Activity.this, R.anim.list_anim);
3)创建LayoutAnimationController对象:
LayoutAnimationController controller = new LayoutAnimationController(animation);
4)设置控件的显示顺序以及延迟时间
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
controller.setDelay(0.5f);
5)为ListView设置LayoutAnimationController属性:
listView.setLayoutAnimation(controller);
扩展:AnimationListener
1、什么是AnimationListener
1).AnimationListener是一个监听器,该监听器在动画执行的各个阶段会得到通知,从而调用相应的方法;
2).AnimationListener主要包括如下三个方法:
n ·onAnimationEnd(Animation animation) - 当动画结束时调用
n ·onAnimationRepeat(Animation animation) - 当动画重复时调用
n ·onAniamtionStart(Animation animation) - 当动画启动时调用
扩展:可以在Activity中动态添加和删除控件,方法是:
1)取到那个Layout
viewGroup = (ViewGroup)findViewById(R.id.layout);
2)添加时,先创建对象,然后添加
ImageView newImageView = new ImageView(
Animation2Activity.this);
newImageView.setImageResource(R.drawable.an);
viewGroup.addView(newImageView,
new LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));
3)删除时,直接删除。
viewGroup.removeView(imageView);
g)绘制(Bitmap,Canvas,Paint)
?画布和图形绘制Day08-3
1.Bitmap对象 (位图对象,由若干像素点组成)
1)Bitmap.createBitmap(....)
2)BitmapFactory,...
2.Canvas对象(类似一个画板对象,此对象上可以放新创建的bitmap);
1)关联Bitmap
Canvas c=new Canvas(bitMap);
2)执行绘制动作
3)将绘制的bitMap添加到对应的view上
3.Paint
--------------------------------------------------
作业
1.总结
2.预习(数据存储)
Day09-1内容回顾
--------------------------------------------------
1.Layouts
1)Common Layout
a)LinearLayout,RelativeLayout,FrameLayout,GridLayout
b)ScrollView,HorizontalScrollView
2)Adapter Layout
a)ListView,ExpandableListView
b)GridView,Spinner
c)ViewPager,.....
2.InputControls
1)Buttons(Button,ImageButton,Switch,RadioButton,CheckBox)
2)TextFields (EditText,AutoCompleteTextView)
3)Bar (ProgressBar,SeekBar,RatingBar)
4)....
3.InputEvents
1)onClick
2)onTouch
3)onKey
4)onChecked
5)onFocus
掌握:事件类型及事件传递机制
4.UI Commons
1)Menus
2)ActionBar
3)Dialog
4)Notification
5)Toast
5.自定义view(官方给定的view不满足我们需求时)
1)直接继承view重写onDraw方法
2)继承view的子类重写onDraw方法
6.Animations
1)View Animation
a)tween animation (四种:res/anim)
b)frame animation (res/drawable)
2)Property Animation(3.0)
a)ValueAnimator,ObjectAnimator
b)AnimatorSet
帮助类: AnimatorInflater.loadXXXX
7.Canvas and drawable
---------------------------------------------------
2.Android Data Store
Data Store (数据存储) Day09-2
1)外部存储(外置sdcard)
存储文件file
1.External Storeage(外部存储):外置sdcard
1)直接IO存储
a)存储状态(Environment.getExternalStorageState())
b)存储目录(Environment.getExternalStorageDirectory())
存储子目录Environment.getExternalStoragePublicDirectory
c)存储大小 StatFs sf=new StatFs(sdcard.getPath());
借助此对象获得磁盘的存储信息StatFs (sdcard.getPath())
获得总块数getBlockCount();
获得可用块数getAvailableBlocks();
获得空闲块数getFreeBlocks();
获得每块的大小(字节)getBlockSize();
d)存储权限 android.permission.WRITE_EXTERNAL_STORAGE
说明:如何获得应用程序外置sdcard私有存储目录,
此目录中的数据有何特点?
a)getExternalCacheDir()
b)getExternalFilesDir(String type)
文件写入外置硬盘方法
获得res/raw目录的a文件的输入流
InputStream in=getResources()//Context
.openRawResource(R.raw.a);
将a写到外置sdcard
1.外置sdcard的存储状态()if语句判断sdcard状态
2.外置sdcard的存储目录大小,try,catch语句判断空间状态
应用程序公有存储目录(Environment.getExternalStoragePublicDirectory())
应用程序在sdcard的私有存储目录(数据在应用卸载时会被删除)(getExternalFilesDir())
3.假如有空间则将a文件写入到sdcard的pictures目录
2)内部存储(内置sdcard)
2.Internal storeage (内部存储):内置sdcard
直接IO存储 (data/data/项目包/files):
私有,应用卸载数据会被删除
a)openFileInput
b)openFileOutput
获得资源getResources().openRawResource(R.raw.a);
说明:直接IO存储时缓存目录的获得:getCacheDir()
3)偏好设置(data/data/项目/share_prefs/.xml)
偏好存储(SharedPreferences):记录用户喜好
定义
SharedPreferences是Android平台上一个轻量级的存储类,简单的说就是可以存储一些我们需要的变量信息。
用处
2个activity 之间的数据传递除了可以他通过intent来传递,还可以使用SharedPreferences来共享数据的方式
SharedPreferences 用法很简单,如果你想要编辑SharedPreferences中的内容就需要用到editor对象。
addPreferencesFromResource添加选项来自资源
getSharedPreferences (String name, int mode)
方法得到一个sharedpreferences对象,参数name是preference文件的名字,mode则是方式,默认为0。
SharedPreferences基本方法
public abstract boolean contains (String key) :检查是否已存在该文件,其中key是xml的文件名。
edit():为preferences创建一个编辑器Editor,通过创建的Editor可以修改preferences里面的数据,但必须执行commit()方法。
getAll():返回preferences里面的多有数据。
getBoolean(String key, boolean defValue):获取Boolean型数据
getFloat(String key, float defValue):获取Float型数据
getInt(String key, int defValue):获取Int型数据
getLong(String key, long defValue):获取Long型数据
getString(String key, String defValue):获取String型数据
registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener):注册一个当preference发生改变时被调用的回调函数。
unregisterOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener):删除当前回调函数。
a)SharedPreferences (data/data/项目/share_prefs/XXX.xml)
获取SharedPreferences的两种方式:
1 调用Context对象的getSharedPreferences()方法
2 调用Activity对象的getPreferences()方法
两种方式的区别:
调用Context对象的getSharedPreferences()方法获得的SharedPreferences对象可以被同一应用程序下的其他组件共享.
调用Activity对象的getPreferences()方法获得的SharedPreferences对象只能在该Activity中使用.
SharedPreferences的四种操作模式:
Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容
Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件.
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件.
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取.
MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入.
preferences.edit();编辑数据(用其对象.put加入)
commit();用于把事务所做的修改保存到数据库
preferences.getString获取数据
b)Editor(存储key/value)
SharedPreferences.Editor
1.简介
用于修改SharedPreferences对象的内容,所有更改都是在编辑器所做的批处理,而不是复制回原来的SharedPreferences或持久化存储,直到你调用commit(),才将持久化存储。
2.重要方法
clear():清除内容。
commit():提交修改
remove(String key):删除preference
扩展:系统设置实现(参考uicommons/settings)
下面arrays在配置的时候,必须为string-arry,否则会出现bug;
prefereneceActivity的addPreferencesFromResource方法
在3.0及更高版本中被弃用了,所以查了一下,总结如下:
1、在API 11以前,系统是支持prefereneceActivity的addPreferencesFromResource方法的。
2、在API 11以后,系统推荐PreferenceFragment的addPreferencesFromResource方法。
3、也就是addPreferencesFromResource方法不变,但是高级版本推荐使用prefereneceFragment类代替prefereneceActivity类。
4)SQLite存储(DBMS,DB,table,view,sql,adb指令)
SQLite存储
DBMS
a)SQLite 是一个开源,轻量级关系型数据库系统,底层使用c/c++实现。
b)SQLite 一般应用于便携式设备,无需启动服务。
c)Adnroid 内置了SQLite 数据库,可以存储联系人信息,媒体库信息,备忘录信息,。。。
d)Android SQLite 相关 API(SQliteDatabase,....)
e)SQLite 中SQL的应用? (DDL,DML,DCL)
打开或创建一个数据库
sdb=openOrCreateDatabase(
"contacts.db",//数据库名
Context.MODE_PRIVATE,//操作模式
null);// 游标工厂CursorFactory
//在数据库中创建表(表是数据库的最基本存储单元)createTable();
private void createTable(){
//语法: create table 表名(字段名 字段类型 约束,...)
String sql=
"create table if not exists contact(" +//创建表如果不存在联系
"_id integer primary key," +//id的整数键
"phone text not null," +//手机文本非空
"name text )";//文本名//DDL(数据定义语言)
//将sql语句发送到SQLite数据库端执行
sdb.execSQL(sql);//执行数据库
Log.i("TAG", "table create ok!");
}
增加
private void insertTable01(){
String sql=
"insert into contact " +
"values(2,'138','B')";//DML
sdb.execSQL(sql);
Log.i("TAG", "insert ok!");
}
写入数据的第二种方法
private void insertTable02(){
ContentValues cv=new ContentValues();
cv.put("_id", 3);//key必须为列的名字
cv.put("phone","189");//非空列必须给值
cv.put("name", "AD");//这一列可以不给值
sdb.insert("tableName",//table
null,//nullColumnHack
cv);
}
查询
private void queryTable01(){
String sql=
"select * from contact where phone=? and _id=? order by _id desc";
//执行查询操作
Cursor c=//Cursor封装了一个访问数据集的一个指针对象
sdb.rawQuery(sql,new String[]{"139","1"});
//处理结果
while(c.moveToNext()){//移动指针
Log.i("TAG","id="+
c.getInt(c.getColumnIndex("_id")));
Log.i("TAG","phone="+c.getString(c.getColumnIndex("phone")));
Log.i("TAG","name="+c.getString(c.getColumnIndex("name")));
Log.i("TAG", "=============");
}
//释放资源
c.close();
}
查询的第二种方法
private void queryTable02(){
Cursor c=
sdb.query("contact",
new String[]{"_id","phone"},//columns ,null表示所有列
"_id=?", //selection选择
new String[]{"2"},//selectionArgs选择自变量
null, //group by
null, //having
"_id desc");//order by _id desc
while(c.moveToNext()){
//????
}
c.close();
}
修改
private void updateTable01(){
//String sql="update contact set phone='137'";
//sdb.execSQL(sql);
String sql=
"update contact " +
"set phone=?,name=? where _id=?"; //?为占位符
sdb.execSQL(sql,
new Object[]{"137","AA",1});
Log.i("TAG", "update ok!");
}
修改的第二种方法
private void updateTable02(){
ContentValues cv=//值对象
new ContentValues();
cv.put("phone", "137");
cv.put("name", "AA");
sdb.update("contact",//tableName
cv,//ContentValues
"_id=?",//whereClause
new String[]{"1"});
}
删除
private void deleteTable01(){
//删除所有数据
//String sql=
//"delete from contact";
//sdb.execSQL(sql);
//删除指定id的记录
String sql=
"delete from contact " +
"where _id=?";
sdb.execSQL(sql,
new Object[]{2});//将2传给?
Log.i("TAG", "delete ok!");
}
删除的第二种方法
private void deleteTable02(){
sdb.delete("contact",//表名
"_id=?",//whereClause
new String[]{"2"});//底层帮你拼接SQL
Log.i("TAG", "delete ok");
}
---------------------------------------------------
作业
1.总结
2.尝试完成一个设置页面
3.尝试完成一个SimpleCursorAdapter应用。
4.尝试借助SQLiteOpenHelper工具类实现一个SQLite数据的存储。
内容回顾(数据存储) Day10-1
1.File 类(代表磁盘中的文件或文件夹)
2.InputStream/OutputStream(两大字节流)
3.Reader/Writer(两大字符流,特殊的字节流)
4.RandomAccessFile(随机读写)
1.外部存储(外置sdcard)
1)直接IO存储(借助IO对象读写磁盘数据)
a)存储状态(Environment)
b)存储目录(应用程序公有(Environment),应用程序私有(context))
c)存储大小(StatFs)
d)存储权限(WRITE_EXTERNAL_STORAGE)
2.内部存储(内置sdcard)
1)直接IO存储(openFileInput,openFileOutput)
2)偏好设置存储(SharedPreferences)
3)SQLite数据库存储(SQLiteDatabase)
---------------------------------------------------
SQLite存储中SQLiteOpenHelper对象的应用Day10-2
SQLiteOpenHelper是一个工具类,此类中提供
一些便于操作SQLite数据库的方法。它是一个抽
象类,我们在使用时一般会构建此类的一个子类
对象。
常用方法:
1)onCreate
2)onUpgrade
3)getReadableDatabase
4)getWriteDatabase()
案例:备忘录(学车日记)***
--------------------------------------------------
作业
1.总结
2.完成备忘录案例
3.扩展(列表页面加上查询,详情页面显示一个textClock)
adb控制台通过adb操作SQLite Day11-1
--------------------------------------------------
进入模拟器linux系统
adb shell
切换到当前项目的数据库目录
cd data/data/com.example.elts/databases
打开或创建数据库
sqlite3 notes.db
显示当前数据库有哪些表
.tables
执行表的DML操作
select * from notetab; 选择
delete from notetab where _id=10;删除
update notetab set content='AAA' where _id=1;更新
insert into notetab values (null,'content03','2016-12-12');插入
退出sqlite系统:
.quit
退出linux系统:
exit
----------------------------------------------------
3.Android Thread,Message
Android 中的线程应用Day11-2
1.Android 线程应用基础(Thread,Runnable)
2.Android 中的消息模型(Message,MessageQueue,Handler,Looper)
3.Android 中的异步任务(AsyncTask)
4.Android 中线程池的应用(Executor,ExecutorService)
----------------------------------------------------
a)Android 中的线程基础(Java中线程应用)
Android 线程应用基础Day11-3
1.何为线程(Thread)?what
线程(Thread)是进程中的一串连续的动作,是进程中的一个顺序的执行流,在Java中表现,形式为Thread类型的对象。
进程:正在运行的一个程序,有自己独立的内存空间,可以启动多个线程并发执行。在一个多任务操作系统中允许多个进程并发执行。
多线程:(MultiThread)通常指多串连续动作的并发执行
非主线程的线程都称为工作线程
说明:真正的并发是要构建于过个处理器的基础
之上的。
运行原理
一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。
随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。
2.多线程的优势,劣势?
1)优势:可以提高系统的并发处理能力,改善用户体验。
2)劣势:可能会导致线程安全问题。
3.Java 中线程对象的创建及应用?
Java 中线程的类型为Thread类型,我们可以通过此类型构建线程对象,并调用对象的start方法启动线程,线程启动以后会执行Thread类的run方法,我们可以将自己要执行的任务写在run方法中。
构建线程对象时,常用的构造方法:
1)Thread();
2)Thread(Runnable r);
4.线程状态及相关方法应用?
线程状态:
1)新建状态: new Thread()
2)就绪状态: start()
3)运行状态: run()
4)阻塞状态: sleep(3000),i/o阻塞
5)死亡状态: run()方法执行结束
Java中线程框架?
Thread 类(通过此类构建线程对象)
Runnable 接口(通过此接口定义线程任务)
Java中线程的构建?
new Thread(){重写run方法}(相当于new 线程对象的子类)
new Thread(new Runnable接口的实现类){}
线程方法:
1)start//启动线程
2)run
3)sleep//设置线程处于休眠状态
4)setName (设置线程名字)
5)setPriority (设置优先级)
6)currentThread():获得当前线程
7)getName()....
8)setDaemon(true) 设置线程为守护线程
9) interrupt() //中断线程
10)yeild() //暂停当前线程,执行其他线程
11)join() //插入,等待线程终止
............
Java中的线程框架
线程基类:SuperClass
接口:Runnable
返回当前线程的名称:Thread.currentThread().getName()
线程的名称是由:Thread-编号定义的。编号从0开始。
线程要运行的代码都统一存放在了run方法中。
线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)
start方法:1)、启动了线程;2)、让jvm调用了run方法。
创建线程的第一种方式:继承Thread ,由子类复写run方法。
步骤:
1,定义类继承Thread类;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,通过创建Thread类的子类对象,创建线程对象;
4,调用线程的start方法,开启线程,并执行run方法。
创建线程的第二种方式:实现一个接口Runnable。
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,通过Thread类创建线程对象;
4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
Ticket t = new Ticket();
/*
直接创建Ticket对象,并不是创建线程对象。
因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。
所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。
*/
Thread t1 = new Thread(t); //创建线程。
/*
只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联
为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。
*/
t1.start();
为什么要有Runnable接口的出现?
1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。
因为实现Runnable接口可以避免单继承的局限性。
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。
所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。
b)Android 中的线程同步(Java中的线程同步)
5.线程的同步?(重点,难点)
线程同步指多个线程在共享数据集上的互斥,协作(通讯)。
同步的目的是为了保证数据的安全,让数据更符合我们
的业务要求。
多线程安全问题的原因:
通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。
涉及到两个因素:
1,多个线程在操作共享数据。
2,有多条语句对共享数据进行运算。
原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。
解决安全问题的原理:
只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
互斥的实现方式: 如何进行多句操作共享数据代码的封装呢?
java中提供了一个解决方式:就是同步代码块。
1)同步代码块synchronized(对象锁){被锁的内容}
2)同步方法(静态,非静态)
synchronized static void method(对象锁为类对象){ // 任意对象都可以。这个对象就是锁。需要被同步的代码;}
synchronized void method(对象锁为this){ // 任意对象都可以。这个对象就是锁。需要被同步的代码;}
说明:
1)加锁以后会影响系统的执行效率
2)在保证数据安全的情况下,锁的范围越小越好。
协作(同步)的实现方式:(保证业务的合理性)
协作是构建在互斥的基础之上,然后借助Object
对象的wait,notifiy,notifyall方法实现线程之间
的通讯。
好处:解决了线程安全问题。
弊端:相对降低性能,因为判断锁需要消耗资源,产生了死
定义同步是有前提的:
1,必须要有两个或者两个以上的线程,才需要同步。
2,多个线程必须保证使用的是同一个锁。
同步的第二种表现形式:
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
同步函数是用的哪个锁呢?
通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。
当同步函数被static修饰时,这时的同步用的是哪个锁呢?
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class
同步代码块和同步函数的区别?
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。
线程间通信:思路:多个线程在操作同一个资源,但是操作的动作却不一样。
1:将资源封装成对象。
2:将线程执行的任务(任务其实就是run方法。)也封装成对象。
1)wait:等待(阻塞当前线程,并释放对象锁)
2)notifiy/notifyall:通知具备相同锁的对象开始执行。
说明:
1)wait,notifiy,notifyall必须用于同步代码块或同步方法内部
2)wait,notifiy,notifyall需要由对象锁调用。
。
更新标题(UI更新)
setTitle(str);(UI更新应在主线程执行)
Lock接口:
多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
---------------------------------------------------
作业
1.总结
2.完成欢迎页面,新手指导,主页面的整合。
3.完成单例模式的至少四种写法?
4.完成线程互斥,协作的课堂案例。
5.预习Android中的消息模型。
c)Android 中的消息模型(Message,MessageQueue,handle,looper,)
Android 中的消息通讯Day12-2
1.Android 中线程的应用机制?
1)Android 中所有的耗时操作应在工作线程执行。
2)Android 中所有的UI操作应该在主线程(UI线程)执行。
FAQ?
1)主线程执行执行耗时操作好吗? 不好,这样会阻塞UI操作。
2)工作执行完耗时操作,假如有数据要传递给主线程,那如何实现?
2.Android 中多线程应用时的消息模型?
使用Android中消息模型的目的是尽量不要去阻塞
主线程,然后给用户一种比较友好的体验。
Android 中消息模型关注对象:
1)Message :数据的载体
2)MessageQueue: 存储多个消息对象
3)Looper:迭代消息队列,取出消息。
4)Handler:发送,处理消息队列中的消息。
Android中消息模型关注对象伪代码实现:
class Message{
Object obj;数据
int what; 数据要用于做什么
.....
}
class MessageQueue{
Object[] message=new Object[5];
public void put(Message msg){}
public Message tabke(){}
}
class Looper{
private MessageQueue msgQ;
public void loop(){
while(true){....}
}
}
class Handler{
public void sendMessage(Message msg){}
public void handleMessage(Message msg){}
}
3.Android 中消息模型应用案例?
1)工作线程发消息给主线程?
2)主线程发消息给主线程?
3)主线程发消息给工作线程?
记住:
1)主线程在启动时已经默认创建了Looper和消息队列对象
2)给谁发消息就让handler关联谁的Looper.
3)handler的handleMessage方法运行在哪个线程由handler
关联的looper而定。
4.Android 中消息模型对象相关方法总结?
1)Message
数据的载体
(obj数据,
what数据要用于做什么,
obtain()获得一个消息,....)
2)MessageQueue存储多个消息对象
3)Looper(prepare()创建一个looper,并绑定到当前线程,
myLooper()获得当前线程Looper,
loop(),
getMainLooper()默认关联当前线程looper,
quit())
4)Handler(sendMessage消息发送给谁,取决于handler关联的looper,
handleMessage可以执行消息的处理操作,sendXXXXX)
------------------------------------------------------------
作业
1.总结
2.完成课堂案例,完善项目
3.尝试自己构建一个消息模型架构
4.预习AsyncTask对象(异步任务对象)
1主线程给主线程发消息
通过主线程给主线程发消息
消息的分类:系统消息、用户自定义消息
主线程中发消息要借助主线程Handler对象。
|-->当我们发送自定义消息时,Handler需要我们自己构建。
|-->一个线程可能关联很多个handler对象
主线程发送的消息会存储在主线程的MQ中。
主线程中的消息队列的管理者是主线程的Looper。
|-->此Looper内置一个阻塞式无限循环。
|-->此循环的目的是遍历和Looper相关联的MQ对象
|-->此Looper负责从MQ中获取消息,并将消息交给Handler。
FAQ:
1)主线程的Looper对象何时创建?系统启动时
此Looper对象创建以后会绑定到当前线程中(借助ThreadLocal)
主线程中的MQ何时创建?Looper对象创建时,MQ自动创建。
MQ自定义消息何时创建?如何创建?
|-->需要时创建。
|-->创建方法?
Message msg=Message.obtain();
Message msg=handler.obtainMessage();
主线程中的Handler对象何时创建?
|-->需要时创建。
|-->创建方式?
匿名内部类:new Handler(){重写HandleMessage方法}
构建handler子类
2 子线程给主线程发送消息
方案:在子线程中获取主线程的Handler对象
如何获取:
在主线程构建Handler对象。
在子线程中构建主线程Handler对象??
handler=new Handler(Looper.getMainLooper());
3 子线程如何创建Looper对象
子线程中创建Looper的理由?
子线程中默认没有Looper对象
子线程中需要使用子线程的handler对象发送消息
子线程创建Looper的方式?创建Looper时自动构建MQ
Looper.prepare();//构建过程?先从当前线程取,若无则创建。创建后绑定到当前线程。
...构建handler
Looper.loop();
FAQ:
子线程的handler构建时需要子线程的Looper
子线程的Handler对象发送消息是发送到子线程的MQ
子线程在处理消息时不能更新主线程界面。
4 主线程如何获取子线程的Handler对象
方案:设法在主线程中获取子线程的Looper对象。
作业:
实现主线程给子线程发消息。
主线程给子线程发消息,子线程收到主线程数据后,更新主线程的数据(不能更新界面)。主线程通过子线程更新的数据更新界面。
两个子线程之间互发消息。(扩展实现)
使用工作线程模拟下载过程,要求带进度条。
5内容回顾
四个对象
Handler:在线程中发送消息
MQ:消息队列,负责存储消息
Message:代表一个消息对象
Looper:负责发现并取出消息,交给Handler处理
消息的发送与处理
主线程给主线程发消息:
借助主线程的Handler
主线程给子线程发消息:
在主线程中获取子线程的Handler,首先要在主线程中获取子线程的Looper。
子线程发消息给主线程:
要获取主线程的Handler。
在主线程构建Handler。
在子线程通过主线程的Looper构建Handler对象。
子线程发消息给子线程发消息
子线程A发消息给子线程B(在子线程A中获取子线程B的Handler)。
内容回顾(Android 中消息通讯) Day13-1
---------------------------------------------------
1.Android 中线程应用机制?
2.Android 中的消息模型?(四个核心对象)
3.Android 中的消息模型应用案例?
注意:
1)主线程默认有一个Looper,此Looper在创建时会创建一个消息队列。
2)工作线程默认没有Looper,需要时可以自己创建Looper.
3)执行消息通讯时给谁发消息就获得与谁的looper关联的handler.
4)每个线程只能有一个Looper,但可以有多个与此looper关联的handler
知识点:自定义消息模式的构建.
----------------------------------------------------
Day13-2 Android消息模型相关对象方法总结
1.Message
1)obj
2)what
3)arg1
4)arg2
5)obtain(.......)
6)sendToTarget() message 从handler 类获取,从而可以直接向该handler 对象发送消息
Message msg = handler.obtainMessage();
msg.arg1 = i;
msg.sendToTarget();
2.MessageQueue
3.Looper
1)prepare()创建一个looper,并绑定到当前线程
2)myLooper()
3)loop
4)getMainLooper()
5)quit()
4.handler
1)sendMessage 是直接调用 handler 的发送消息方法发送消息,允许处理Message对象(Message里可以包含数据,)。
Message msg=new Message();
msg.arg1=i;
handler.sendMessage(msg);
2)sendEmptyMessage()只能放数据
3)post(...)在子线程中利用post(Runnable r)更新UI,原理和sendMessage()类似.
4)postDelay(....)
5)postAtTime();
6)postAtFrontOfQueue(....)
7)handleMessage(...)
8)XXXXX
一般来说在工作线程中执行耗时任务,当任务完成时,会返回UI线程,一般是更新UI。这时有两种方法可以达到目的。
一种是handler.sendMessage。发一个消息,再根据消息,执行相关任务代码。
另一种是handler.post(r)。r是要执行的任务代码。意思就是说r的代码实际是在UI线程执行的。可以写更新UI的代码。(工作线程是不能更新UI的)
d)Android 中的异步任务(AsyncTask)
异步任务对象(AsyncTask) Day14-1
---------------------------------------------------what when why how
异步任务定义
AsyncTask 是一个工具类,它封装了android中消息模型的的应用过程,用于简化消息传递及处理的方式,此类是一个抽象类,它内部定义的方法有的定义主线程,有的运行在工作线程,我们在使用此类时,通常要根据需要重写其中方法。
目的why
Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。
AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
abstract class AsyncTask<Params,Progress,Result>{}
•Params 启动任务执行的输入参数,比如HTTP请求的URL。
•Progress 后台任务执行的百分比。
•Result 后台执行任务最终返回的结果,比如String
class Task extends AsyncTask<String,Integer,Bitmap>{
Bitmap doInBackground(String ...params){...}
}
•execute 启动任务
•doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行(此方法运行在工作线程),完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
•onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行(此方法运行在主线程,在doInBackground方法之后执行),任务执行的结果作为此方法的参数返回
•onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行(此方法用于执行进度更新操作,它工作在主线程),用于显示任务执行的进度。
•onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法(在doInBackground之前执行),可以在这里显示进度对话框。
•onCancelled() 用户调用取消时,要做的操作
当我们在执行异步任务时,要构建异步任务对象,
然后调用对象的executeXXX方法执行任务。
进度条对象.setProgress(i);//此更新也是在主线程(底层在发消息)
publishProgress();//发布进度,此时会执行onProgressUpdate
Task01 task=new Task01();//构建异步任务对象,Task01是Task对象
task.execute();//执行异步任务对象
new DownTask().execute(pos); 每次点击会构建一个任务,然后在那个默认的工作线程中执行
new DownTask().downTask(pos); 适合多个任务顺序执行的场合
new DownTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pos); 如下方法适合多个任务并发执行的场合
异步任务常用方法:
1)
2)......
AsyncTask和Handler对比
1 ) AsyncTask实现的原理,和适用的优缺点
AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.
使用的优点:
l 简单,快捷
l 过程可控
使用的缺点:
l 在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
2 )Handler异步实现的原理和适用的优缺点
在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程)àthread(子线程)运行并生成Message-àLooper获取Message并传递给HandleràHandler逐个获取Looper中的Message,并进行UI变更。
使用的优点:
l 结构清晰,功能定义明确
l 对于多个后台任务时,简单,清晰
使用的缺点:
l 在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)
使用AsyncTask类,以下是几条必须遵守的准则:
•Task的实例必须在UI thread中创建;
•execute方法必须在UI thread中调用;
•不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
•该task只能被执行一次,否则多次调用时将会出现异常;
e)Android 中的线程池(Executor,Executors)
Android 中线程池的应用Day14-2
1.何为线程池?
1)内存中的一块区域
2)允许存储若干个可重用线程对象。
2.使用线程池的目的?
1)减少线程对象的创建次数,提高线程对象的利用率。
2)提高系统的整体执行性能。
3.Java中线程池的创建及应用?
1)Executors (工具类)
2)Executor(线程池对象)
a)ExecutorService(线程池接口类型,继承Executor)
a.1)execute(....)
a.2)submit(....)
b)ThreadPoolExecutor(具体线程池对象)
c)ScheduledExecutorService(继承ExecutorService)
c.1)scheduled(.....):延迟执行
c.2)scheduleAtFixedRate (按一定的间隔执行任务)
c.3)scheduleWithFixedDelay (按一定的间隔(任务执行时间+delay)执行任务)
扩展:Timer(内置一个线程对象,可以通过此线程对象顺序执行多个任务)
--------------------------------------------------
作业
1.总结(AsyncTask,Executor,Executors,ThreadLocal)
2.尝试完成(通过异步任务加载SQLite中数据显示):改写学习日记
3.尝试编写(异步加载大图片)
内容回顾Day15-1
----------------------------------------------------
1.异步任务(AsynTask)
1)启动方法
a)execute(Params ...params):采用默认线程池中的线程执行任务(默认线程池只有一个线程)
b)execute(Runnable r):静态的(采用默认线程池中的线程执行任务)
c)executeOnExecutor(Executor e,Params ...params)
2)具体业务方法
a)onPreExecute()
b)doInBackground(Params ...params)
c)onPostExecute(Result result)
d)publishProgress(Progress... p)
e)onProgressUpdate(Progress ...values)
2.线程池(Executor)
1)ExecutorService(线程池对象接口)
2)ScheduledExecutorService(线程池对象接口,可以进行延迟任务调度)
3)ThreadPoolExecutor(线程池实现类)
3)Executors(工具类)
3.线程绑定(ThreadLocal)
a)set(要绑定的对象)
b)get()获得当前线程对象
c)remove()解除绑定
4.FAQ?
1)假如要多个下载操作并发执行,是要启动多个异步任务对象,
还是要启动一个异步任务然后调用多次execute方法。
2)异步任务对象可以使用自己定义的线程池吗?可以
3)在执行耗时操作时我们是启动异步任务还是自己直接
启动工作线程?(由习惯决定)
----------------------------------------------------
案例:加载大图片(异步,压缩,缓存)
案例讲解Day15-2
1.启动异步任务加载SQLite中的数据
1)AsyncTask.execute(new Runnable{public void run(){}})
2)update UI :runOnUIThread(.....): 不局限于一种写法
2.实现图片的高效加载
好处*:
我们常常提到的“Android程序优化”,通常指的是性能和内存的优化,即:更快的响应速度,更低的内存占用。Android程序的性能和内存问题,大部分都和图片紧密相关,而图片的加载在很多情况下很用到Bitmap(位图)这个类。而由于Bitmap自身的特性(将每个像素的属性全部保存在内存中),导致稍有不慎就会创建出一个占用内存非常大的Bitmap对象,从而导致加载过慢,还会有内存溢出的风险。所以,Android程序要做优化,Bitmap的优化是必不可少的一步
需要对Bitmap进行优化的情形
首先请看一行代码:
mImageView.setImageResource(R.drawable.my_image);
这是一行从资源文件中加载图片到ImageView的代码。通常这段代码没什么问题,但有些情况下,你需要对这段代码进行优化。例如当图片的尺寸远远大于ImageView的尺寸时,或者当你要在一个ListView或GridView中批量加载一些大小未知的图片时。实际上,以上这行代码会在运行时使用BitmapFactory.decodeStream()方法将资源图片生成一个Bitmap,然后由这个Bitmap生成一个Drawable,最后再将这个Drawable设置到ImageView。由于在过程中生成了Bitmap,因此如果你使用的图片过大,就会导致性能和内存占用的问题。另外,需要优化的情形不止这一种,这里就不再列举
1)图片压缩
1. 获取原图片尺寸
通常,我们使用BitmapFactory.decodeResource()方法来从资源文件中读取一张图片并生成一个Bitmap。通过BitmapFactory.Options类类指定解码方法。在解码图片的时候设置inJustDecodeBounds属性设置为true,可以避免内存分配,decodeResource()方法就不会生成Bitmap对象,而仅仅是读取该图片的尺寸和类型信息:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//true即只读边不读内容
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
为了避免java.lang.OutOfMemeory异常,在解码图片之前就要检查图片的尺寸,除非你十分确信图片资源的尺寸是可预见的并且有着充裕的可用内存。
2. 根据原图尺寸和目标区域的尺寸计算出合适的Bitmap尺寸
BitmapFactory.Options类有一个参数inSampleSize,该参数为int型,他的值指示了在解析图片为Bitmap时在长宽两个方向上像素缩小的倍数。inSampleSize的默认值和最小值为1(当小于1时,解码器将该值当做1来处理),且在大于1时,该值只能为2的幂(当不为2的幂时,解码器会取与该值最接近的2的幂)。例如,当inSampleSize为2时,一个2000*1000的图片,将被缩小为1000*500,相应地,它的像素数和内存占用都被缩小为了原来的1/4:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 原始图片的宽高
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// 在保证解析出的bitmap宽高分别大于目标尺寸宽高的前提下,取可能的inSampleSize的最大值
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
//高度和宽度都按照此比例进行压缩
return inSampleSize;
}
3.根据计算出的inSampleSize生成Bitmap
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// //加载图片的边界信息(只读取图片的高度,宽度,不读取具体字节)
final BitmapFactory.Options options = new BitmapFactory.Options();
//将此选项设置true,此时再加载图片就会只读取图片边界信息了
options.inJustDecodeBounds = true;
//加载图片边界,并将边界信息封装到options对象
BitmapFactory.decodeResource(res, resId, options);
// 计算 inSampleSize 的
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 根据计算出的 inSampleSize 来解码图片生成Bitmap
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
这里有一点要注意,就是要在第二遍decode之前把inJustDecodeBounds设置回false。
4. 调用以上的decodeSampledBitmapFromResource方法,使用自定尺寸的Bitmap。
如果你要将一张大图设置为一个100*100的缩略图,执行以下代码:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
到此,使用decodeResource()方法将一个大图解析为小尺寸bitmap的应用就完成了。同理,还可以使用decodeStream(),decodeFile()等方法做相同的事,原理是一样的
延伸:一个Bitmap到底占用多大内存?系统给每个应用程序分配多大内存?
· Bitmap占用的内存为:像素总数 * 每个像素占用的内存。在Android中,Bitmap有四种像素类型:ARGB_8888、ARGB_4444、ARGB_565、ALPHA_8,他们每个像素占用的字节数分别为4、2、2、1。因此,一个2000*1000的ARGB_8888类型的Bitmap占用的内存为2000*1000*4=8000000B=8MB。
· Android根据设备屏幕尺寸和dpi的不同,给系统分配的单应用程序内存大小也不同,具体如下表(表格取自Android 4.4 Compatibility Definition Document (CDD)):
屏幕尺寸
DPI
应用内存
small / normal / large
ldpi / mdpi
16MB
small / normal / large
tvdpi / hdpi
32MB
small / normal / large
xhdpi
64MB
small / normal / large
400dpi
96MB
small / normal / large
xxhdpi
128MB
xlarge
mdpi
32MB
xlarge
tvdpi / hdpi
64MB
xlarge
xhdpi
128MB
xlarge
400dpi
192MB
xlarge
xxhdpi
256MB
2)图片在工作线程加载
在工作线程加载并压缩图片:记住:以后在activity写内部类时,内部类又是一个线程对象,请尽量使用静态内部类
继承AsyncTask,利用弱引用,引用外部类的中的属性imageView
在doInBackground 返回值为使用decodeResource()方法,将一个大图解析为小尺寸bitmap的应用
onPostExecute用if判断弱引用与(bitmap)result是否为空如果为空返回,不为空则提取imageview,再用if语句imageview判断是否为空,如果不为空则将bitmap写进imageview
3)图片缓存在内存(LruCache)
4)图片缓存在外存(DiskLruCache):????????
扩展:引用
强引用(Strong References):强引用引用的对象不可被销毁
1.强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
Object o=new Object(); // 强引用
2.当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:
o=null; // 帮助垃圾收集器回收此对象
显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。
3.在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。
但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。
public void test(){
Object o=new Object();
// 省略其他操作
}
强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:
private transient Object[] elementData;
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。
弱引用(WeakReferences):弱引用引用的对象可以被销毁
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
String str=new String("abc");
WeakReference<String> abcWeakRef=new WeakReference<String>(str);
str=null;
当垃圾回收器进行扫描回收时等价于:
str = null;
System.gc();、
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。
下面的代码会让str再次变为一个强引用:
String abc = abcWeakRef.get();
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。
这个引用不会在对象的垃圾回收判断中产生任何附加的影响。
软引用(SoftReferences):软引用引用的对象可以在内存不足时被销毁。
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str=new String("abc"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str);//软引用
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
总结
Java4种引用的级别由高到低依次为:
强引用 > 软引用 > 弱引用 > 虚引用
通过图来看一下他们之间在垃圾回收时的区别:
当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收,如果对这方面不是很了解,可以参考如下的文章:
---------------------------------------------------------------
作业
1.总结(整理思路)
2.尝试完成一个DiskLruCache的应用案例
内容回顾Day16-1
--------------------------------------------------
1.AsyncTask 在SQLite数据加载过程中的应用
1)异步加载
2)UI更新(主线程更新)
2.AsyncTask 在图片加载过程中的应用
1)图片的压缩
2)图片的异步加载
3)图片的缓存(LruCache,DiskLruCache)
4)UI更新(主线程)
图片缓存一般分为内存缓存和外存缓存。
内存 缓存运用java的缓存机制,在程序完全退出后,缓存所在的内存空间可能被其它应用程序占用从而丢失。外存缓存一般放在程序特有的访问空间或者sd卡中, 在sd卡中存放的资源为公有资源,其它程序也可以访问,且对用户来讲没有一个强制清除缓存的规范机制。
考虑:在使用异步加载时要考虑内存泄漏
1)自定义view (记得recycle())
2)工作线程的Looper(记得quit)
3)activity中的线程内部类(尽量使用static)
4)记得在工作线程对象中使用UI时(尽量使用弱引用)
有很多人使用软引用加载图片的多 ,但是现在已经不再推荐使用这种方式了,
(1)因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,
这让软引用和弱引用变得不再可靠。
(2)另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,
因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃,
所以我这里用得是LruCache来缓存图片,当存储Image的大小大于LruCache设定的值,系统自动释放内存,
这个类是3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。
?4.Android Components
Android 中的应用组件(四个) Day16-2
1.ContentProvider (内容提供者)
2.Service (服务)
3.BroadcastReceiver(广播接收者)
4.Activity(UI线程组件,负责与用户交互)
学习:
1)what
2)when
3)why
4)how
----------------------------------------------------
a) Content Provider (跨进程私有数据共享)
***ContentProvider 数据存储组件Day16-3
1.ContentProvider 是什么(What)
ContentProvider:为存储和获取数据提供统一的接口。可以在不同的应用程序之间共享数据。Android已经为常见的一些数据提供了默认的ContentProvider
1)Android 中的一个应用组件(作为应用组件的
表现是生命周期方法)
2)Android 中的内容提供者(一般是内部存储中的数据)
2.ContentProvider 对象的应用场合(When)
当android中的一个app私有数据要共享给其它应用时,可以借助ContentProvider实现。
例如:
1)微信访问手机中联系人数据
2)支付宝访问手机中联系人数据
3).......
ContentProvider能够实现进程间私有数据的访问。
1) ContentProvider为存储和读取数据提供了统一的接口
2) 使用ContentProvider,应用程序可以实现数据共享
3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)
总的来说使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
3.ContentProvider 在实际项目中的应用?how
假设现有一个A进程,它需要将其私有数据共享给
其它进程(例如B进程),此时可以在A进程端创建
ContentPrivider对象,并对外公布一个URI.此时
假如B进程想访问A进程中的私有数据,可以借助
B端的ContentResolver对象,通过A发布的URI
对象找到A的ContentProvider对象进行访问就可
以了。
说明:
我们的重点不是要写一个ContentProvider,重点
是理解过程,并能够应用ContentResolver去访
问别人的ContentProvider(例如系统的媒体库)。
案例:参考
1)Day16_component_1(contentprovider)
2)Day16_component_2(contentresolver)
1)、ContentProvider使用表的形式来组织数据
无论数据的来源是什么,ContentProvider都会认为是一种表,然后把数据组织成表格
2)、ContentProvider提供的方法
query:查询
insert:插入
update:更新
delete:删除
getType:得到数据类型
onCreate:创建数据时调用的回调函数
3)、每个ContentProvider都有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。Android所提供的ContentProvider都存放在android.provider包当中
使用ContentProvider共享数据
1)ContentProvider类主要方法的作用:
public boolean onCreate():该方法在ContentProvider创建后就会被调用,Android开机后,ContentProvider在其它应用第一次访问它时才会被创建。
public Uri insert(Uri uri, ContentValues values):该方法用于供外部应用往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs):该方法用于供外部应用从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):该方法用于供外部应用更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):该方法用于供外部应用从ContentProvider中获取数据。
public String getType(Uri uri):该方法用于返回当前Url所代表数据的MIME类型
2)如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,
例如:要得到所有person记录的Uri为content://com.bing.provider.personprovider/person,那么返回的MIME类型字符串应该为:"vnd.android.cursor.dir/person"。
如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,例如:得到id为10的person记录,Uri为content://com.bing.provider.personprovider/person/10,那么返回的MIME类型字符串为:"vnd.android.cursor.item/person"。
ContentResolver操作ContentProvider中的数据
1)当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。
2)ContentResolver 类提供了与ContentProvider类相同签名的四个方法:
public Uri insert(Uri uri, ContentValues values):该方法用于往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs):该方法用于从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):该方法用于更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):该方法用于从ContentProvider中获取数据。
这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,
其实和contentprovider里面的方法是一样的.他们所对应的数据,最终是会被传到我们在之前程序里面定义的那个contentprovider类的方法,
假设给定的是:Uri.parse("content://com.bing.providers.personprovider/person/10"),那么将会对主机名为com.bing.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。
监听ContentProvider中数据的变化
如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者
如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法
ContentProvider的内部原理
自定义一个ContentProvider,来实现内部原理
步骤:
1、定义一个CONTENT_URI常量(里面的字符串必须是唯一)
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentprovider");
如果有子表,URI为:
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentProvider/users");
2、定义一个类,继承ContentProvider
Public class MyContentProvider extends ContentProvider
3、实现ContentProvider的所有方法(query、insert、update、delete、getType、onCreate)
package com.WangWeiDa.cp;
CursorLoader对象Day17-1
-------------------------------------------------
此对象内置一个异步任务对象,可以通过此对象
在工作线程访问一个ContentProvider对象,然后
获得我们需要的数据。
CursorLoader对象的启动:
借助Activity的LoaderManager对象的
initLoader(int id,Bundle args,LoaderCallBacks call)方法启动
在CursorLoader初始化时会执行LoaderCallBacks的
onCreateLoader方法,一般此方法中创建一个CursorLoader
对象。
在CursorLoader数据加载完成以后会回调onLoadFinished方法
,可以在方法中更新UI.
运用模拟器里dev toolsMedia ProviderScan Sd card
扩展:Uri介绍
Uri代表了要操作的数据,Uri主要包含了两部分信息:1需要操作的ContentProvider , 2对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
scheme:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://"
主机名或authority:URI 的标识,用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。这个标识在元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称
路径(path):通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"
ID:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部; "content://com.bing.provider.myprovider/tablename/#" #表示数据id。
尽管这种查询字符串格式很常见,但是它看起来还是有点令人迷惑。为此,Android提供一系列的帮助类(在android.provider包下),里面包含了很多以类变量形式给出的查询字符串,这种方式更容易让我们理解一点,因此,如上面content://contacts/people/45这个URI就可以写成如下形式:
Uri person = ContentUris.withAppendedId(People.CONTENT_URI, 45);
然后执行数据查询:
Cursor cur = managedQuery(person, null, null, null);
Cursor cur = managedQuery(
mContacts,
columns, // 要返回的数据字段
null, // WHERE子句
null, // WHERE 子句的参数
null // Order-by子句
);
PS:
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
1、要操作person表中id为10的记录,可以构建这样的路径:/person/10
2、要操作person表中id为10的记录的name字段, person/10/name
3、要操作person表中的所有记录,可以构建这样的路径:/person
4、要操作xxx表中的记录,可以构建这样的路径:/xxx
5、当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
6、如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")
withAppendedId(uri, id)用于为路径加上ID部分
Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")
Uri resultUri = ContentUris.withAppendedId(uri, 10);
//生成后的Uri为:content://com.bing.provider.personprovider/person/10
parseId(uri)方法用于从路径中获取ID部分:
Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10")
long personid = ContentUris.parseId(uri);//获取的结果为:10
uri增删改查
执行数据查询:
Cursor cur = managedQuery(person, null, null, null);这个查询返回一个包含所有数据字段的游标
ContentResolver.update()方法来修改数据
updateRecord(10, ”XYZ”); //更改第10条记录的name字段值为“XYZ”
增加记录调用ContentResolver.insert()方法,该方法接受一个要增加的记录的目标URI,以及一个包含了新记录值的Map对象,调用后的返回值是新记录的URI,包含记录号。
可以调用insertRecords(name, phoneNo)的方式来向联系人信息簿中添加联系人姓名和电话号码。
Content Provider中的getContextResolver.delete()方法可以用来删除记录
可以指定WHERE条件语句来删除特定的记录:
getContentResolver().delete(uri, “NAME=” + “‘XYZ XYZ’”, null);这将会删除name为‘XYZ XYZ’的记录
4.案例:媒体库的访问
参考:(Day16_component_3(media))
------------------------------------------------
作业
1.总结
2.完成课堂案例
3.预习媒体播放的实现
java.security.MessageDigest类用于为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。
扩展:Activity中finish() onDestroy() 和System.exit()的区别
Activity.finish()
Call this when your activity is done and should be closed.
在你的activity动作完成的时候,或者Activity需要关闭的时候,调用此方法。
当你调用此方法的时候,系统只是将最上面的Activity移出了栈,并没有及时的调用onDestory()方法,其占用的资源也没有被及时释放。因为移出了栈,所以当你点击手机上面的“back”按键的时候,也不会再找到这个Activity。
Activity.onDestory()
the system is temporarily destroying this instance of the activity to save space.
系统销毁了这个Activity的实例在内存中占据的空间。
在Activity的生命周期中,onDestory()方法是他生命的最后一步,资源空间等就被回收了。当重新进入此Activity的时候,必须重新创建,执行onCreate()方法。
System.exit(0)
这玩意是退出整个应用程序的,是针对整个Application的。将整个进程直接KO掉。
案例:Android开发之MdiaPlayer详解
MediaPlayer类可用于控制音频/视频文件或流的播放,我曾在《Android开发之基于Service的音乐播放器》一文中介绍过它的使用。下面让我们看一下MediaPlayer类的详细介绍。
一、类结构:
java.lang.Object
?
android.media.MediaPlayer
二、构造方法和公有方法
构造方法:
Public Constructors
MediaPlayer()
默认构造方法。
公有方法:
Public Methods
static MediaPlayer
create(Context context, Uri uri, SurfaceHolder holder)
指定从资源ID对应的资源文件中来装载音乐文件,同时指定了SurfaceHolder对象并返回MediaPlyaer对象。
static MediaPlayer
create(Context context, int resid)
指定从资源ID对应的资源文件中来装载音乐文件,并返回新创建的MediaPlyaer对象。
static MediaPlayer
create(Context context, Uri uri)
从指定Uri装在音频文件,并返回新创建的MediaPlayer对象。
int
getCurrentPosition()
获取当前播放的位置。
int
getDuration()
获取音频的时长。
int
getVideoHeight()
获取视频的高度。
int
getVideoWidth()
获取视频的宽度。
boolean
isLooping()
判断MediaPlayer是否正在循环播放。
boolean
isPlaying()
判断MediaPlayer是否正在播放。
void
pause()
暂停播放。
void
prepare()
准备播放(装载音频),调用此方法会使MediaPlayer进入Prepared状态。
void
prepareAsync()
准备播放异步音频。
void
release()
释放媒体资源。
void
reset()
重置MediaPlayer进入未初始化状态。
void
seekTo(int msec)
寻找指定的时间位置。
void
setAudioStreamType(int streamtype)
设置音频流的类型。
void
setDataSource(String path)
指定装载path路径所代表的文件。
void
setDataSource(Context context, Uri uri, Map<String, String headers)
指定装载uri所代表的文件。
void
setDataSource(Context context, Uri uri)
指定装载uri所代表的文件。
void
setDataSource(FileDescriptor fd, long offset, long length)
指定装载fd所代表的文件中从offset开始长度为length的文件内容。
void
setDataSource(FileDescriptor fd)
指定装载fd所代表的文件。
void
setDisplay(SurfaceHolder sh)
设置显示方式。
void
setLooping(boolean looping)
设置是否循环播放。
void
setNextMediaPlayer(MediaPlayer next)
设置当前流媒体播放完毕,下一个播放的MediaPlayer。
void
setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)
注册一个回调函数,在网络视频流缓冲变化时调用。
void
setOnCompletionListener(MediaPlayer.OnCompletionListener listener)
为Media Player的播放完成事件绑定事件监听器。
void
setOnErrorListener(MediaPlayer.OnErrorListener listener)
为MediaPlayer的播放错误事件绑定事件监听器。
void
setOnPreparedListener(MediaPlayer.OnPreparedListener listener)
当MediaPlayer调用prepare()方法时触发该监听器。
void
setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener)
当MediaPlayer调用seek()方法时触发该监听器。
void
setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener)
注册一个用于监听视频大小改变的监听器。
void
setScreenOnWhilePlaying(boolean screenOn)
置是否使用SurfaceHolder来显示。
void
setSurface(Surface surface)
设置Surface。
void
setVideoScalingMode(int mode)
设置视频缩放的模式。
void
setVolume(float leftVolume, float rightVolume)
设置播放器的音量。
void
setWakeMode(Context context, int mode)
为MediaPlayer设置低级电源管理行为。.
void
start()
开始或恢复播放。
void
stop()
停止播放。
三、常用方法分析:
1.使用进度条:
进度条SeekBar可以用来显示播放进度,用户也可以利用SeekBar的滑块来控制音乐的播放。
SeekBar需要使用的一些方法:
setProgress(int value):设置滑块的位置方法为。
setMax(int value):设置进度条的最大长度。
setOnSeekBarChangeListener(OnSeekBarChangeListener l):设置SeekBar的进度改变事件。
MusicPlayer需要使用的一些方法:
getDuration():获得音乐长度为。
getCurrentPosition():获得现在播放的位置。
seekTo(int msec):调用seekTo()方法可以调整播放的位置。
seekTo(int)方法是异步执行的,所以它可以马上返回,但是实际的定位播放操作可能需要一段时间才能完成,尤其是播放流形式的音频/视频。当实际的定位播放操作完成之后,内部的播放引擎会调用客户端程序员提供的OnSeekComplete.onSeekComplete()回调方法。可以通过setOnSeekCompleteListener(OnSeekCompleteListener)方法注册。
seekTo(int)方法也可以在其它状态下调用,比如Prepared,Paused和PlaybackCompleted状态。此外,目前的播放位置,实际可以调用getCurrentPosition()方法得到,它可以帮助如音乐播放器的应用程序不断更新播放进度。
2.装载音频文件:
为了让MediaPlayer来装载指定音频文件,MediaPlayer提供了如下简单的静态方法。
static MediaPlayer create(Context context, Uri uri):从指定Uri来装载音频文件,并返回新创建的MediaPlayer对象。
static MediaPlayer create(Context context, int resid):从 resid资源 ID对应的资源文件中装载音频文件,并返回新创建的MediaPlayer对象。
提示:上而这两个方法用起来非常方便,但这两个方法每次都会返回新创建的MediaPlayer对象,如来程序需要使用MediaPlayer循环播放多个音频文件,使用MediaPlayer的静态create方法就不太合适了,此时可通过MediaPlayer的setDataSource()方法来装载指定的音频文件。MediaPlayer提供了如下方法来指定装载相应的音频文件。
setDataSource(String path):指定装载path路径所代表的文件。
setDataSource(FileDescriptor fd, long offset,long length):指定装载fd所代表的文件中从offset开始长度为length的文件内容。
setDataSource(FileDescriptor fd):指定装载fd所代表的文件。
setDataSource(Context context, Uri uri):指定装载uri所代表的文件。
提示:执行上面所示的setDataSource()方法之后,MediaPlayer并未真正去装载那些音频文件,还需要调用MediaPlayer的prepare()方法去准备音频,所谓“准备”,就是让MediaPlayer真正去装载音频文件。
3.与MediaPlayer有关的事件监听器:
MediaPlayer提供了一些绑定事件监听器的方法,用于监听MediaPlayer播放过程中所发生的特定事件,绑定事件监听器的方法如下。
setOnCompletionListener(MediaPlayer.OnCompletionListener listener):为 Media Player的播放完成事件绑定事件监听器。
setOnErrorListener(MediaPlayer.OnErrorListener listener):为MediaPlayer的播放错误事件绑定事件监听器。
setOnPreparedListener(MediaPlayer.OnPreparedListener listener):当 MediaPlayer调用prepare()方法时触发该监听器。
setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener):当MediaPlayer调用seek()方法时触发该监听器。
四、MediaPlayer播放不同来源的音频文件:
1.播放应用的资源文件
播放应用的资源文件需要两步即:
1) 调用MediaPlayer的create(Context context,int resid)方法加指定资源文件。
2) 调用 MediaPlayer的 start()、pause()、stop()等方法控制播放即可
2. 播放应用的原始资源文件
播放应用的资源文件按如下步骤执行。
1) 调用 Context的 getAssets()方法获取应用的 AssetManager。
2) 调用AssetManager对象的openFd(String name)方法打开指定的原生资源,该方法返回一个AssetFileDescriptor对象。
3) 调用 AssetFileDescriptor的 getFileDescriptor()、getStartOffset()和 getLength()方法来获取音频文件的FileDescriptor、开始位置、长度等。
4) 创建MediaPlayer对象(或利用已有的MediaPlayer对象),并调用MediaPlayer对象的setDataSource(FileDescriptor fd,long offset, long length)方法来装载音频资源。
5) 调用MediaPlayer对象的prepare()方法准备音频。
6) 调用MediaPlayer的start()、pause()、stop()等方法控制播放即可
3. 播放外部存储器上音频文件
播放外部存储器上音频文件按如下步骤执行。
1) 创建MediaPlayer对象(或利用已有的MediaPlayer对象),并调用MediaPlayer对象的setDateSource(String path)方法装载指定的音频文件。
2) 调用MediaPlayer对象的prepare()方法准备音频。
3) 调用MediaPlayer的start()、pause()、stop()等方法控制播放即可。
4.播放来自网络的音频文件
播放来自网络的音频文件?两种方式:1.直接使用MediaPlayer的静态create(Context context, Uri uri)方法;2.调用 MediaPlayer的setDataSource(Context context,Uri uri)装载指定Uri对应的音频文件。
以第二种方式播放来自网络的音频文件的步骤如下。
1. 根据网络上的音频文件所在的位置创建Uri对象。
2. 创建MediaPlayer对象(或利用己有的MediaPlayer对象),并调用MediaPlayer对象的 setDateSource(Context context,Uri uri)方法装载Uri对应的音频文件。
3. 调用MediaPlayer对象的prepare()方法准备音频。
4. 调用MediaPlayer的start()、pause()、stop()等方法控制播放即可。
MediaPlayer除了调用prepare()方法来准备声音之外,还以调用prepareAsync()来准备声音,prepareAsync()与普通prepare()方法的区别在于,prepareAsync()是异步的,它不会阻塞当前的UI线程。
五、解析MdiaPlayer的状态图
上面这张状态转换图清晰的描述了MediaPlayer的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常。
Idle状态:当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态。在处于Idle状态时,调用getCurrentPosition(), getDuration(), getVideoHeight(),getVideoWidth(),setAudioStreamType(int), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(int), prepare()或者 prepareAsync()方法都会出现相应错误。这两种方法的一个重要差别就是:当一个MediaPlayer对象刚被构建的时候,内部的播放引擎和对象的状态都没有改变,在这个时候调用以上的那些方法,框架将无法回调客户端程序注册的OnErrorListener.onError()方法,所以不会进入Error状态;但若这个MediaPlayer对象调用了reset()方法之后,再调用以上的那些方法,内部的播放引擎就会回调客户端程序注册的OnErrorListener.onError()方法,这时MediaPlayer会进入Error状态。
提示:使用new操作符创建的MediaPlayer对象处于Idle状态,而那些通过重载的create()便利方法创建的MediaPlayer对象却不是处于Idle状态。事实上,如果成功调用了重载的create()方法,那么这些对象已经是Prepare状态了。
End状态:通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉,以释放相关的软硬件组件资源,这其中有些资源是只有一份的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会再进入任何其他状态了。
Initialized状态:这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。
提示:若当此MediaPlayer处于其它的状态下,调用setDataSource()方法,会抛出IllegalStateException异常。
Prepared状态:初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。
提示:在不合适的状态下调用prepare()和prepareAsync()方法会抛出IllegalStateException异常。当MediaPlayer对象处于Prepared状态的时候,可以调整音频/视频的属性,如音量,播放时是否一直亮屏,循环播放等。
Preparing状态:这个状态比较好理解,主要是和prepareAsync()配合,如果异步准备完成,会触发OnPreparedListener.onPrepared(),进而进入Prepared状态。
Started状态:显然,MediaPlayer一旦准备好,就可以调用start()方法,这样MediaPlayer就处于Started状态,这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态,类似的,如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。
Paused状态:Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer,从而进入Paused状态,MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放,转到Started状态,暂停状态时可以调用seekTo()方法,这是不会改变状态的。
Stop状态:Started或者Paused状态下均可调用stop()停止MediaPlayer,而处于Stop状态的MediaPlayer要想重新播放,需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。
PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。
Error状态:在一般情况下,由于种种原因一些播放控制操作可能会失败,如不支持的音频/视频格式,缺少隔行扫描的音频/视频,分辨率太高,流超时等原因,等等会触发会触发OnErrorListener.onError()事件,此时MediaPlayer会进入Error状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。
开发者可以通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener设置监听器来监听MediaPlayer是否进入Error状态。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到Idle状态。
b) Service (长时间的后台耗时操作)
***Service 组件Day17-2
Service 是什么(What)?
Service是一种在Android应用后台的一种组件,没有自己的界面,不需要与用户交互。
1)Android 中的一个应用组件?(生命周期方法)
2)Android 中的一个后台服务?(可以长时间运行于后台)
最基本的两种用途:执行长时间时间运行的耗时操作,如网络下载,音乐播放,文件系统检测。
一种是组件间的交互(通过将某些功能以Service组件的形式进行封装,然后提供给其他应用组件调用,而不管这些组件是否与Service组件在同一进程中)。
FAQ?
1)对于长时间的耗时操作是直接起工作线程,还是启service?
建议:在Service中启工作线程。
2)为什么不在activity启工作线程执行耗时操作呢?
因为当activity工作于后台处于停止状态时,它所
在进程的生命力就会比较薄弱,在内存不足时可
能会被杀死。
2、Service 与 Thread 的区别
很多时候,你可能会问,为什么要用 Service,而不用 Thread 呢,因为用 Thread 是很方便的,比起 Service 也方便多了,下面我详细的来解释一下。
1). Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
2). Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!
既然这样,那么我们为什么要用 Service 呢?其实这跟 android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。
举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。
因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。
在Android 中的进程可以分为如下几种类型
1)前台进程(可见,可以与用户交互)
2)可见进程(可见,但不能与用户交互)
3)服务进程(后台有service在运行)
4)后台进程(没有service组件,所有的activity都处于停止状态)
5)空进程(没有任何组件在运行的进程)
以上进程的生命力,从高到低。
按运行地点分类:
类别
区别
优点
缺点
应用
本地服务(Local)
该服务依附在主进程上,
服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。
主进程被Kill后,服务便会终止。
非常常见的应用如:HTC的音乐播放服务,天天动听音乐播放服务。
远程服务(Remote)
该服务是独立的进程,
服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。
该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。
一些提供系统服务的Service,这种Service是常驻的。
其实remote服务还是很少见的,并且一般都是系统服务。
按运行类型分类:
类别
区别
应用
前台服务
会在通知一栏显示 ONGOING 的 Notification,
当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。常见的如音乐播放服务。
后台服务
默认的服务即为后台服务,即不会在通知一栏显示 ONGOING 的 Notification。
当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等。
有同学可能会问,后台服务我们可以自己创建 ONGOING 的 Notification 这样就成为前台服务吗?答案是否定的,前台服务是在做了上述工作之后需要调用 startForeground ( android 2.0 及其以后版本 )或 setForeground (android 2.0 以前的版本)使服务成为 前台服务。这样做的好处在于,当服务被外部强制终止掉的时候,ONGOING 的 Notification 任然会移除掉。
按使用方式分类:
类别
区别
startService 启动的服务
主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService
bindService 启动的服务
该方法启动的服务要进行通信。停止服务使用unbindService
startService 同时也 bindService 启动的服务
停止服务应同时使用stepService与unbindService
以上面三种方式启动的服务其生命周期也有区别,将在随后给出。
2.Android 中的Service 的应用场合?(when,why)
执行长时间耗时操作时时,可以考虑在service
组件中执行,例如音乐的播放,股票信息的时
时加载等。
记住:并不是耗时都要写到service,短时间的
耗时完全可以在activity中启工作线程执行。
Android 中Service 的类型?
启动模式的Service
如果Service组件是长时间运行的操作,则一般采用启动模式,采用启动模式的Service一般持续执行一个单一的操作,而且并不会向调用者返回任何信息。Service被启动后,将一直处于运行状态,即使调用startService的进程结束了,Service仍然还存在,直到有进程调用stopService,或者Service调用stopSelf自杀。
绑定模式的Service
如果Service组件提供一种封装的功能供其他组件使用,则一般采用绑定模式。步骤: 通过调用组件对象的bindService方法启动Service对象实例,如果没有指定的Service实例被创建,则该方法调用Service的onCreate()方法来创建一个实例,实例启动后,将调用onBind()方法,onBind方法返回给客户端一个IBinder接口实例,IBinder允许客户端回调Service方法,不如得到Service运行的状态或其他操作。只要连接建立,Service就会一直运行,(不管客户是否保留Service的IBinder的引用)。通常IBinder是一个使用AIDL写成的复杂接口。
3)混合模式的Service
我们在理解这些模式的service时,需要重点掌握
这些模式service对象的生命期,以及其生命周期
方法,我们可以在对应的生命周期方法中执行业
务。
***启动模式的Service
1、启动模式下的Service
实现步骤:1、创建Service类,继承android.app.Service类
2、实现onStartCommand等生命周期方法。
3.在Andridmanifest.xml文件中配置Service组件。
由于Service是在主线程运行的,为避免产生应用无响应异常,必须在Service类的内部创建一个单独的线程,用于耗时的业务逻辑。
Android SDK提供了更好的方法,既IntentService(同时解决了多请求下线程同步的问题)。
IntentService:
1、在应用的主线程外创建一个单独的工作线程来执行传递到onStartCommand方法的Intent组件。
2、创建一个工作队列,它每次将一个Intent传递到onHandleIntent(),不需要考虑多线程的同步问题。
3、当所有请求被处理完成后,将自动停止服务而不需要显示调用stopSelf方法。
4、提供一个返回null值的onBind方法的默认实现。
5、提供了onStartCommand方法的默认时间,它将所有的Intent发送到一个工作队列,并进一步发送到onHandleInteng方法。
运行模式:
在启动模式下,Service中的业务逻辑主要在onStartCommand方法中实现,每次通过调用方法startService来启动Service,都会调用onStartCommand方法,其中方法的返回值决定了Service的运行模式。
1、START_NOT_STICKY:如果Sevice在启动后,被kill掉,并且没有新启动的Intent传给它,那么将Service移出启动状态并且不重新生成,知道再次显示调用Context.startService。适用场景:网上下载数据。
2、START_REDELIVER_INTENT:如果Service进程在启动后kill掉,那么它将会被重启,并且最后传给他的Intent通过onStartCommand(Intent ,int,int)会被重新传给他,这种模式保证了传给它Intent一定会被处理完毕,适用场景:关键业务处理。
3、START_STICKY:如果Service在它启动后被kill掉,那么Android将让Service继续保持started状态,但是不保留启动它的Intent,Android将重新创建Service实例,并执行onStartCommand方法,如果此时没有新的Intent请求,此时Intent的参数是null,这一点要特别注意。适用场景:后台播放音乐。这种运行模式的特点是需要显示启动并停止Service。
启动模式service的启动和关闭
1)通过startService方法启动
2)通过stopService停止,在内部可以通过stopSelf(startId)去停止
**启动模式Service对象的生命周期方法:
1)onCreate (service创建时执行)
2)onStartCommand(每次启动都会执行)
3)onDestory(service销毁时会执行)
生命周期
使用context.startService() 启动Service是会会经历:
context.startService() ->onCreate()- >onStart()->Service running
context.stopService() | ->onDestroy() ->Service stop
context.startService()->onCreate()->onStart()->Servicerunning->context.stopService()-> onDestroy()-> Service stop
如果Service还没有运行,则android先调用onCreate()然后调用onStart();如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次。
stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。该Service的调用者再启动起来后可以通过stopService关闭Service。
所以调用startService的生命周期为:onCreate --> onStart(可多次调用) --> onDestroy
Serivce 的子类IntentService的应用?
IntentService会默认启动一个工作线程,我们
可以将耗时操作放在onHandleIntent中,此方
法执行结束,service会自动关闭。
FAQ?
1)启动模式Service 的粘性?(参考onStartCommand方法的返回值)
2)启动模式Service 如何通过隐式Intent启动
a)隐式意图需要在清单配置文件配置intent-filter
切记:service不用了,要记得停止。
----------------------------------------------------
作业
1.总结
2.完成课堂案例
3.预习绑定模式service,混合模式service.
4.尝试在service中如何实现音乐的播放,暂停等操作。
***绑定模式的service Day18-1
-------------------------------------------------
绑定模式service相当于在activity端与service端
建立了一个长链接(ServiceConnection),然后可以
通过此长连接对象中的方法获得Service中返回的
绑定对象。通过此对象调用service中的一些业务
方法。
绑定模式service的生命周期方法:
1)onCreate (只执行一次)
2)onBind(只执行一次)
3)onUnbind(只执行一次)
4)onDestory(只执行一次)
我们一般要把自己的业务,在service对象的生命
周期方法中实现。
使用使用context.bindService()启动Service会经历:
context.bindService()->onCreate()->onBind()->Service running
onUnbind() -> onDestroy() ->Service stop
context.bindService()->onCreate()->onBind()->Servicerunning->onUnbind()->onDestroy() -> Service stop
onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
***Service使用场合
服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。
1. 使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出了,服务仍然运行。
如果打算采用Context.startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。
如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。
采用startService()方法启动的服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。
2. 使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。
采用Context.bindService()方法启动服务时只能调用onUnbind()方法解除调用者与服务解除,服务结束时会调用onDestroy()方法。
***混合模式的service Day18-2
混合模式service一般是绑定和启动相结合的一种
service应用模式,此应用模式service的生命周期
与启动和绑定的顺序相关。
混合模式其生命周期相关方法:
1)onCreate
2)onStartCommon (可以执行多次)
3)onBind
4)onUnbind
5)onDestory
在Service每一次的开启关闭过程中,只有onStart可被多次调用(通过多次startService调用),其他onCreate,onBind,onUnbind,onDestory在一个生命周期中只能被调用一次。
案例:音乐播放器2代码解析
1、Activity中,PlayMusicService中通过重写OnClickListener 接口onClick()方法实现对播放音乐的控制,把音乐各种操作用数字通过Intent传递给service
然后通过构造一个Intent , intent = new Intent("com.homer.service.musicService");
其中,com.homer.service.musicService是 AndroidManifest.xml 对service的定义,即上面“注册service”
2、Activity中,音乐播放的控制,利用Bundle绑定数字op后,通过 startService(intent); 服务后发送出去
Bundle bundle = new Bundle();
bundle.putInt("op", op);
intent.putExtras(bundle);
startService(intent);
3、 Service中,会处理Activity启动的 startService(intent);服务,依次调用service的启动过程:onCreate --> onStart(可多次调用) --> onDestroy
onCreate(),创建mediaPlayer
onStart(),通过获取Bundle bundle = intent.getExtras();,提取int op = bundle.getInt("op");,然后执行响应的音乐播放操作
onDestroy(),停止并释放mediaPlayer音乐资源,如果当执行context.stopService()时调用此方法
4、Activity中,onClick()函数中close与exit是执行含义是不同的:
close:只是执行了this.finish(); 关闭了本Activity窗体,service并没有被关掉,音乐依然会继续在后台播放
exit: 先调用了stopService(intent); 关闭了service服务,在Service中会调用3中的onDestroy()停止并释放音乐资源,后才执行this.finish(); 关闭了本Activity窗体
扩展:进程和声明周期
Android操作系统尝试尽可能长时间的保持应用的进程,但当可用内存很低时最终要移走一部分进程。怎样确定那些程序可以运行,那些要被销毁,Android让每一个进程在一个重要级的基础上运行,重要级低的进程最有可能被淘汰,一共有5级,下面这个列表就是按照重要性排列的:
1 一个前台进程显示的是用户此时需要处理和显示的。下列的条件有任何一个成立,这个进程都被认为是在前台运行的。
a 与用户正发生交互的。
b 它控制一个与用户交互的必须的基本的服务。
c 有一个正在调用生命周期的回调函数的service(如onCreate()、onStar()、onDestroy())
d 它有一个正在运行onReceive()方法的广播接收对象。
只有少数的前台进程可以在任何给定的时间内运行,销毁他们是系统万不得已的、最后的选择——当内存不够系统继续运行下去时。通常,在这一点上,设备已经达到了内存分页状态,所以杀掉一些前台进程来保证能够响应用户的需求。
2 一个可用进程没有任何前台组件,但它仍然可以影响到用户的界面。下面两种情况发生时,可以称该进程为可用进程。
它是一个非前台的activity,但对用户仍然可用(onPause()方法已经被调用)这是可能发生的,例如:前台的activity是一个允许上一个activity可见的对话框,即当前activity半透明,能看到前一个activity的界面,它是一个服务于可用activity的服务。
3 一个服务进程是一个通过调用startService()方法启动的服务,并且不属于前两种情况。尽管服务进程没有直接被用户看到,但他们确实是用户所关心的,比如后台播放音乐或网络下载数据。所以系统保证他们的运行,直到不能保证所有的前台可见程序都正常运行时才会终止他们。
4 一个后台进程就是一个非当前正在运行的activity(activity的onStop()方法已经被调用),他们不会对用户体验造成直接的影响,当没有足够内存来运行前台可见程序时,他们将会被终止。通常,后台进程会有很多个在运行,所以他们维护一个LRU最近使用程序列表来保证经常运行的activity能最后一个被终止。如果一个activity正确的实现了生命周期的方法,并且保存它当前状态,杀死这些进程将不会影响到用户体验。
5 一个空线程没有运行任何可用应用程序组,保留他们的唯一原因是为了设立一个缓存机制,来加快组件启动的时间。系统经常杀死这些内存来平衡系统的整个系统的资源,进程缓存和基本核心缓存之间的资源。
Android把进程里优先级最高的activity或服务,作为这个进程的优先级。例如,一个进程拥有一个服务和一个可见的activity,那么这个进程将会被定义为可见进程,而不是服务进程。
此外,如果别的进程依赖某一个进程的话,那么被依赖的进程会提高优先级。一个进程服务于另一个进程,那么提供服务的进程不会低于获得服务的进程。例如,如果进程A的一个内容提供商服务于进程B的一个客户端,或者进程A的一个service被进程B的一个组件绑定,那么进程A至少拥有和进程B一样的优先级,或者更高。
因为一个运行服务的进程的优先级高于运行后台activity的进程,一个activity会准备一个长时间运行的操作来启动一个服务,而不是启动一个线程–尤其是这个操作可能会拖垮这个activity。例如后台播放音乐的同时,通过照相机向服务器发送一张照片,启动一个服务会保证这个操作至少运行在service 进程的优先级下,无论这个activity发生了什么,广播接收者应该作为一个空服务而不是简单的把耗时的操作单独放在一个线程里。
?Messenger对象的应用Day18-3
Messenger 对象可以实现跨进程之间的消息传递,
它可以实现在service与activity之间的通讯,底层
需要封装对应的一个handler对象。
相关方法
1)Messenger(binder) :绑定成功以后在connection中应用
2)Messenger(handler):replyTo
3)send(message):
1.service实现一个 Handler 接收客户端每一次调用的回调。
2. Handler 用来创建一个Messenger对象,它是一个Handler的引用。
3. Messenger创建一个 IBinder,service从 onBind()中把它返回给客户端。
4.客户端使用这个IBinder来实例化Messenger (service的Handler的引用),客户端使用它来向service发送Message对象。
5.service在它的Handler中接收每一个Message对象,在它的 handleMessage()方法中。
注意 Handler中的 handleMessage() 方法是service接收到来的 Message并且决定做什么的地方。
客户端需要做的仅仅是创建一个基于service所返回的 IBinder的 Messenger,然后用 send()方法发送信息
想要service响应客户端,你需要在客户端中创建一个 Messenger。
然后当客户端接收到onServiceConnected()回调方法时,它会发送一个 Message到service,在它的send() 方法的replyTo参数中包含了客户端的Messenger。
----------------------------------------------------
作业
1.总结
2.完成课堂案例
3.熟练实现activity与service之间的消息通讯
4.预习广播接收者组件,并尝试接收一个系统启动时的广播。
Day19-1内容回顾
1.Service 是什么?
2.Service 的应用场合?(长时间的耗时操作)
1)了解进程的优先级(android在内存不足时有可能 会杀死一些进程)
2)应用场合(例如媒体播放,后台下载,....)
3.Service 的应用模式?
1)启动模式
2)绑定模式
3)混合模式
FAQ?
1)Service 一般工作在服务进程,服务进程是否可转换为前台进程?(可以)
2)这些模式中重点掌握生命周期,应用场合。
4.Service 与Activity之间的数据传递?
1)通过intent向service传数据
2)通过messenger实现service与activity之间互传
---------------------------------------------------
c) BroadCastReceiver(实现一对多的跨进程数据传递)
***BroadCast Receiver 广播组件Day19-2
1.BroadcastReceiver 是什么?
1)Android 中的一个应用组件
2)Android 中的广播接收对象
广播接收者( BroadcastReceiver )用于接收广播 Intent ,广播 Intent 的发送是通过调用 Context.sendBroadcast() 、 Context.sendOrderedBroadcast() 来实现的。通常一个广播 Intent 可以被订阅了此 Intent 的多个广播接收者所接收。
广播是一种广泛运用的在应用程序之间传输信息的机制 。而 BroadcastReceiver 是对发送出来的广播进行过滤接收并响应的一类组件;
来自普通应用程序,如一个应用程序通知其他应用程序某些数据已经下载完毕。
BroadcastReceiver 自身并不实现图形用户界面,但是当它收到某个通知后, BroadcastReceiver 可以启动 Activity 作为响应,或者通过 NotificationMananger 提醒用户,或者启动 Service 等等。
2.BroadcastReceiver 应用场合?
1)实现一对多的跨进程(Process)业务通知操作。
2)实现与系统应用的一些协同操作。
3)实现组件之间的数据传递 ,协同操作。
例如
1)实现电话的拦截
2)实现短信拦截
3)发现系统电量不足
4)网络链接状态检测
5).......
描述了 Android 中广播的生命周期,其次它并不像 Activity 一样复杂,运行原理很简单如下图:
3.BroadcastReceiver 对象的基本应用?
1)系统广播处理
2)应用广播处理
注意:系统广播的接收通常会需要一定权限。
广播类型
普通广播 (Normal broadcasts)
发送一个广播,所以监听该广播的广播接收者都可以监听到改广播。
异步广播 , 当处理完之后的Intent ,依然存在,这时候registerReceiver(BroadcastReceiver, IntentFilter) 还能收到他的值,直到你把它去掉 , 不能将处理结果传给下一个接收者 , 无法终止广播 .
有序广播 (Ordered broadcasts)
按照接收者的优先级顺序接收广播 , 优先级别在 intent-filter 中的 priority 中声明 ,-1000 到
1000 之间 , 值越大 , 优先级越高 . 可以终止广播意图的继续传播 . 接收者可以篡改内容 .
广播接收者创建方式:
1)编写类继承BroadcastReceiver
2)广播接收者的注册:
a)在清单配置文件进行注册(告诉底层系统)
<receiver ....>
b)在Java代码中进行注册
b.1)registerReceiver
b.2)unregisterReceiver
注意:
1)静态注册每次接收新的广播,都会创建一个广播
接收者对象,此对象的onReceive方法执行结束
其生命周期也会伴随结束。
2)动态注册的广播接收者,其对象生命周期何时结束取决于何时解除的注册。
3.BroadcastReceiver 对象实现有序接收(有序广播)
有序广播是按照广播接收者的优先级接收广播,
优先级比较高的广播接收者对象在接收到广播
以后可以选择广播要继续传递还是要终止传递。
发送有序广播相关方法:
sendOrderBroadcast(......)
广播接收者的优先级
1)静态注册:默认优先级从上到下依次递减
2)静态注册:可以在intent-filter元素中借助 priority属性配置优先级(范围-1000~+1000)
3)动态注册:优先级的设置需要借助IntentFilter
对象的setPriority()进行设置。
优先级别高的广播接收者可以通过abortBroadcast
方法终止广播的继续传递。
可以通过getResultData
获得相关内容,也可以通过setResultData等方法修改相关内容。
广播的收发
该组件接收被广播的 intent,Context 可以通过 sendBroadcast() 和 sendOrderedBroadcast()
方法实现广播的发送 .
首先在需要发送信息的地方 ,把要发送的信息和用于过滤的信息 ( 如 Action 、 Category) 装入一个 Intent 对象 ,然后通过调用 Context.sendBroadcast() 、 sendOrderBroadcast() 或 sendStickyBroadcast() 方法,把 Intent 对象以广播方式发送出去。
使用 sendBroadcast() 或 sendStickyBroadcast() 方法发出去的 Intent ,所有满足条件的 BroadcastReceiver 都会随机地执行其 onReceive() 方法
普通广播(Normal Broadcast)的发送和接收:
sendBroadcast(intent); //发送广播
Intent intent = new Intent( "cn.lenovo.yangguangf " );
sendBroadcast(intent);
priority :这个是 AndroidManifest.xml 中 intent-filter 的参数。
< receiver android:name = ".MyBroadcastReceiver" >
< intent-filter android:priority = "1000" >
< action android:name = "cn.lenovo.yangguangfu" />
</ intent-filter >
</ receiver >
sendOrderedBroadcast(intent, receiverPermission);
1 ,他决定该广播的级别,级别数值是在 -1000 到 1000 之间 , 值越大 , 优先级越高;
2 ,同级别接收是先后是随机的;级别低的收到广播;
3 ,在 android 系统中只要监听该广播的接收者,都能够收到 sendBroadcast(intent) 发出的广播 ;
4 ,不能截断广播的继续传播,
5 ,实验现象,在这个方法发来的广播中,代码注册方式中,收到的广播的先后和注明优先级最高的他们的先后是随机。如果都没有优先级,代码注册收到为最先。
有序广播(Ordered Broadcast)的发送和接收:
sendOrderedBroadcast(intent, receiverPermission);
sendOrderedBroadcast(intent, receiverPermission, resultReceiver,
scheduler, initialCode, initialData, initialExtras)
意图,广播,所有匹配的这一意图将接收机接收广播。
receiverPermission 这是权限,一个接收器必须持以接收您的广播。如果为 null ,不经许可的要求。
resultReceiver 您自己 BroadcastReceiver 来当作最后的广播接收器。
调度自定义处理程序,用以安排 resultReceiver 回调 ; 如果为 null 将语境中的主线程举行。
initialCode 一种结果代码的初始值。通常为 Activity.RESULT_OK 。这个值是 -1 ;为其他 int 型 也可以,如 0,1,2 ;
initialData 一种结果数据的初始值。通常情况下为空 , 是 String 类型 ;
initialExtras 一种结果额外的初始值。通常情况下为空 , 是 Bundle;
1, 该广播的级别有级别之分,级别数值是在 -1000 到 1000 之间 , 值越大 , 优先级越高;
2, 同级别接收是先后是随机的,再到级别低的收到广播;
3, 同级别接收是先后是随机的,如果先接收到的把广播截断了,同级别的例外的接收者是无法收到该广播的。( abortBroadcast() )
4 ,能截断广播的继续传播,高级别的广播收到该广播后,可以决定把该钟广播是否截断掉。
5 ,实验现象,在这个方法发来的广播中,代码注册方式中,收到广播先后次序为:注明优先级的、代码注册的、没有优先级的;如果都没有优先级,代码注册收到为最先。
异步广播的发送和接收:
sendStickyBroadcast(intent);
当处理完之后的Intent ,依然存在,直到你把它去掉。
发这个广播需要权限
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
去掉是用这个方法removeStickyBroadcast(intent); 但别忘了在执行这个方法的应用里面 AndroidManifest.xml 同样要加上面的权限;
sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,
initialCode, initialData, initialExtras)
这个方法具有有序广播的特性也有异步广播的特性;
发送这个广播要: <uses-permission android:name="android.permission.BROADCAST_STICKY" /> 这个权限。才能使用这个方法。如果您并不拥有该权限,将抛出 SecurityException 的。
实验现象( sendStickyOrderedBroadcast ()中),在这个方法发来的广播中,代码注册方式中,收到广播先后次序为:注明优先级的、代码注册的、没有优先级的;如果都没有优先级,代码注册收到为最先。
代码中注册广播:
注册广播方法一: registerReceiver(BroadcastReceiver receiver, IntentFilter filter) ,第一个参数是我们要处理广播的 BroadcastReceiver (广播接收者,可以是系统的,也可以是自定义的);第二个参数是意图过滤器。
注册广播方法二: registerReceiver(receiver, filter, broadcastPermission, scheduler) ,第一个参数是 BroadcastReceiver (广播接收者,可以是系统的,也可以是自定义的);第二个参数是意图过滤器;第三个参数是广播权限;第四个参数是 Hander ;
注意:权限重复现象,如果功能清单文件里注册了权限,在该方法再注册,则 receiver 无法收到广播,如果 功能清单文件里没有注册了权限,该方法注册也无法收到。当该方法没有注册权限,功能清单里注册的时候, receiver 能收到广播。
总结:在 Activity 中代码注册广播建议在: onResume() 中注册;
思维拓展: 1 ,如果在代码调用 registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 十次( receiver , filter 的参数是同一参数),那么是否当该广播发送来的时候会收到十次呢?
2 ,注销是否也要注销十次才能把广播全部注销呢?
BroadCastReceiver 的 API
abortBroadcast ():
这个方法可以截获由 sendOrderedBroadcast () 发送来的 广播,让其它广播接收者无法收到这个广播。
clearAbortBroadcast ()
这个方法是针对上面的 abortBroadcast() 方法的,用于取消截获广播。这样它的下一级广播接收者就能够收到该广播了。
getAbortBroadcast ()
这个方法作用是:判断是否调用了 abortBroadcast (),如果先调用 abortBroadcast (),接着再调用 getAbortBroadcast (),将返回 true; 如果在调用 abortBroadcast() 、 clearAbortBroadcast ()
getAbortBroadcast (),将返回 false;
public final boolean getDebugUnregister ()
Since: API Level 1
Return the last value given to setDebugUnregister(boolean) .
getResultCode ()
如果用下面四个方法发送得广播,返回码为: -1 ;
// sendBroadcast(intent);
// sendBroadcast(intent, receiverPermission);
// sendOrderedBroadcast(intent, receiverPermission);
// sendStickyBroadcast(intent);
如果用下面两个方法发送得广播,返回码为:根据你设置 initialCode 的数字是多少就是多少;
// sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,
// initialCode, initialData, initialExtras)
// sendOrderedBroadcast(intent, receiverPermission, resultReceiver,
// scheduler, initialCode, initialData, initialExtras)
getResultData ()
得到发送广播时设置的 initialData 的数据;
getResultExtras (boolean makeMap)
If true then a new empty Map will be made for you if the current Map is null; if false you should be prepared to receive a null Map.
得到由
sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,
// initialCode, initialData, initialExtras) ;
// sendOrderedBroadcast(intent, receiverPermission, resultReceiver,
// scheduler, initialCode, initialData, initialExtras)
中 initialExtras 传入的参数。
实验:我用上面两个方法发了 initialExtras (这个一个 Bundle )传入的参数时,只要不为空,那么 makeMap 是否为 true 和 false 都能够得到数据。
isInitialStickyBroadcast ()
Returns true if the receiver is currently processing the initial value of a sticky broadcast -- that is, the value that was last broadcast and is currently held in the sticky cache, so this is not directly the result of a broadcast right now.
如果广播接收者是目前处理的一个宿主的广播的初始值,将返回 true , - 也就是说,这个值是最后的广播出的值,目前正在举行的宿主缓存,所以这并不是直接导致了现在的广播。
实验:在第三个应用中调用这个方法,无论你用哪种方式发送广播,这个方法得到的总是 false ;在发送广播 的 resultReceiver 广播接收者里面调用,得到的也是 false ;
isOrderedBroadcast ()
sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras)
上面这个方法发送时,得到的是 true;
判断是否是有序广播;
d) Activity(呈现view,实现与用户交互)
***Activity 组件?Day19-3
1.Activity 是什么?
1)Android 中的一个应用组件?(生命周期方法)
2)Android 中的Context?(强大的资源访问能力)
3)Android 中的Controller?(呈现view,访问model)
2.Activity 的应用场合?
1)呈现UI,实现与用户交互。
2)控制UI逻辑,访问model/模型。
Activity包含一个window窗口,可以此窗口呈现
UI对象。
3.Activity 对象的应用?
activity对象生命周期(对象从创建到销毁)
a)onCreate (Activity 创建时执行)
b)onRestart (Activity重新启动时执行)
c)onStart (Activity启动时执行)
d)onResume(Activity 运行时执行)
e)onPause (Activity 暂停时执行)
f)onStop (Activity 停止时执行)
g)onDestroy(Activity 销毁时执行)
2)activity对象状态保存(可以保存在内存或外存)
a)onSaveInstanceState (此方法用户状态保存)
b)onRestoreInstanceState(此方法用于状态恢复,也可以在onCreate方法进行状态恢复)
说明:
当app所在进程处于后台运行时,进程的优先级就
会降低,在内存不足时有可能会被kill,当一旦被
kill后,假如再次运行希望数据被恢复,可以在activity暂停时将状态信息进行保存。
应用效果
打开应用时
先后执行了onCreate()->onStart()->onResume三个方法
BACK键:
当我们按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法
HOME键:
当我们打开应用程序时,比如浏览器,我正在浏览NBA新闻,看到一半时,我突然想听歌,这时候我们会选择按HOME键,然后去打开音乐应用程序,而 当我们按HOME的时候,Activity先后执行了onPause()->onStop()这两个方法,这时候应用程序并没有销毁。
再次启动ActivityDemo应用程序时
先后分别执行了onRestart()->onStart()->onResume()三个方法
4.Activity 对象的回退栈?
Activity在启动以后会存储在一个回退栈中,
栈顶元素为正在运行的activity,栈底最先启动
的那个activity.一个app进程可能会存在很多
这样的回退栈。
5.Activity 对象的启动模式(launchMode)?
Activity在启动时是可以指定启动模式的,一般
可以在清单配置文件中通过修改activity的launchMode属性进行配置,它的值有如下四种
类型:
standard(标准模式):
每次启动activity都会重新
创建一个新的实例。(此模式为默认模式)
1)每次启动Activity时,都会创建该Activity的新实例
2)总是在当前任务中启动Activity的新实例
3 ) 一个任务中可以包含同一Activity的多个实例
4)同一Activity的多个实例 可以位于多个任务中
2)singleTop(栈顶模式):
当此activity已经是栈顶时,再次启动不会创建新的activity.
1)当该Activity不位于栈顶时,其表现与标准模式一致
2)当该Activity位于栈顶时,再次启动该Activity不会创建新的实例
而是调用onNewIntent方法将启动意图传入处理,然后在使该实例
重新进入交互状态
3)singleTask(单任务模式):
一个任务栈中此activity
的实例只能有一份,当它不是栈顶时,再次启动它
时,位于它上面的activity会销毁。
1)当该Activity启动时,首先判断当前任务的亲族值(taskAffinity)是否与该Activity的亲族值相同
如果相同则在当前任务中创建实例,否则在新的任务中创建实例
注:默认不设置亲族值,那么每个Task的亲族值都一样。
2)设置为singleTask的Activity是单例的。
3)当重新启动该Activity时,首先将任务栈中位于其上的其他Activity销毁,再
使该Activity进入交互状态
4)singleInstance(单实例模式):
在内存中此activity
实例只有一份,且会独享一个任务栈。
1) 设置为singleInstance的Activity是单例的。
2)该Activity不与其他Activity共存于同一任务中(假设为A任务)
所以创建该Activity实例一定是在新的任务中 (假设为B任务)
当通过该Activity启动其他Activity时,首先判断
是否存在与被启动Activity亲族值相同的任务,如果存在
则在该任务中创建Acitivity的实例,否则在新的任务中 (假设为C任务)
创建实例
这端话的意思就是说:当你进入一个应用时,开辟了A任务,然后当点击一个启动模式为singleInstance的Activity B 的时候,那么该Activity B 就一定会开辟任务B,然后在从该Activity B 启动其他 Activity C 的时候,那么就会判断Activity C 的亲族值和任务B的亲族值(其实就是Activity B 的亲族值)是否相同,如果相同,Activity C 就会在B任务中创建,否则开辟任务C,作为根Activity来创建实例。
6.Activity 的亲族设置?
Activity的亲族设置与启动模式会影响activity在
任务栈中的一个存储位置,亲族设置的配置需要
借助activity的taskAffinity属性进行配置。
在android中每个任务栈都有一个亲族值,此
值与回退栈的栈底activity的亲族值相同,每个
activity都有亲族值,假如没有配置默认与application对象的亲族值相同,application对
象的亲族值假如没有配置默认与清单配置文件
的根元素指定的包名(package)相同。
亲族值一般会与activity的单任务模式和单实例
模式一起使用。
singleTask模式的activity在启动时,首先检测内存中有没有相同亲族值的任务栈,假如没有会创建一个新的任务栈进行存储。通过singleTask模式启动的非singleInstance模式的activity会与这个singleTask模式的activity存储在同一个任务栈中。
singleInstance模式的activity在启动其它的activity时,这个activity存储在哪里取决于有没有相同亲族配置的任务栈,假如有则直接存储,假如没有则会创建一个新的任务栈,再进行存储。
taskAffinity
1、任务吸附值或任务亲族值
2、对于每个Application来说如果未明确设置,则该值与应用程序的主包名一致
3、对于每个Activity来说,如果未明确设置,则该值与Activity所在的|Application的taskAffinity值相同
4、对于task来说,该值与其任务栈栈底的Acitivity的taskAffinity值相同
1.onWindowFocusChanged方法:在Activity窗口获得或失去焦点时被调用,例如创建时首次呈现在用户面前;当前Activity被其他Activity覆盖;当前Activity转到其他Activity或按Home键回到主屏,自身退居后台;用户退出当前Activity。以上几种情况都会调用onWindowFocusChanged,并且当Activity被创建时是在onResume之后被调用,当Activity被覆盖或者退居后台或者当前Activity退出时,它是在onPause之后被调用,如图所示:
这个方法在某种场合下还是很有用的,例如程序启动时想要获取视特定视图组件的尺寸大小,在onCreate中可能无法取到,因为窗口Window对象还没创建完成,这个时候我们就需要在onWindowFocusChanged里获取;如果大家已经看过我写的Android动画之Frame Animation这篇文章就会知道,当时试图在onCreate里加载frame动画失败的原因就是因为窗口Window对象没有初始化完成,所以最后我将加载动画的代码放到了onWindowFocusChanged中,问题迎刃而解。不过大家也许会有疑惑,为什么我在代码里将它注释掉了,因为对当前Activity每一个操作都有它的执行log,我担心这会影响到整个流程的清晰度,所以将它注掉,大家只要了解它应用的场合和执行的顺序就可以了。
2.onSaveInstanceState:(1)在Activity被覆盖或退居后台之后,系统资源不足将其杀死,此方法会被调用;(2)在用户改变屏幕方向时,此方法会被调用;(3)在当前Activity跳转到其他Activity或者按Home键回到主屏,自身退居后台时,此方法会被调用。第一种情况我们无法保证什么时候发生,系统根据资源紧张程度去调度;第二种是屏幕翻转方向时,系统先销毁当前的Activity,然后再重建一个新的,调用此方法时,我们可以保存一些临时数据;第三种情况系统调用此方法是为了保存当前窗口各个View组件的状态。onSaveInstanceState的调用顺序是在onPause之前。
3.onRestoreInstanceState:(1)在Activity被覆盖或退居后台之后,系统资源不足将其杀死,然后用户又回到了此Activity,此方法会被调用;(2)在用户改变屏幕方向时,重建的过程中,此方法会被调用。我们可以重写此方法,以便可以恢复一些临时数据。onRestoreInstanceState的调用顺序是在onStart之后。
练习
一个任务的activity启动另外一个任务栈的activity(singletask模式),此activity所在任务栈已经有多个
activity.
a)同一个app内部activity之间的启动
b)不同app的activity之间的启动(隐式意图)
注意:
在使用隐式意图启动某个activity时,此acitivty
的intent-filter内部还需要添加一个默认分类:
android.intent.category.DEFAULT
1.动作测试
<intent-filter>元素中可以包括子元素<action>,
一条<intent-filter>元素至少应该包含一个<action>,否则任何Intent请求都不能和该<intent-filter>匹配。如果Intent
请求的Action和<intent-filter>中个某一条<action>匹配,那么该Intent就通过了这条<intent-filter>的动作测试。如果
Intent请求或<intent-filter>中没有说明具体的Action类型,那么会出现下面两种情况。
(1) 如果<intent-filter>中没有包含任何Action类型,那么无论什么Intent请求都无法和这条<intent-filter>匹配;
(2) 反之,如果Intent请求中没有设定Action类型,那么只要<intent-filter>中包含有Action类型,这个Intent请求就将顺
利地通过<intent-filter>的行为测试。
2.类别测试
<intent-filter>元素可以包含<category>子元素
只有当Intent请求中所有的Category与组件中某一个IntentFilter的<category>完全匹配时,才会让该 Intent请求通过测试
,IntentFilter中多余的<category>声明并不会导致匹配失败。一个没有指定任何类别测试的 IntentFilter仅仅只会匹配没
有设置类别的Intent请求。
3.数据测试
数据在<intent-filter>中的描述如下
<data>元素指定了希望接受的Intent请求的数据URI和数据类型,URI被分成三部分来进行匹配:scheme、 authority和path
。其中,用setData()设定的Inteat请求的URI数据类型和scheme必须与IntentFilter中所指定的一致。若IntentFilter中还指定了authority或path,它们也需要相匹配才会通过测试。
❑ action
使用 android:name 特性来指定对响应的动作名。动作名必须是独一无二的字符串,所以,一个好的习惯是使用基于 Java 包的命名方式的命名系统。
❑ category
使用 android:category 属性用来指定在什么样的环境下动作才被响应。每个 Intent Filter 标签可以包含多个 category 标签。你可以指定自定义的种类或使用 Android 提供的标准值,如下所示:
❑ ALTERNATIVE
你将在这章的后面所看到的,一个 Intent Filter 的用途是使用动作来帮忙填入上下文菜单。 ALTERNATIVE 种类指定,在某种数据类型的项目上可以替代默认执行的动作。例如,一个联系人的默认动作时浏览它,替代的可能是去编辑或删除它。
❑ SELECTED_ALTERNATIVE
与 ALTERNATIVE 类似,但 ALTERNATIVE 总是使用下面所述的 Intent 解析来指向单一的动作。SELECTED_ALTERNATIVE在需要一个可能性列表时使用。
❑ BROWSABLE
指定在浏览器中的动作。当 Intent 在浏览器中被引发,都会被指定成 BROWSABLE 种类。
❑ DEFAULT
设置这个种类来让组件成为 Intent Filter 中定义的 data 的默认动作。这对使用显式 Intent 启动的 Activity 来说也是必要的。
❑ GADGET
通过设置 GADGET 种类,你可以指定这个 Activity 可以嵌入到其他的 Activity 来允许。
❑ HOME
HOME Activity 是设备启动(登陆屏幕)时显示的第一个 Activity 。通过指定 Intent Filter 为 HOME 种类而不指定动作的话,你正在将其设为本地 home 画面的替代。
❑ LAUNCHER
使用这个种类来让一个 Activity 作为应用程序的启动项。
❑ data
data 标签允许你指定组件能作用的数据的匹配;如果你的组件能处理多个的话,你可以包含多个条件。你可以使用下面属性的任意组合来指定组件支持的数据:
❑ android:host
指定一个有效的主机名(例如, com.google )。
❑ android:mimetype
允许你设定组件能处理的数据类型。例如,<type android:value=”vnd.android.cursor.dir/*”/>能匹配任何 Android 游标。
❑ android:path
有效地 URI 路径值(例如, /transport/boats/ )。
❑ android:port
特定主机上的有效端口。
❑ android:scheme
需要一个特殊的图示(例如, content 或 http )。
扩展:Activity屏幕方向的相关知识
我们可以为一个Activity指定一个特定的方向,指定之后即使转动屏幕方向,显示方向也不会跟着改变:
1.指定为竖屏:在AndroidManifest.xml中对指定的Activity设置android:screenOrientation="portrait",或者在onCreate方法中指定:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//竖屏2.指定为指定为横屏:在AndroidManifest.xml中对指定的Activity设置android:screenOrientation="landscape",或者在onCreate方法中指定:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
//横屏为应用中的Activity设置特定的方向是经常用到的办法,可以为我们省去不少不必要的麻烦。不过,我们今天讲的是屏幕方向改变时的生命周期,所以我们并不采用固定屏幕方向这种办法。
首先我们需要进入“Settings->Display”中,将“Auto-rotate Screen”一项选中,表明可以自动根据方向旋转屏幕,然后我们就可以测试流程了,当我们旋转屏幕时,我们发现系统会先将当前Activity销毁,然后重建一个新的:
系统先是调用onSaveInstanceState方法,我们保存了一个临时参数到Bundle对象里面,然后当Activity重建之后我们又成功的取出了这个参数。
为了避免这样销毁重建的过程,我们需要在AndroidMainfest.xml中对OrientationActivity对应的<activity>配置android:configChanges="orientation",然后我们再测试一下,我试着做了四次的旋转,打印如下:
可以看到,每次旋转方向时,只有onConfigurationChanged方法被调用,没有了销毁重建的过程。
以下是需要注意的几点:
1.如果<activity>配置了android:screenOrientation属性,则会使android:configChanges="orientation"失效。
2.模拟器与真机差别很大:模拟器中如果不配置android:configChanges属性或配置值为orientation,切到横屏执行一次销毁->重建,切到竖屏执行两次。真机均为一次。模拟器中如果配置android:configChanges="orientation|keyboardHidden"(如果是Android4.0,则是"orientation|keyboardHidden|screenSize"),切竖屏执行一次onConfigurationChanged,切横屏执行两次。真机均为一次。
Activity的生命周期与程序的健壮性有着密不可分的关系,希望朋友们能够认真体会、熟练应用。
---------------------------------------------------
作业:
1.总结
1)BroadcastReceiver
2)Activity
2.完成四大核心组件的整合,实现音乐播放
3.预习Fragment,Intent
---------------------------------------------------
Day20-1 内容回顾
---------------------------------------------------
1.BroadcastReceiver 组件
1)what (组件,广播接收者)
2)when(实现一对多跨进程通讯,也可以是进程内部)
3)why(需要进行一对多的进程通讯,可以同时)
4)how (编写,注册,解除注册,生命周期,有序实现)
2.Activity组件
1)what(组件,context,控制器)
2)when(呈现view,控制ui逻辑,访问model)
3)why(呈现view,控制ui逻辑)
4)how(编写,注册,生命周期,状态保存,任务栈,启动模式,亲族设置)
----------------------------------------------------------------
e) 关联组件(Fragment,Intent,Application)
Fragment模块化对象?Day20-2
1.Fragment 是什么?
fragment在应用中应当是一个模块化和可重用的组件
1)Activity中的模块化对象。
2)Android3.0中的一个新特性。
2.Fragment 应用场合?(when,why)
1)适配各种屏幕尺寸.
2)对界面中的UI内容进行模块化.
例如:
1)radiogroup+fragment+viewpager 实现底部菜单
2)actionBar(tab)+fragment+viewpager 实现tab导航
3).....
3.Fragment 对象的应用?
1)类的编写(直接或间接继承fragment,无需在清单配置文件注册)
2)对象的构建(直接执行new的动作)
3)将对象添加到activity(静态方式,动态方式)
4)fragment对象与activity的交互(生命周期方法)
a)fragment: getActivity()
b)activity: findFragmentById,findFragmentByTag
注意:fragment对象的生命周期受activity对象
生命周期的影响。
。
4.其它Fragment对象
1)ListFragment
2)DialogFragment
3)FragmentTabHost
4)......
5.Fragmeng优点
•Fragment可以使你能够将activity分离成多个可重用的组件,每个都有它自己的生命周期和UI。
•Fragment可以轻松得创建动态灵活的UI设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
•Fragment是一个独立的模块,紧紧地与activity绑定在一起。可以运行中动态地移除、加入、交换等。
•Fragment提供一个新的方式让你在不同的安卓设备上统一你的UI。
•Fragment 解决Activity间的切换不流畅,轻量切换。
•Fragment 替代TabActivity做导航,性能更好。
•Fragment 在4.2.版本中新增嵌套fragmeng使用方法,能够生成更好的界面效果。
•Fragment做局部内容更新更方便,原来为了到达这一点要把多个布局放到一个activity里面,现在可以用多Fragment来代替,只有在需要的时候才加载Fragment,提高性能
可以看到Fragment比Activity多了几个额外的生命周期回调方法:
onAttach(Activity):在fragment与Activity关联之后调用。需要注意的是,初始化fragment参数可以从getArguments()获得,但是,当Fragment附加到Activity之后,就无法再调用setArguments()。所以除了在最开始时,其它时间都无法向初始化参数添加内容。有关Fragment参数初始化及传递的问题,我们会在后面的篇章中细讲。
当Fragment与Activity发生关联时调用。
onCreate:fragment初次创建时调用。尽管它看起来像是Activity的OnCreate()函数,但这个只是用来创建Fragment的。此时的Activity还没有创建完成,因为我们的Fragment也是Activity创建的一部分。所以如果你想在这里使用Activity中的一些资源,将会获取不到。比如:获取同一个Activity中其它Frament的控件实例。(代码如下:),如果想要获得Activity相关联的资源,必须在onActivityCreated中获取。
onCreateView(LayoutInflater, ViewGroup,Bundle)
在这个fragment构造它的用户接口视图(即布局)时调用。在这里期望返回此Fragment的一个视图层次结构。使用LayoutInflater的inflater()方法来构造实图。
创建该Fragment的视图
onActivityCreated(Bundle)
在Activity的OnCreate()结束后,会调用此方法。所以到这里的时候,Activity已经创建完成!在这个函数中才可以使用Activity的所有资源。如果把下面的代码放在这里,获取到的btn_Try的值将不会再是空的!
当Activity的onCreate方法返回时调用
onDestoryView()
与onCreateView想对应,当该Fragment的视图被移除时调用
onDetach()
与onAttach相对应,当Fragment与Activity关联被取消时调用
注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现
Fragment静态应用
这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中。步骤:
1、继承Fragment,重写onCreateView决定Fragemnt的布局
2、在Activity中声明此Fragment,就当和普通的View一样
Fragment动态应用
可以看到我们使用FragmentManager对Fragment进行了动态的加载,这里使用的是replace方法~~下一节我会详细介绍FragmentManager的常用API。
注:如果使用Android3.0以下的版本,需要引入v4的包,然后Activity继承FragmentActivity,然后通过getSupportFragmentManager获得FragmentManager。不过还是建议版Menifest文件的uses-sdk的minSdkVersion和targetSdkVersion都改为11以上,这样就不必引入v4包了。
代码中间还有两个Fragment的子类,ContentFragment上面已经见过,FriendFragment其实类似
FragmentTransaction.replace(R.id.layoutid//布局id,
new Fragment2()//fragment的构造方法);
Fragment家族常用的API
Fragment常用的三个类:
android.app.Fragment 主要用于定义Fragment
android.app.FragmentManager 主要用于在Activity中操作Fragment
android.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词,一定能明白~
a、获取FragmentManage的方式:
getFragmentManager() // v4中,getSupportFragmentManager
b、主要的操作都是FragmentTransaction的方法
FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
transaction.add()
往Activity中添加一个Fragment
transaction.remove()
从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。
transaction.replace()
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~
transaction.hide()
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
transaction.show()
显示之前隐藏的Fragment
detach()
会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
attach()
重建view视图,附加到UI上并显示。
transatcion.commit()//提交一个事务
注意:常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。
上述,基本是操作Fragment的所有的方式了,在一个事务开启到提交可以进行多个的添加、移除、替换等操作。
值得注意的是:如果你喜欢使用Fragment,一定要清楚这些方法,哪个会销毁视图,哪个会销毁实例,哪个仅仅只是隐藏,这样才能更好的使用它们。
a、比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望会到A还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。
b、再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。
c、remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。
Activity与Fragment生命周期的对比图
onStart:当到OnStart()时,Fragment对用户就是可见的了。但用户还未开始与Fragment交互。在生命周期中也可以看到Fragment的OnStart()过程与Activity的OnStart()过程是绑定的。意义即是一样的。以前你写在Activity的OnStart()中来处理的代码,用Fragment来实现时,依然可以放在OnStart()中来处理。
onResume:当这个fragment对用户可见并且正在运行时调用。这是Fragment与用户交互之前的最后一个回调。从生命周期对比中,可以看到,Fragment的OnResume与Activity的OnResume是相互绑定的,意义是一样的。它依赖于包含它的activity的Activity.onResume。当OnResume()结束后,就可以正式与用户交互了。
onPause:此回调与Activity的OnPause()相绑定,与Activity的OnPause()意义一样。
onStop:这个回调与Activity的OnStop()相绑定,意义一样。已停止的Fragment可以直接返回到OnStart()回调,然后调用OnResume()。
onDestroyView:如果Fragment即将被结束或保存,那么撤销方向上的下一个回调将是onDestoryView()。会将在onCreateView创建的视图与这个fragment分离。下次这个fragment若要显示,那么将会创建新视图。这会在onStop之后和onDestroy之前调用。这个方法的调用同onCreateView是否返回非null视图无关。它会潜在的在这个视图状态被保存之后以及它被它的父视图回收之前调用。
onDestroy:当这个fragment不再使用时调用。需要注意的是,它即使经过了onDestroy()阶段,但仍然能从Activity中找到,因为它还没有Detach。
onDetach:Fragment生命周期中最后一个回调是onDetach()。调用它以后,Fragment就不再与Activity相绑定,它也不再拥有视图层次结构,它的所有资源都将被释放。
查看V4包的源码
-----------------------------------------
1. 找到sdk的路径
2. 在sdk下依次打开extras -> android -> support -> v4 -> src,并将完整路径复制
3. 在项目的libs文件夹下,创建android-support-v4.jar.properties文件,并编辑,内容为 src=复制的路径
4. 在eclipse中将项目关闭(close)然后重新打开(open)
src=D://software//android-sdk-windows//extras//android//support//v4//src//java
Day21-1 内容回顾
---------------------------------------------------
1.Fragment 是什么?
2.Fragment 的应用场合?
1)适配不同屏幕尺寸
2)activity ui内容的模块化
3.Fragment 对象的应用?
1)编写,注册(允许在布局文件注册或java代码)
2)实现与activity的关联,交互
3)常用组合案例(......)
4.其它Fragment对象?
1)ListFragment
2)DialogFragment,..........
----------------------------------------------------
?intent 对象Day21-2
1.Intent 是什么?
1)意图,信使
2)值对象(封装数据,实现数据传递)
2.Intent对象的应用场合?
1)启动组件(activity,service,BroadcastReceiver)
2)停止service,解除receiver的动态注册
3)数据传递(组件之间)
3.Intent对象实现原理及过程?
1)封装意图信息(你要做什么)
2)封装数据信息(实现数据传递)
startActivity(intent)
startService(intent)
sendBroadcastReceiver(intent)
意图会通过context对象的相关传递给底层系统,
底层根据intent中封装的具体意图信息找到对应
的组件,并启动他。
4.意图对象应用方式?
1)显式意图(明确指定要启动的组件)
2)隐式意图(没有明确指定要启动的组件,只是传递
字符串给底层系统,底层去匹配对象)
对于一个隐式意图对象常用的配置:(在intent-filter中实现)
1)action(代表具体意图)
2)Category (代表分类,环境)
3)data (数据,可以指定类型)
说明:在使用隐式意图启动activity时,此activity
的intent-filter中需要添加一个默认分类(DEFAULT)
隐式意图一般应用于跨app启动其它组件.
?扩展:PendingIntent(延迟意图),此对象内部可以
封装一个意图对象。
Intent对象其它配置:
1)setFlags(int n):重点关注的是和启动模式相关一些标记
2)setDataAndType(uri,type)
3)......
intent
常用的四大组件
启动一个Activity:Context.startActivity(Intent intent);
启动一个Service:Context.startService(Intent service);
绑定一个Service:Context.bindService(Intent service, ServiceConnection conn, int flags);
发送一个Broadcast:Context.sendBroadcast(Intent intent);
Intent是系统各组件之间进行数据传递的数据负载者。当我们需要做一个调用动作,我们就可以通过Intent告诉Android系统来完成这个过程,Intent就是调用通知的一种操作。
我们为Intent指定相应的action,然后调用startActivity方法后,系统会根据action跳转到对应的Activity
data和extras,即执行动作要操作的数据和传递到目标的附加信息(setData&putExtra)
在进行搜索时,我们使用了一个putExtra方法,将关键字做为参数放置在Intent中,我们成为extras(附加信息),这里面涉及到了一个Bundle对象。
Bundle和Intent有着密不可分的关系,主要负责为Intent保存附加参数信息,它实现了android.os.Paracelable接口,内部维护一个Map类型的属性,用于以键值对的形式存放附加参数信息。在我们使用Intent的putExtra方法放置附加信息时,该方法会检查默认的Bundle实例为不为空,如果为空,则新创建一个Bundle实例,然后将具体的参数信息放置到Bundle实例中。我们也可以自己创建Bundle对象,然后为Intent指定这个Bundle即可
Intent intent = new Intent("com.scott.intent.action.TARGET");
Bundle bundle = new Bundle();
bundle.putInt("id", 0);
bundle.putString("name", "scott");
intent.putExtras(bundle);
startActivity(intent);
startActivity(Intent):
用于开启一个新的Activity,新Activity压入栈顶。参数Intent用于描述新Activity。
startActivityForResult(Intent,int):
用于开启新Activity,新Activity退出后,返回结果给旧Activity
Intent既可以显式的指定类去打开,也可以包含目标需要执行的动作。在后者的情况下,运行时会选择Activity去打开,使用一个熟知的处理过程——“Intent解析”
startActivity方法查找、启动与Intent最匹配的单一Activity。
当使用startActivity时,新启动的Activity结束是你的应用程序不会接收到任何通知。为了追踪打开画面的反馈,使用startActivityForResult方法,在后面会描述更多细节。
隐式意图和显式意图
intent主要包括隐式意图和显式意图。显式意图通常主要是启动本应用中的Activity之间的数据,而隐式意图则常见于启动系统中的某些特定的动作,比如打电话,发短信,或者是跨应用的Activity启动(如在QQ点击链接地址启动一个浏览器Activity)。
显式意图:调用Intent.setComponent()、Intent.setClass()、Intent.setClassName()方法明确指定了组件名的Intent为显式意图,显式意图明确指定了Intent应该传递给哪个组件。
隐式意图:没有明确指定组件名的Intent为隐式意图。 Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。
扩展:Intent-filter意图过滤器属性详解
------------------------------
IntentFilter的作用是对Intent进行过滤,使得其中一部分Intent可以放行,而另一部分将被拦截,亦可以理解为是判断Intent是否匹配某些条件。
IntentFilter对Intent的过滤,可以通过以下属性进行过滤:
1. Action
2. Category
通俗的原则:IntentFilter中的Action、Category,相关Intent对象中的Action、Category只能多,不能少,或者理解为在Intent对象中的Action、Category在同一个IntentFilter中都可以找到。
注意:如果Intent对象中已经显式的指定了ComponentName属性,则不会进行任何匹配,直接激活显式指定的组件。
注意:关于Intent与IntentFilter的匹配,还需要参考Type和Data属性。
如果一个 Intent 请求在一片数据上执行一个动作, Android 如何知道哪个应用程序(和组件)能用来响应这个请求呢?
Intent Filter就是 用来注册 Activity 、 Service 和 Broadcast Receiver 具有能在某种数据上执行一个动作的能力。
使用 Intent Filter ,应用程序组件告诉 Android ,它们能为其它程序的组件的动作请求提供服务,包括同一个程序的组
件、本地的或第三方的应用程序。
为了注册一个应用程序组件为 Intent 处理者,在组件的 manifest 节点添加一个 intent-filter 标签。
---------------------------------------------------
扩展:Parcelable
Parcelable实现要点:需要实现三个东西
1)writeToParcel 方法。该方法将类的数据写入外部提供的Parcel中.声明如下:
writeToParcel (Parcel dest, int flags) 具体参数含义见javadoc
2)describeContents方法。没搞懂有什么用,反正直接返回0也可以
3)静态的Parcelable.Creator接口,本接口有两个方法:
createFromParcel(Parcel in) 实现从in中创建出类的实例的功能
newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话(return new T[size])即可。估计本方法是供外部类反序列化本类数组使用。
1. Parcelable接口
Interface for classes whose instances can be written to and restored from a Parcel。 Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator interface。
2.实现Parcelable就是为了进行序列化,那么,为什么要序列化?
1)永久性保存对象,保存对象的字节序列到本地文件中;
2)通过序列化对象在网络中传递对象;
3)通过序列化在进程间传递对象。
3.实现序列化的方法
Android中实现序列化有两个选择:一是实现Serializable接口(是JavaSE本身就支持的),一是实现Parcelable接口(是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC))。实现Serializable接口非常简单,声明一下就可以了,而实现Parcelable接口稍微复杂一些,但效率更高,推荐用这种方法提高性能。
注:Android中Intent传递对象有两种方法:一是Bundle.putSerializable(Key,Object),另一种是Bundle.putParcelable(Key,Object)。当然这些Object是有一定的条件的,前者是实现了Serializable接口,而后者是实现了Parcelable接口。
4.选择序列化方法的原则
1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。
5.应用场景
需要在多个部件(Activity或Service)之间通过Intent传递一些数据,简单类型(如:数字、字符串)的可以直接放入Intent。复杂类型必须实现Parcelable接口。
Parcelable接口定义
public interface Parcelable
{
//内容描述接口,基本不用管
public int describeContents();
//写入接口函数,打包
public void writeToParcel(Parcel dest, int flags);
//读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入
//为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例
public interface Creator<T>
{
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
}
实现Parcelable步骤
1)implements Parcelable
2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据
3)重写describeContents方法,内容接口描述,默认返回0就可以
4)实例化静态内部对象CREATOR实现接口Parcelable.Creator
public static final Parcelable.Creator<T> CREATOR注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。
简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。
1)Serializable的实现,只需要implements Serializable 即可。这只是给对象打了一个标记,系统会自动将其序列化。
2)Parcelabel的实现,不仅需要implements Parcelabel,还需要在类中添加一个静态成员变量CREATOR,这个变量需要实现 Parcelable.Creator 接口。
两者代码比较:
1)创建Person类,实现Serializable
public class Person implements Serializable
{
private static final long serialVersionUID = -7060210544600464481L;
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}2)创建Book类,实现Parcelable
public class Book implements Parcelable
{
private String bookName;
private String author;
private int publishDate;
public Book()
{
}
public String getBookName()
{
return bookName;
}
public void setBookName(String bookName)
{
this.bookName = bookName;
}
public String getAuthor()
{
return author;
}
public void setAuthor(String author)
{
this.author = author;
}
public int getPublishDate()
{
return publishDate;
}
public void setPublishDate(int publishDate)
{
this.publishDate = publishDate;
}
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags)
{
out.writeString(bookName);
out.writeString(author);
out.writeInt(publishDate);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>()
{
@Override
public Book[] newArray(int size)
{
return new Book[size];
}
@Override
public Book createFromParcel(Parcel in)
{
return new Book(in);
}
};
public Book(Parcel in)
{
bookName = in.readString();
author = in.readString();
publishDate = in.readInt();
}
}
?Application 对象Day21-3
1.Application 是什么?
1)Android 中的一个Context对象(资源访问能力)
2)Android 中的全局访问对象(生命周期同APP生命周期相同)
2.Application 对象应用场合?
1)为应用组件提供全局的数据访问(共享数据)
2)记录相应数据信息(不适合大量数据)
例如:
1)用户登录成功以后的用户信息。
2)各个组件都要使用的少量数据信息。
3.Application 对象的应用?
1)编写(继承Application),
2)注册(清单配置文件中指定application的name属性)
3)生命周期(onCreate,......)
说明:我在Application中存储数据也可以直接借助
静态的属性存储数据。
案例实现:
在考试系统中用户使用学车日记,首先检测用户
是否登录OK,假如没有登录则直接跳转到登录界面
进行登录,登录成功以后将用户信息写到application
中。
---------------------------------------------------
总结Day21-4
1.Android UI
a)Layout (CommonLayout,Adapter Layout)
b)InputControls(Buttons,TextFileds,Bars)
c)InputEvents(onClick,onKey,onTouch,onChecked)
d)UI Components(Menu,ActionBar,Dialog,Notification,Toast,Setting)
e)自定义view (直接或间接继承view,重写onDraw,自定义属性,资源回收)
f) 动画(属性动画,帧动画,补间动画)
g)绘制(Bitmap,Canvas,Paint)
2.Android Data Store
1)外部存储(外置sdcard)
2)内部存储(内置sdcard)
3)偏好设置(data/data/项目/share_prefs/.xml)
4)SQLite存储(DBMS,DB,table,view,sql,adb指令)
3.Android Thread,Message
a)Android 中的线程基础(Java中线程应用)
b)Android 中的线程同步(Java中的线程同步)
c)Android 中的消息模型(Message,MessageQueue,handle,looper,)
d)Android 中的异步任务(AsyncTask)
e)Android 中的线程池(Executor,Executors)
案例:加载大图片(异步,压缩,缓存)
4.Android Components
a) Content Provider (跨进程私有数据共享)
b) Service (长时间的后台耗时操作)
c) BroadCastReceiver(实现一对多的跨进程数据传递)
d) Activity(呈现view,实现与用户交互)
e) 关联组件(Fragment,Intent,Application)
---------------------------------------------------
Day21-5 综合案例:(项目改写)
1.学车日记点击时没有登录提示登录
2.点击(娱乐):更新播放进度,时间
3.完成上一首,下一首的播放请求