Android 四大组件 五大存储 六大布局
最近girl friend需要搞Android基础汇报,在不情不愿却又你情我愿的的情况下,一起搞定了她的汇报文档,搞都搞了就想着自己也巩固下基础,做一份总结,希望可以让对Android感兴趣的朋友对Android的基础知识有些了解
1. Android四大组件
android四大组件分别是:Activity, service,content provider,broadcast receiver
生命周期相关概念
Android系统是一个多任务(Multi-Task)的操作系统,可以在用手机听音乐的同时,也执行其他多个程序。每多执行一个应用程序,就会多耗费一些系统内存,当同时执行的程序过多,或是关闭的程序没有正确释放掉内存,系统就会觉得越来越慢,甚至不稳定。
为了解决这个问题, Android 引入了一个新的机制—生命周期(Life Cycle)。
Android 应用程序的生命周期是由Android 框架进行管理,而不是由应用程序直接控制。通常,每一个应用程序(入口一般会是一个Activity 的onCreate 方法),都会产生一个进程(Process)。当系统内存即将不足的时候,会依照优先级自动进行进程(process)的回收。不管是使用者或开发者, 都无法确定的应用程序何时会被回收。所以为了很好的防止数据丢失和其他问题,了解生命周期很重要。
1.1 Activity
1.1.1 概念
-
应用程序中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。
-
Activity之间通过Intent进行通信。在Intent 的描述结构中,有两个最重要的部分:动作和动作对应的数据。
-
典型的动作类型有:MAIN(activity的门户)、VIEW、PICK、EDIT 等。而动作对应的数据则以URI 的形式进行表示。例如:要查看一个人的联系方式,你需要创建一个动作类型为VIEW 的intent,以及一个表示这个人的URI。
-
与之有关系的一个类叫IntentFilter。相对于intent 是一个有效的做某事的请求,一个intentfilter 则用于描述一个activity(或者IntentReceiver)能够操作哪些intent。一个activity 如果要显示一个人的联系方式时,需要声明一个IntentFilter,这个IntentFilter 要知道怎么去处理VIEW 动作和表示一个人的URI。IntentFilter 需要在AndroidManifest.xml 中定义。通过解析各种intent,从一个屏幕导航到另一个屏幕是很简单的。当向前导航时,activity 将会调用startActivity(Intent myIntent)方法。然后,系统会在所有安装的应用程序中定义的IntentFilter 中查找,找到最匹配myIntent 的Intent 对应的activity。新的activity 接收到myIntent 的通知后,开始运行。当startActivity 方法被调用将触发解析myIntent 的动作,这个机制提供了两个关键好处:
-
Activities 能够重复利用从其它组件中以Intent 的形式产生的一个请求;
-
Activities 可以在任何时候被一个具有相同IntentFilter 的新的Activity 取代。
AndroidManifest文件中含有如下过滤器的Activity组件为默认启动类当程序启动时系统自动调用它
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /></intent-filter>
-
1.1.2 生命周期
介绍生命周期之前,先提一下任务的概念
任务其实就是activity 的栈它由一个或多个Activity组成的共同完成一个完整的用户体验, 换句话说任务就是” 应用程序” (可以是一个也可以是多个,比如假设你想让用户看到某个地方的街道地图。而已经存在一个具有此功能的activity 了,那么你的activity 所需要做的工作就是把请求信息放到一个Intent 对象里面,并把它传递给startActivity()。于是地图浏览器就会显示那个地图。而当用户按下BACK 键的时候,你的activity 又会再一次的显示在屏幕上,此时任务是由2个应用程序中的相关activity组成的)栈底的是启动整个任务的Activity,栈顶的是当前运行的用户可以交互的Activity,当一个activity 启动另外一个的时候,新的activity 就被压入栈,并成为当前运行的activity。而前一个activity 仍保持在栈之中。当用户按下BACK 键的时候,当前activity 出栈,而前一个恢复为当前运行的activity。栈中保存的其实是对象,栈中的Activity 永远不会重排,只会压入或弹出,所以如果发生了诸如需要多个地图浏览器的情况,就会使得一个任务中出现多个同一Activity 子类的实例同时存在。
任务中的所有activity 是作为一个整体进行移动的。整个的任务(即activity 栈)可以移到前台,或退至后台。举个例子说,比如当前任务在栈中存有四个activity──三个在当前activity 之下。当用户按下HOME 键的时候,回到了应用程序加载器,然后选择了一个新的应用程序(也就是一个新任务)。则当前任务遁入后台,而新任务的根activity 显示出来。然后,过了一小会儿,用户再次回到了应用程序加载器而又选择了前一个应用程序(上一个任务)。于是那个任务,带着它栈中所有的四个activity,再一次的到了前台。当用户按下BACK 键的时候,屏幕不会显示出用户刚才离开的activity(上一个任务的根activity)。取而代之,当前任务的栈中最上面的activity 被弹出,而同一任务中的上一个activity 显示了出来。
Activity栈:先进后出规则
Activity的生命周期:
如图,Activity整个生命周期中存在4种状态、7个重要方法和3个嵌套循环。
四种状态
-
活动(Active/Running)状态
当Activity运行在屏幕前台(处于当前任务活动栈的最上面),此时它获取了焦点能响应用户的操作,属于运行状态,同一个时刻只会有一个Activity 处于活动(Active)或运行(Running)状态
-
暂停(Paused)状态
当Activity失去焦点但仍对用户可见(如在它之上有另一个透明的Activity或Toast、AlertDialog等弹出窗口时)它处于暂停状态。暂停的Activity仍然是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接),但是当系统内存极小时可以被系统杀掉
-
停止(Stopped)状态
完全被另一个Activity遮挡时处于停止状态,它仍然保留着所有的状态和成员信息。只是对用户不可见,当其他地方需要内存时它往往被系统杀掉
-
非活动(Dead)状态
Activity 尚未被启动、已经被手动终止,或已经被系统回收时处于非活动的状态,要手动终止Activity,可以在程序中调用"finish"方法。
如果是(按根据内存不足时的回收规则)被系统回收,可能是因为内存不足了
内存不足时,Dalvak 虚拟机会根据其内存回收规则来回收内存:
- 先回收与其他Activity 或Service/Intent Receiver 无关的进程(即优先回收独立的Activity)因此建议,我们的一些(耗时)后台操作,最好是作成Service的形式
- 不可见(处于Stopped状态的)Activity
- Service进程(除非真的没有内存可用时会被销毁)
- 非活动的可见的(Paused状态的)Activity
- 当前正在运行(Active/Running状态的)Activity
七个重要方法
Activity 类中定义了七个回调方法,覆盖了活动生命周期的每一个环节。当Activity从一种状态进入另一状态时系统会自动调用下面相应的方法来通知用户这种变化。
-
onCreate(Bundle savedInstanceState)
当Activity第一次被实例化的时候系统会调用,整个生命周期只调用1次这个方法
通常用于初始化设置:
1、为Activity设置所要使用的布局文件
2、为按钮绑定监听器等静态的设置操作 -
onStart()
当Activity可见未获得用户焦点不能交互时系统会调用
-
onRestart()
当Activity已经停止然后重新被启动时系统会调用
-
onResume()
当Activity可见且获得用户焦点能交互时系统会调用
-
onPause()
当系统准备去启动或者恢复另一个Activity的时候调用。当系统启动另外一个新的Activity时,在新Activity启动之前被系统调用保存现有的Activity中的持久数据、停止动画等,这个实现方法必须非常快。当系统而不是用户自己出于回收内存时,关闭了activity 之后。用户会期望当他再次回到这个activity 的时候,它仍保持着上次离开时的样子。此时用到了onSaveInstanceState(),方法onSaveInstanceState()用来保存Activity被杀之前的状态,在onPause()之前被触发,当系统为了节省内存销毁了Activity(用户本不想销毁)时就需要重写这个方法了,当此Activity再次被实例化时会通过onCreate(Bundle savedInstanceState)将已经保存的临时状态数据传入因为onSaveInstanceState()方法不总是被调用,触发条件为(按下HOME键,按下电源按键关闭屏幕,横竖屏切换情况下),你应该仅重写onSaveInstanceState()来记录activity的临时状态,而不是持久的数据。应该使用onPause()来存储持久数据。
-
onStop()
当Activity被新的Activity完全覆盖不可见时被系统调用
-
onDestroy()
当Activity(用户调用finish()或系统由于内存不足)被系统销毁杀掉时系统调用,(整个生命周期只调用1次)用来释放onCreate ()方法中创建的资源,如结束线程等
3个嵌套循环
-
Activity完整的生命周期
Activity在 onCreate() 方法和 onDestroy() 方法之间所经历的,就是完整生存期。一般情况下,一个活动会在 onCreate() 方法中完成各种初始化操作,而在 onDestroy() 方法中完成释放内存的操作。
-
Activity的可视生命周期
Activity在 onStart() 方法和 onStop() 方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在 onStart() 方法中对资源进行加载,而在 onStop() 方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
-
Activity的前台生命周期
Activity在 onResume() 方法和 onPause() 方法之间所经历的,就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行相互的,我们平时看到和接触最多的也这个状态下的活动。
举例说明:
例1: 有3个Acitivity,分别用One,Two(透明的),Three表示,One是应用启动时的主Activity
启动第一个界面Activity One时,它的次序是
onCreate (ONE) - onStart (ONE) - onResume(ONE)
点"打开透明Activity"按钮时,这时走的次序是
onPause(ONE) - onCreate(TWO) - onStart(TWO) - onResume(TWO)
再点back回到第一个界面,Two会被杀这时走的次序是
onPause(TWO) - onActivityResult(ONE) - onResume(ONE) - onStop(TWO) -onDestroy(TWO)
点"打开全屏Activity"按钮时,这时走的次序是
onPause(ONE) - onCreate(Three) - onStart(Three) - onResume(Three) - onStop(ONE)
再点back回到第一个界面,Three会被杀这时走的次序是
onPause(Three) - onActivityResult(ONE) - onRestart(ONE) - onStart(ONE) -onResume(ONE) - onStop(Three) - onDestroy(Three)
再点back退出应用时,它的次序是
onPause(ONE) - onStop(ONE) - onDestroy(ONE)
例2: 横竖屏切换时候Activity的生命周期
他切换时具体的生命周期是怎么样的:
-
新建一个Activity,并把各个生命周期打印出来
-
运行Activity,得到如下信息
onCreate–>
onStart–>
onResume–>
-
按crtl+f12切换成横屏时
onSaveInstanceState–>
onPause–>
onStop–>
onDestroy–>
onCreate–>
onStart–>
onRestoreInstanceState–>
onResume–>
-
再按crtl+f12切换成竖屏时,发现打印了两次相同的log
onSaveInstanceState–>
onPause–>
onStop–>
onDestroy–>
onCreate–>
onStart–>
onRestoreInstanceState–>
onResume–>
onSaveInstanceState–>
onPause–>
onStop–>
onDestroy–>
onCreate–>
onStart–>
onRestoreInstanceState–>
onResume–>
-
修改AndroidManifest.xml,把该Activity添加android:configChanges=“orientation”,执行步骤3
onSaveInstanceState–>
onPause–>
onStop–>
onDestroy–>
onCreate–>
onStart–>
onRestoreInstanceState–>
onResume–>
-
再执行步骤4,发现不会再打印相同信息,但多打印了一行onConfigChanged
onSaveInstanceState–>
onPause–>
onStop–>
onDestroy–>
onCreate–>
onStart–>
onRestoreInstanceState–>
onResume–>
onConfigurationChanged–>
-
把步骤5的android:configChanges=“orientation” 改成 android:configChanges=“orientation|keyboardHidden”,执行步骤3,就只打印onConfigChanged
onConfigurationChanged–>
-
执行步骤4
onConfigurationChanged–>
onConfigurationChanged–>
总结:
-
不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
-
设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
-
设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
补充一点,当前Activity产生事件弹出Toast和AlertDialog的时候Activity的生命周期不会有改变
Activity运行时按下HOME键(跟被完全覆盖是一样的):onSaveInstanceState --> onPause --> onStop,再次进入激活状态时: onRestart -->onStart -->onResume
1.1.3 四种启动模式
启动模式一共有 4 种:standard、singleTop、singTask 和 singleInstance,可以在 AndroidManifest.xml 中通过 标签指定 android:launchMode 属性来选择启动模式。
-
standard 模式
standard 模式是 Activity 的默认启动模式,在不进行显示指定的情况下,所有 Activity 都会自动使用这种启动模式。这种模式下,每当启动一个新的 Activity,它就会在返回栈的栈顶位置。对于使用 standard 模式启动的 Activity,系统不会在乎这个 Activity 是否已经存在在栈中了。每次启动的时候都会创建一个新的实例。
谁启用了这个模式的 Activity,那么这个 Activity 就属于启动它的 Activity 的任务栈。模式示意图
-
singleTop 模式
如果 Activity 指定为 singleTop,在启动 Activity 的时候发现返回栈的栈顶已经是该 Activity 了。则认为可以直接使用它,就不会再创建新的 Activity 实例了。
因为不会创建新的 Activity 实例,所以 Activity 的生命周期就没有什么变化了。但是它的 onNewIntent 方法会被调用。模式示意图
-
singleTask 模式
singleTop 很好的解决了重复创建栈顶 Activity 的问题。如果 Activity 没有处于栈顶的位置,还是可能会创建多个 Activity 实例的。如何解决这种问题呢?那就需要借助 singleTask 了。当 Activity 的启动模式为 singleTask 的时候,每次启动该 Activity 的时候系统会首先在返回栈中检查是否存在该 Activity 的实例,如果发现已经存在则直接使用该实例。并把这个 Activity 之上的所有 Activity 全部出栈,如果没有就会创建一个新的 Activity 实例。
生命周期正常调用,onNewIntent 也会被调用。模式示意图
-
singleInstance 模式
singleInstance 模式的 Activity 会启用一个新的返回栈来管理这个 Activity (其实如果 singleTask 模式指定了不同的 taskAffinity,也会启动一个新的返回栈)。意义:假如我们的程序中有一个 Activity 是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个 Activity 实例。那么如何实现呢?假如使用前面 3 中启动模式,肯定不行。因为,我们每个应用程序都有自己的返回栈,虽然是同样这个 Activity,但是在不同的返回栈入栈的时候肯定是创建了新的实例了。而 singleInstance 可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个 Activity,不管是那个应用程序来访问这个 Activity,都共用的同一个返回栈,也就解决了共享 Activity 实例的问题。
模式示意图
1.1.4 三种跳转方式
显示启动
Intrent 内部直接声明要启动的activity所对应的的class
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intnet);
隐式启动
进行三个匹配,一个是activity,一个是category,一个是data,全部或者部分匹配,应用于广播原理
-
清单文件中 里配置activity属性,activity的名字要和跳转内容一样
<activity android:name="com.exanple.android.tst.secondActivity" android:label = @string/title> <intent=filter> <action android:name="com.exanple.android.tst.secondActivity/> <category android:name="android.intent.category.DEFAULT"/> <intent-filter/> </activity>
-
在需要跳转的地方
Intent intent = new Intent("com.example.android.tst.secondActivity"); startActivity(intnet);
跳转后再返回,能获取返回值
Intent in = new Intent(MainActivity.this,OtehrActivity.class);
in.putExtra("a",a);
startActivityForResult(in,1000);
在OTherActivity中设置返回值
Intent int = new Intent();
int.putExtra("c",c);
setResult(1001,int);
finish();
在MainActivity中获取返回值
@Override
protected void onActivityResult(int requestCode, int resultCode ,Intent data) {
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == 1000){
if(resultCode == 1001){
int c = data.getExtra("c",0);
}
}
}
1.2 Service
1.2.1 概念
一个Service 是一段长生命周期的,没有用户界面的程序,可以用来开发如监控类程序。
比较好的一个例子就是一个正在从播放列表中播放歌曲的媒体播放器。在一个媒体播放器的应用中,应该会有多个activity,让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的activity,因为使用者当然会认为在导航到其它屏幕时音乐应该还在播放的。在这个例子中,媒体播放器这个activity 会使用Context.startService()来启动一个service,从而可以在后台保持音乐的播放。同时,系统也将保持这个service 一直执行,直到这个service 运行结束。另外,我们还可以通过使用Context.bindService()方法,连接到一个service 上(如果这个service 还没有运行将启动它)。当连接到一个service 之后,我们还可以service 提供的接口与它进行通讯。拿媒体播放器这个例子来说,我们还可以进行暂停、重播等操作。
Service使用步骤如下
-
继承service类
-
AndroidManifast.xml配置清单文件中节点里对服务进行配置
<service name=".SMSService"/>
1.2.2 Service的种类及生命周期
Service按照不同方式分类可以有多种类型,我们按照Google官网对service的分类方式,按照启动方式分为两类:Started和Bound。其中,Started()是通过startService()来启动,主要用于程序内部使用的Service,而Bound是通过bindService()来启动,允许多个应用程序共享同一个Service。
Started Service
通过startService()方式开启服务
使用service的步骤:
1,定义一个类继承service
2,manifest.xml文件中配置service
3,使用context的startService(Intent)方法启动service
4,不在使用时,调用stopService(Intent)方法停止服务
使用startService()方式启动的生命周期:
- startService() -> onCreate() -> onStartCommand() -> onDestroy()
注意: 如果服务已经开启,不会重复回调onCreate()方法,如果再次调用context.startService()方法,service而是会调用onStart()或者onStartCommand()方法。停止服务需要调用context.stopService()方法,服务停止的时候回调onDestory被销毁。
特点:
一旦服务开启就跟调用者(开启者)没有任何关系了。开启者退出了,开启者挂了,服务还在后台长期的运行,开启者不能调用服务里面的方法。
Bound Service
采用bindService()的方式开启服务
使用service的步骤:
1,定义一个类继承Service
2,在manifest.xml文件中注册service
3,使用context的bindService(Intent,ServiceConnection,int)方法启动service
4,不再使用时,调用unbindService(ServiceConnection)方法停止该服务
使用这种bindService()方式启动的service的生命周期如下:
- bindService() -> onCreate() -> onBind() ->unbindService() -> onUnbind() -> onDestroy()
注意: 绑定服务不会调用onStart()或者onStartCommand()方法
特点: bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法。
IntentService
IntentService是Service的子类,比普通的Service增加了额外的功能。先看Service本身存在两个问题:
- Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中;
- Service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务;
IntentService特征:
- 会创建独立的worker线程来处理所有的Intent请求;
- 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
- 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
- 为Service的onBind()提供默认实现,返回null;
- 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;
1.3 BroastReceiver
1.3.1 BroastReceiver分类
-
无序广播
- 使用Context.sendBroadcast()方法发送
- 完全异步,无优先级
- 效率高
-
有序广播
- 使用Context.sendOrderedBroadcast()方法发送
- 允许接收者设定优先级,并且根据优先级依次传递广
- 优先级高的接收者,可以对广播数据处理、继续传播、停止传播
-
本地广播
- 只能使用LocalBroadcastManager对象提供的方法注册和发送
- 只在当前App内传播
- 优先级更高
-
Sticky广播
- 使用Context.sendStickyBroadcast()方法发送
- 需要BROADCAST_STICKY权限
- Sticky广播发送后不会取消,滞留在系统中,等待接收者出现
- 接收者处理后需要手动调用removeStickyBoradcast取消
- 6.0已废弃(@Depracated)
1.3.2 BroastReceiver注册
-
静态注册
编写BroastReceiver子类,在AndroidManifest.xml中注册,通过设置广播优先级和过滤器。
-
动态注册
编写BroastReceiver子类,创建IntentFilter对象,设置广播优先级和过滤器,用Context.registerReceiver(mReceiver,mIntentFilter)等方法注册,使用Context.unregisterReceiver()取消注册。
- 动态广播需要手动取消,不需要可能导致内存泄漏,例如Activity内部类和Context对象等
- 出于安全考虑和效率原因,一些系统广播只能通过动态注册,例如SCREEN_ON、SCREEN_OFF、TIME_TICK等
静态注册和动态注册区别
- 动态注册广播不是常驻型广播,也就是说广播跟随activity的生命周期。注意: 在activity结束前,移除广播接收器。
静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。 - 当广播为有序广播时:
- 优先级高的先接收
- 同优先级的广播接收器,动态优先于静态
- 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
- 当广播为普通广播时:
- 无视优先级,动态广播接收器优先于静态广播接收器
- 同优先级的同类广播接收器,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的。
1.3.3 BroastReceiver生命周期
如果一个广播处理完onReceive 那么系统将认定此对象将不再是一个活动的对象,也就会finished掉它。
至此,大家应该能明白 Android 的广播生命周期的原理。
步骤:
1,自定义一个类继承BroadcastReceiver
2,重写onReceive方法
3,在manifest.xml中注册
注意 :
-
BroadcastReceiver生命周期很短,如果需要在onReceiver完成一些耗时操作,应该考虑在Service中开启一个新线程处理耗时操作,不应该在BroadcastReceiver中开启一个新的线程,因为BroadcastReceiver生命周期很短,在执行完onReceiver以后就结束,如果开启一个新的线程,可能出现BroadcastRecevier退出以后线程还在,而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。
-
动态注册广播接收器还有一个特点,就是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用
系统常见广播Intent,如开机启动、电池电量变化、时间改变等广播
1.3.4 BroastReceiver原理
Android的广播本质上是一种事件的订阅和发布机制。通过registerReceiver或者Manifest文件订阅消息,通过sendBroadcast等方法发送消息。
- 普通广播
普通广播消息由AMS管理,通过registerReceiver或者Manifest文件订阅消息,最后都会走AMS,由AMS统一派发。涉及进程间通信,效率有损耗。
- 本地广播
本地广播消息由LocalBroadcastManager管理,基于观察者模式,注册时记录Action、IntentFilter和Receiver对象,发送时遍历记录,通过Handler异步回调Receiver对象的onReceive方法。
1.4 ContentProvider
1.4.1 概念
contentprovider是android四大组件之一的内容提供器,它主要的作用就是将程序的内部的数据和外部进行共享,为数据提供外部访问接口,被访问的数据主要以数据库的形式存在,而且还可以选择共享哪一部分的数据。这样一来,对于程序当中的隐私数据可以不共享,从而更加安全。contentprovider是android中一种跨程序共享数据的重要组件。
1.4.2 使用系统的ContentProvider
系统的ContentProvider有很多,如通话记录,短信,通讯录等等,都需要和第三方的app进行共享数据。既然是使用系统的,那么contentprovider的具体实现就不需要我们担心了,使用内容提供者的步骤如下
- 获取ContentResolver实例
- 确定Uri的内容,并解析为具体的Uri实例
- 通过ContentResolver实例来调用相应的方法,传递相应的参数,但是第一个参数总是Uri,它制定了我们要操作的数据的具体地址
可以通过读取系统通讯录的联系人信息,显示在Listview中来实践这些知识。不要忘记在读取通讯录的时候,在清单文件中要加入相应的读取权限。
- 权限
属性 | 说明 |
---|---|
android:grantUriPermssions: | 临时许可标志。 |
android:permission: | Provider读写权限。 |
android:readPermission: | Provider的读权限。 |
android:writePermission: | Provider的写权限。 |
android:enabled: | 标记允许系统启动Provider。 |
android:exported: | 标记允许其他应用程序使用这个Provider。 |
android:multiProcess: | 标记允许系统启动Provider相同的进程中调用客户端。 |
-
ContentResolver
ContentResolver通过URI查询ContentProvider中提供的数据。ContentProvider是以类似数据库中表的方式将数据暴露出去,ontentResolver也是采用类似数据库的操作来从ContentProviders中获取数据。
-
ContentObserver
内容观察者,观察特定URI指向的ContentProvider数据变化,做出响应。(类似监听器)
-
进程间通信原理
- ContentProvider是通过IBinder实现通信过程的
- getContentResolver获得到的是ApplicationContentResolver(在ContextImpt中实现的)
- Client端ApplicationContentResolver使用ContentProviderProxy作为IBinder的Proxy(ContentProviderNative中实现)
- Provider端通过Transport作为IBinder的实现端(ContentProvider中实现)
-
注意事项
ContentProvider的onCreate方法在Application的onCreate方法之前,耗时操作会影响应用的启动。
-
优点
- 安全,开发了增删改查接口,但是没有直接开放数据库权限,避免数据库毁灭性危机
- 统一数据共享方式,解耦底层数据存储方式
- 更加高效简单的访问系统应用数据
1.4.3 自定义ContentProvider
系统的contentprovider在与我们交互的时候,只接受了一个Uri的参数,然后根据我们的操作返回给我们结果。系统到底是如何根据一个Uri就能够提供给我们准确的结果呢?只有自己亲自实现一个看看了。
和之前提到的一样,想重新自定义自己程序中的四大组件,就必须重新实现一个类,重写这个类中的抽象方法,在清单文件中注册,最后才能够正常使用。
重新实现ContentProvider之后,发现我们重写了6个重要的抽象方法
- oncreate
- query
- update
- insert
- delete
- gettype
大部分的方法在数据库那里已经见过了,他们内部的逻辑可想而知都是对数据的增删改查操作,其中这些方法的第一个参数大多都是Uri实例。其中有两个方法比较特殊:
- oncreate方法应该是内容提供者创建的时候所执行的一个回调方法,负责数据库的创建和更新操作。这个方法只有我们在程序中获取ContentResolver实例之后准备访问共享数据的时候,才会被执行。
- gettype方法是获取我们通过参数传递进去的Uri的MIME类型,这个类型是什么,后面会有实例说明。
内容提供者首先要做的一个事情就是将我们传递过来的Uri解析出来,确定其他程序到底想访问哪些数据。
1.4.4 Uri
统一资源标识符,唯一标识ContentProvider的数据。
格式
schema + authority + path + id
例如:content://media/external/images/media/61645616
组成 | 说明 | 举例 |
---|---|---|
schema | Android固定为content:// | content:// |
authority | 标识ContentProvider的唯一字符串,注册时指定的android:authority | media |
path | 标识 authority 数据的某些子集 | external/images/media |
id | 标识 path 子集中的某个记录,不指定是标识全部记录 | 61645616 |
2. Android五大存储
2.1 文件存储
默认存储路径: /data/data/<PackageName>/files
文件操作模式:
- MODE_PRIVATE(默认):覆盖、
- MODE_APPEND:追加
-
写入文件
public void save(){ String data = "save something here"; FileOutputStream out = null; ButteredWriter writer = null; try{ out = openFileOutput("data",Context.MODE_PRIVATE); writer = new ButteredWriter(new OutputSreamWriter(out)); writer.write(data); }catch(IOException e){ e.printStackTrace(); }finally{ try{ if(writer!=null){ writer.close(); } }catch(IOException e){ e.printStackTrace(); } }
-
读取数据
public String load(){ FileInputStream in = null; ButteredReader reader = null; StringBuilder builder = new StringBuilder(); try{ in = openFileInput("data"); reader = new ButteredReader(new InputStreamReader(in)); String line= ""; while((line = reader.readline()) != null){ builder.append(); } }catch(IOException e){ e.printStackTrace(); }finally{ if(reader != null){ try{ reader.close(); }catch(IOException e){ e.printStackTrace(); } } } }
2.2 SharedPreferences
默认存储路径: /data/data/<PackageName>/shared_prefs
操作模式:
- MODE_PRIVATE(默认):只有当前的应用程序才能对文件进行读写、
- MODE_MULTI_PROCESS:用于多个进程对同一个SharedPreferences进行读写。
存储数据格式: 键值对
获取SharedPreferences对象的方法
- Context的getSharedPreferences()方法,参数一是文件名,参数二是操作模式
- Activity的getPreferences()方法,参数为操作模式,使用当前应用程序包名为文件名
- PreferenceManager的getDefaultSharedPreferences()静态方法,接收Context参数,使用当前应用程序包名为文件名
存储数据
- 调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象
- 向Editor对象中添加数据putBoolean、putString等
- 调用commit()方法提交数据
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name","ZhangSan");
editor.putInt("age",12);
editor.putBoolean("isMarried",false);
editor.commit();
从SharedPreferences文件中读取数据
SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE);
String name = pref.getString("name");
int age = pref.getInt("age");
boolean isMarried = pref.getBoolean("isMarried");
2.3 SQLite数据库存储
默认存储路径: /data/data/<PackageName>/databases
数据类型
- integer 整型
- real 浮点型
- text 文本类型
- blob 二进制类型
public class MyDatabaseHelper extends SQLiteOpenHelper{
public static final String CREATE_BOOK = "create table book ( "
+ " id integer primary key autoincrement,"
+ " author text,"
+ "price real,"
+ "pages integer,"
+ "name text)";
private Context context;
public MyDatabaseHelper (Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
this.context = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
//当打开数据库时传入的版本号与当前的版本号不同时会调用该方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
在MainActivity中
MyDatabaseHelper helper = new MyDatabaseHelper(this,"BookStore.db",null,1);
//检测到没有BookStore这个数据库,会创建该数据库并调用MyDatabaseHelper中的onCreated方法。
helper.getWritableDatabase();
升级数据库
public class MyDatabaseHelper extends SQLiteOpenHelper{
......
//当打开数据库时传入的版本号与当前的版本号不同时会调用该方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
onCreate(db):
}
}
在MainActivity中只需将version改为大于原来版本号即可。
MyDatabaseHelper helper = new MyDatabaseHelper(this,"BookStore.db",null,2);
helper.getWritableDatabase();
向数据库添加数据
insert()方法,参数一表名,参数二是在未指定添加数据的情况下给某些可为空的列自动赋值为NULL,设置为null即可,参数三是ContentValues对象。
MainActivity
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name","The Book Name");
values.put("author","chen");
values.put("pages",100);
values.put("price",200);
db.insert("Book",null,values);
更新数据库中的数据
update()方法,参数一是表名,参数二是ContentValues对象,参数三、四是去约束更新某一行或某几行的数据,不指定默认更新所有。
MainActivity
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",120);
db.update("Book",values,"name= ?",new String[]{"The Book Name"});
从数据库中删除数据
delete()方法,参数一是表名,参数二、三是去约束删除某一行或某几行的数据,不指定默认删除所有。
MainActivity
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("Book","pages> ?",new String[]{"100"});
查询数据库中的数据
query()方法,参数一是表名,参数二是指定查询哪几列,默认全部,参数三、四是去约束查询某一行或某几行的数据,不指定默认查询所有,参数五是用于指定需要去group by的列,参数六是对group by的数据进一步的过滤,参数七是查询结果的排序方式
MainActivity
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("Book",null,null,null,null,null,null);
if(cursor.moveToFirst()){
do{
String name = cursor.getString(cursor.getColumnIndex("name");
String author = cursor.getString(cursor.getColumnIndex("author");
int pages = cursor.getString(cursor.getColumnIndex("pages");
double price = cursor.getString(cursor.getColumnIndex("price");
}while(cursor.moveToNext());
}
cursor.close():
使用SQL语句操作数据库
//添加数据
db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?) "
,new String[]{"The Book Name","chen",100,20});
//更新数据
db.execSQL("update Book set price = ? where name = ?",new String[]
{"10","The Book Name"});
//删除数据
db.execSQL("delete from Book where pages > ?",new String[]{"100"});
//查询数据
db.execSQL("select * from Book",null);
使用事务操作
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction(); //开启事务
try{
......
db.insert("Book",null,values);
db.setTransactionSuccessful(); //事务成功执行
}catch(SQLException e){
e.printStackTrace();
}finally{
db.endTransaction(); //结束事务
}
2.4 ContentProvider
ContentProvider主要用于不同的程序之间实现数据共享的功能。
- 访问其他应用程序中的数据
工具类ContentResolver
,提供了一系列方法对数据进行CRUD操作。
ContentResolver的使用方法
-
内容URI
内容URI是由权限和路径组成的,权限是用于区分不同的应用程序,一般是以包名来命名。路径是用于区分同一个应用程序的不同表。//包名为com.example.app的表table1访问路径 Uri uri = Uri.parse("content://com.example.app.provider/table1");
-
使用Uri对象进行数据操作
-
查询
Cursor cursor = getContentResolver().query(uri,null,null,null,null); if(cursor != null){ while(cursor.moveToNext()){ String column1 = cursor.getString(cursor.getColumnIndex("column1")); String column2 = cursor.getString(cursor.getColumnIndex("column2")); } cursor.close(); }
-
插入
ContentValues values = new ContentValues(); values.put("column1","text"); values.put("column2",1); getContentResolver().insert(uri,values);
-
2.5 网络存储
网络存储需要与Android的数据包进行交互操作。
Android的网络存储使用HTTP协议,我们编写的Android网络应用就相当于一个浏览器。
3. Android六大布局
3.1 LinearLayout 线性布局
线性布局,如名字所描述的那样,这个布局将它所包含的控件在线性方向上一次排列,方向分为 水平方向和数值方向。
- 属性 android:orientation = “vertical” | “horizontal” 竖直或水平,默认水平
- 属性 android:layout_gravity = “top” | “center” | “bottom” 内部的布局方式
- 属性 android:gravity = “top”|“center”|“bottom” 相对于父容器的对齐方式
- 属性 android:layout_weidht 使用比例方式执行控件的大小,在手机屏幕适配方面起到非常重要的作用
3.2 TableLayout 表格布局
表格布局与HTML中的table td tr标签类似
<table>
<tr><td></td></tr>
</table>
如何确定行与列
- 如果在TableLayout下添加组件,这个组件会占满整行
- 如果想把多个组件放在同一行,需要添加TableRow的容器,然后把组件放进去
- TableRow中的组件个数决定的该行的列数,而列的宽度由列中最宽的单元格决定
- TableRow嗯layout_width属性默认是fill-parent,修改无效。但是layout_height默认是wrapcontent,可以修改
- 整个表格的宽度取决于父容器的宽度(占满父容器)
重要的属性:
- android:collapaseColumns:设置需要被隐藏的列的序号
- android:shrinkColumns:设置允许被收缩的列的序号
- android:stretchCoumns:设置运行被拉伸嗯列的序号
这三个属性都是从0开始算的
shrinkColumns= "2" //对应第三行
shrinkColumns = '"0,2" //设置多个都生效
shrinkColumns = "" //所有列都生效
- android:layout_column=“2”: 表示跳过第二个,直接显示第三个,从1开始
- android:layout_span=“4”:表示合并*4个单元格,也就说这个组件占4个单元格
3.3 FrameLayout 帧布局
FrameLayout布局在不使用layout_gravity属性的情况下,布局中的多项元素会在父容器的左上角重叠,使用layout_gravity 属性,可以设置不同的位置。
重要属性
- top、bottom、left、right:将对象放在其容器的上、下、左、右的位置
- center、center_vertical、center_horizontal:讲对象居中、水平居中、竖直居中
注意 : 区分 “android:gravity” 和 “android:layout_gravity”
- android:gravity :是对控件本身而言,控制控件自身的内容在控件中的位置
- android:layout_gravity:是相对于控件父容器而言,设置该控件在其父容器中的位置
3.4 RelativeLayout 相对布局
相对布局是用的比较多的一种布局。因为布局套用越多,布局加载越慢,如果使用相对布局,紧紧只需要一次布局。一般使用布局都是相对布局+线性布局使用。
相对布局主要记住重要的属性
注意: margin 和 padding 的区别
- margin: 代表的是偏移,是相对于父容器而言
- padding: 代表的是填充,是本组件内部而言
3.5 GridLayout 网格布局
网格布局与TableLayout(表格布局)类似,不过网格布局功能更多,也更好用。
- 可以设置布局中组件的排列方式
- 可以设置网格布局有几行几列
- 可以直接设置组件的位置,位于某行某列
- 可以直接设置组件占多少行多少列
使用网格布局,需要先设置排列方式、对齐方式、行数、列数。然后对布局中的子组件进行行列设置。
3.6 AbsoluteLayout 绝对布局
绝对布局放在最后,是因为绝对布局基本上是不使用的。一般布局需要适配不同机型,如果使用绝对布局,在不同尺寸的手机上显示会变形。
重要属性
- android:layout_x: 设置组件的x坐标
- android:layout_y: 设置组件的y坐标
<AbsoluteLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="50dp"
android:layout_y="100dp"
android:text="=" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="150dp"
android:layout_y="100dp"
android:text="=" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="100dp"
android:layout_y="120dp"
android:text="." />
</AbsoluteLayout>