Activity
1.页面跳转
1.1 配置文件中配置的参数解释
<!-- android:name:节点对应的类名称 -->
<!-- android:label:App启动图标的名称,Application节点下的名称是软件名称,在设置中查看即显示软件名称 -->
<!-- android:icon:软件的启动图标,同上 -->
<activity
android:name="com.heima.personclac.ResultActivity"
android:label="@string/app_name"
android:icon="@drawable/test"
>
<!-- main 主入口,如果你想让你的应用程序有多个启动图标,就在对应的activity节点中配置下面注释的intent-filter节点 -->
<!--
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
-->
<intent-filter>
<action android:name="com.heima.result" /><!-- 标识你这个activity的字符串名字,可以随意自定义 -->
<data android:scheme="heima"/><!-- 作为activity中放置的数据的约束,你想要传递数据必须符合这里定义的约束格式 -->
<category android:name="android.intent.category.DEFAULT" /><!-- 标识默认,不配置为LANUCHER通常就配置为默认 -->
</intent-filter>
</activity>
1.2 隐式意图跳转
意义:开启一个界面,通过指定的一组动作或者数据,主要用来开启别的应用的界面
//隐式意图 开启一个界面
//创建一个意图对象
Intent intent = new Intent();
//设置意图的动作,就是Manifest文件中对应要开启Activity的action标签【必须】
intent.setAction("com.heima.result");
//对应的Data标签,清单文件中没有【:】,
intent.setData(Uri.parse("heima:"+"nihao"));
//对应的catagory标签【必须】
intent.addCategory("android.intent.category.DEFAULT");
startActivity(intent);
1.3 显示意图
意义:一般通过指定的类和包名开启界面,主要用来开启自己应用中的其他界面
//显示意图
Intent intent = new Intent(this,ResultActivity.class);
//intent.setClassName(this, "com.heima.clacperson.ResultActivity");较麻烦
//开启新界面
startActivity(intent);
1.4 小知识
<activity android:name="com.heima.mobilesafe55.activity.LocationDialogActivity"
android:excludeFromRecents="true"----------从系统最近应用应用列表中排除掉
android:theme="@android:style/Theme.Translucent.NoTitleBar"----------------Activity的背景透明设置
/>
<manifest
android:installLocation="auto/internalOnly/preferExternal"------------表示优先装在手机内存, 如果内存不够,装sdcard
internalOnly------------只能装在手机内存, 默认选项
preferExternal------------优先装在sdcard
>
2 Activity类
int | BIND_AUTO_CREATE | 从Context类继承的字段,作用是作为开启连接服务的连接类型值 |
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void |
setContentView(int layoutResID)
设置界面中显示的内容通过布局Id
| ||||||||||
void |
setContentView(
View view)
设置界面中显示的内容通过对象
| ||||||||||
View |
findViewById(int id)
通过控件Id查找相应的控件
| ||||||||||
void |
startActivity(
Intent intent)
开启一个界面
| ||||||||||
Intent |
getIntent()
获取开启此界面的意图对象
| ||||||||||
void |
startActivityForResult(
Intent intent, int requestCode)
开启一个新界面,通过requestCode请求码来标识这个界面,
一般去某界面只是为了获取一个值时用此方
| ||||||||||
void |
onActivityResult(int requestCode, int resultCode,
Intent data)
这是一个受保护的方法,可以获取某界面的返回值,requestCode:标识不同界面;data:封装了传回本界面的数据
| ||||||||||
void |
finish()
关闭当前界面,等同于手机返回键
| ||||||||||
void |
finishActivity(int requestCode)
关闭当前界面,等同于手机返回键,不过可以同时关闭requestCode值相同的一类界面
| ||||||||||
void |
onCreate(
Bundle savedInstanceState)
生命周期方法,
受保护的方法,在activity第一次创建时被调用
| ||||||||||
void |
onStart()
生命周期方法,受保护的方法,当activity变成可见时被调用
| ||||||||||
void |
onResume()
生命周期方法,
受保护的方法,当activity上的按钮可以被点击时被调用
| ||||||||||
void |
onPause()
生命周期方法,
受保护的方法,当activity上的按钮不可以被点击时调用,当有透明应用覆盖时你不能点击按钮却能看见按钮
| ||||||||||
void |
onStop()
生命周期方法,
受保护的方法,当avtivity不再可见时被调用
| ||||||||||
void |
onDestroy()
生命周期方法,
受保护的方法,当activity被销毁时调用
| ||||||||||
void |
onRestart()
生命周期方法,受保护的方法,当activity通过点击手机的Home键而达到不可见的状态,也就是Stop状态,这时不是activity不会被destory。当再次开启软件,这时此方法会被调用,并且接下来回到start方法的时期那里去
| ||||||||||
int |
getTaskId()
获取当前任务栈的id,任务栈会给每一个应用分配一个任务栈id
| ||||||||||
void |
onBackPressed()
当手机的返回按键被点击时调用
| ||||||||||
void |
sendBroadcast(
Intent intent)
发送一个无序广播,是从ContextWrapper继承的方法
| ||||||||||
abstract void |
sendOrderedBroadcast(
Intent intent,
String receiverPermission,
BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode,
String initialData,
Bundle initialExtras)
发送一个有序广播,是从ContextWrapper继承的方法,参数:意图对象;接受时所需的权限;最终接受者,一定最后运行,不管中间有没有接受者中断广播;处理器;初始码;初始数据;初始化值,经常是null
| ||||||||||
final void |
setResult(int resultCode)
设置一个结果码,来标记是从哪个界面的返回的,此结果码被
startActivityForResult方法接受
| ||||||||||
abstract ComponentName |
startService(
Intent service)
从context类继承的方法,开启一个服务
| ||||||||||
abstract boolean |
stopService(
Intent service)
从context类继承的方法,关闭一个服务
| ||||||||||
boolean |
bindService(
Intent service,
ServiceConnection conn, int flags)
从context类继承的方法。conn:是开启服务后对服务状态进行监视的连接器,可用这个连接器获取服务中的IBinder对象,从而调用服务对象中的方法;flags:绑定的类型,值在该类成员字段处
| ||||||||||
void |
unbindService(
ServiceConnection conn)
从context类继承的方法,接触绑定服务。
当调用了多次bindService方法时,会绑定多个不同连接器对象,这是再次调用解绑方法会报异常,因为他不知道解绑哪个连接了。conn:传入已绑定服务的连接器对象
| ||||||||||
abstract Intent |
registerReceiver(
BroadcastReceiver receiver,
IntentFilter filter)
从context类继承的方法,动态注册广播的方法
| ||||||||||
abstract void |
unregisterReceiver(
BroadcastReceiver receiver)
从context类继承的方法,解绑注册的广播,当Activity中注册了广播时,必须在onDestroy方法中添加这个方法。否则报错
| ||||||||||
abstract ContentResolver |
getContentResolver()
从context类继承的方法,获取内容解析者
| ||||||||||
final boolean |
requestWindowFeature(int featureId)
通过这个方法,然后传入参数Window.FEATURE_NO_TITLE可清除标题栏
| ||||||||||
void |
overridePendingTransition(int enterAnim, int exitAnim)
设置进入下一个Activity,退出本Activity的动画效果,
必须在startActivity(Intent)之后执行
| ||||||||||
boolean |
onTouchEvent(
MotionEvent event)
Activity触摸监听事件的方法
| ||||||||||
boolean |
onKeyDown(int keyCode,
KeyEvent event)
当键盘按键被点击时触发方法,参数1:系统会把被点击按钮的key值传入此方法,所以你可以通过与KeyEvnent中的全局常量值判断来使用。如
KeyEvent.KEYCODE_MENU |
3.生命周期
特殊:
(1)手机横竖屏切换时就是界面完全关闭再重启的过程。会从新走一遍Activity的生命周期。解决方法:<activity android:srceenOrientation="值"> 值可以是【portrait】——竖屏,【landscpe】——横屏。这样设置你横竖屏生命周期方法不会从新执行一遍。不过此属性只在4.0以上系统能用
4.任务栈概念和Activity的四种启动模式
4.1 任务栈
栈:先进后出;队列:先进先出
(1)定义:打开一个界面就会有一个Activity进栈,每按一次返回(关闭)都会清除栈结构顶层的Activity,显示出下一层的
(2)意义:这时用来维护Activity中打开和关闭关系的,也就是维护用户的操作体验的
4.2 四种启动模式
在清单文件activity节点下可以配置如下属性 <activity android:launchMode="模式值">,四种模式如下解释
(1)standard:默认的启动模式
(2)singleTop:单一顶部模式,如果任务栈栈顶存在这个要开启的Activity,不重新创建Activity,复用过去的。保证栈顶如果存在,则不重复创建。应用场景:浏览器的书签页面就是使用了这个启动模式,用户添加书签时不会因为多次点击更添加了大量重复的书签
(3)singleTask:单一任务栈,在当前任务栈里面只能有一个实例存在。当开启此类型的Activity时,会去检查任务栈里面是否有实例已经存在,如果有实例就复用这个已经存在的实例,并且把这个Activity之上的所有Activity全部清空。这样就可以保证任务栈里面只有一个这个的实例存在。应用场景:游戏实玩界面、浏览器界面(BrowserActivity)就是用的这种启动模式
(4)singleInstance:这种启动模式非常特殊,此类型的Activity会运行自定新开辟的一个任务栈之中,并且这个任务栈之中里面只有一个实例存在。如果你想保证一个Activity在真个手机操作系统里面只有一个实例存在,请使用这种启动模式。应用场景:(InCallScreen)来电话的界面,有道词典,当你在进行任何其他应用,这种模式的Activity会被立刻启动
Intent连接的桥梁
1.Intent类
Constants | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
String | ACTION_CALL | 打电话Activity界面的Action值 | |||||||||
int | FLAG_ACTIVITY_NEW_TASK | 设置一个标着,通过setFlags方法传递给目标Activity,表示这个Activity自己维护自己的任务栈,如果任务栈中没有当前任务,就会创建一个任务放入任务栈。文档中有多个Flag值都是让目标Activity执行一些特殊操作的,可以去看一下 |
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Intent |
putExtras(
Intent src)
存储另一个Intent类型的对象
| ||||||||||
Intent |
putExtras(
Bundle extras)
存储Bundle类型的对象
| ||||||||||
Intent |
putExtra(
String name,
Parcelable[] value)
Add extended data to the intent.
| ||||||||||
Intent |
putExtra(
String name,
Serializable value)
Add extended data to the intent.
| ||||||||||
Intent |
putExtra(
String name,
String value)
除Object类外其他常用类型都可以存储,键值对形式
| ||||||||||
Bundle |
getExtras()
获取Bundle类型传递过来的数据
| ||||||||||
String |
getStringExtra(
String name)
获取字符串类型的数据,通过键获取值
| ||||||||||
Intent |
setAction(
String action)
设置Action值,来指明
跳转的界面、
发出的广播的事件、建立的服务等等
| ||||||||||
Intent |
setData(
Uri data)
设置传递数据并且按照对方定义的规则书写
| ||||||||||
Intent |
addCategory(
String category)
设置分类值,值为对方定义好的
| ||||||||||
String |
getAction()
获取Action值,可以判断是否是自己想要的界面、广播、服务
| ||||||||||
Intent |
setType(
String type)
设置传递数据的类型,当对应Intent-filter节点中配置了mimeType时你需要调用这个方法设置成相应的值
| ||||||||||
Intent |
setFlags(int flags)
告诉接受该Intent的Activity一些标识,通过这些标识这个Activity会修改一定的模式
|
2.常用隐式意图的跳转
2.1 App安装界面
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");//这是设置文件和类型,file是一个File对象
startActivityForResult(intent, 0);
2.2 App卸载界面
Intent intent = new Intent(this,Intent.ACTION_VIEW);
intent.addCategory(Intent.GATEGORY_DEFAULT);
intent.setData(Uri.parse("package:"+getPackageName());
startActivity(intent);
2.3 App分享的界面
//因为每一个相关应用都有一个Activity界面,action值是ACTION_SEND,你可以通过这个分享功能打开这个Activity
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "开启此Intent将会弹出所有可以接受文本信息应用供你选择,呼叫本地系统可以分享文字的app");
startActivity(intent);
2.4 跳转到桌面
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
BroadcastReceiver广播接收者
解释:类似于一个电台,电台可以发送不同的广播事件,类如外拨电话的广播事件,你只要进行了外拨电话这项操作,就会触发这个事件,然后电台会发送一条广播,我们作为这种事件的监听者,也是BroadcastReceiver接受者处理这个事件
特殊:
(1)当你注册了一个广播接受者到系统时,无论当前广播接受者所在线程是否正在运行(所在进程就算被杀死),你只要触发了接受者注册的事件,这个接受者所在线程会被立即启动
(2)在4.0系统中,在设置中对每个应用多了一个【force stop】的操作,强行停止应用,相当于冻结。这时广播接受者不会被触发
1.BroadcastReceiver抽象类
概述:定义广播接收者要继承该类,同时要配置这个广播接收者,通过静态或者动态的方法
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
abstract void |
onReceive(
Context context,
Intent intent)
当接收到存储广播信息的Intent之后调用,定义广播接收者不需要复写该方法
| ||||||||||
final String |
getResultData()
获取当前广播接受的数据。就是广播传递过来的字符串
| ||||||||||
final void |
setResultData(
String data)
设置广播中的的数据
| ||||||||||
final void |
abortBroadcast()
终止广播,不过只能用在有序广播中
|
1.1 去电广播接受者
public class OutGoingCallReceive extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//获取到自己要获取的号码
String data = getResultData();
//获取到我自己设置的ip号码
SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
String ipNum = sp.getString("number", "");
//生成一个新的号
String newNum = ipNum + data;
//改变拨打的号码
if(data.startsWith("0")){
setResultData(newNum);
}
}
}
1.2 接受短信广播接受者
public class SmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Object objects[] = (Object[]) intent.getExtras().get("pdus");
for (Object object : objects) {
SmsMessage sms = SmsMessage.createFromPdu((byte[])object);
String mesData = sms.getMessageBody();//获取短信内容
String mesSender = sms.getOriginatingAddress();//获取发信人
System.out.println("短信内容:"+mesData+"\t短信发送者:"+mesSender);
}
}
}
2.静态配置广播接收者
<!-- android:name是自定义的广播接收者的类 -->
<receiver android:name="com.heima.ipdail.OutGoingCallReceive">
<intent-filter >
<!-- action中的name是要监听的事件 -->
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
3.动态配置广播接收者
3.1 实现步骤
//注册广播通过代码
receiver = new ScreenReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.SCREEN_OFF");
filter.addAction("android.intent.action.SCREEN_ON");
registerReceiver(receiver, filter);
还需要解除注册
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
receiver = null;
}
3.2 动态注册广播的优点
(1)需要时注册,不需要时解除注册
(2)静态注册会因为每次触发事件而重新解析注册注册一遍,相对消耗资源,动态注册只会注册一遍,不用重复注册多遍
3.3 服务中注册广播的好处
(1)Activity中注册广播的局限性:.在Activity中注册广播,如果你退出Activity时,不在onDestory()方法中解除注册的广播,会抛出异常。而你解除了广播,动态广播就不能一直服务下去
(2)在服务中注册广播,由于服务进程不容易被杀死,服务可以长期在后台运行,这样注册的广播也得以长期为你服务4.系统广播常用事件
(1)外拨电话的事件:android.intent.action.NEW_OUTGOING_CALL
(2)发送短信的事件:android.provider.Telephone.SMS_REERVED
(3)SD卡的状态事件:
<1>.挂载:android.intent.action.MEDIA_MOUNTED
<2>.卸载:android.intent.action.MEDIA_UNMOUNTED
<3>移除:android.intent.action.MEDIA_REMOVED
注意:SD卡的事件静态注册时必须在intent-filter节点下添加 <data android:schema="file" />否则广播事件不生效
(4)手机重启事件:android.intent.action.BOOT_COMPLETED
(5)应用事件:
<1>.应用被卸载事件:android.intent.action.PACKAGE_REMOVED
<2>.应用被安装事件:android.intent.action.PACKAGE_ADDED
(6)屏幕事件
<1>.屏幕锁屏事件:android.intent.action.SCREEN_OFF
<2>.屏幕解屏事件:android.intent.action.SCREEN_ON
(7)电量事件
<1>.电量过低时:Intent.ACTION_BATTERY_LOW
<2>.电池电量充满时:Intent.ACTION_BATTERY_OKAY
(8)手机插入时:Intent.ACTION_HEADSET_PLUG
(9)网络是否断开时:Intent.ACTION_HEADSET_PLUG
注意:对于屏幕事件不能使用静态注册广播的形式注册,谷歌禁止。因为该事件是一个非常频繁的操作,每当事件一激活就会注册一遍广播,非常的消耗资源。需要使用动态的方式注册广播
5.自定义广播
5.1 无序广播
实现方法:通过Activity子类对象的sendBroadReceiver(intent)发送一个无序广播,intent对象中封装了事件名,intent对象设置事件名通过setActiion方法
5.2 有序广播
实现方法:通过Activity子类对象的sendOrderedBroadcast()方法发送一个有序广播,intent对象中封装了事件名,intent对象设置事件名通过setAction方法。然后接受者通过在Intent-filter节点下配置属性<Intent-filter android:priority="权限值">,然后接受者们就会按照权限值大小的顺序接受广播。
Intent intent = new Intent();
intent.setAction("com.heima.sendReceiver");
intent.putExtra("content", "第一次作为自定义广播发送者");
//发送一个无序广播
sendBroadcast(intent);
Intent intent = new Intent();
intent.setAction("com.heima.sendrice");
/**
* intent 意图,封装数据和标识
* receiverPermission 接受的权限,不需要权限则为null
* resultReceiver 是最终的接受者
* scheduler handler 处理器
* initialCode 初始化码
* initialData 初始化的数据
*
*///发送一条有序广播
sendOrderedBroadcast(intent, null, null, null, 10, "国务院给每一个人民发送了1000斤大米", null);
Service服务
了解:当一个应用将要开启,任何组件还没加载,Linux系统就会开启一个进程其中包含一个线程,之后默认应用的所有组件都运行在这个进程中的线程中,这个线程就是主线程或叫UI线程
重点:服务可以理解为没有界面的Activity
1.进程的生命周期( 进程的优先级)
注意:系统会按照优先级的大小,在内存不够时杀死优先级比较低的线程
1.1 Foreground process( 前台进程)
(1)用户正在与Activity交互,onResume()方法正在执行的进程
(2)广播接受者被触发,onReceive()方法正在执行
1.2 Visible process( 可视进程)
(1)Activity一直可以看见却不可以交互,Activity执行了onPause()方法
1.3 Service process( 服务进程)
(1)当一个服务被开启时
1.4 Background process( 后台进程)
1.5 Empty process( 空进程)
(1)当Activity执行了onDestory()方法,界面已经不存在,该线程不会被摧毁,还保持着这个线程存活,为了加快下一次启动该应用的时间,起到缓存的作用。系统经常杀死这个进程,
2.Service抽象类
疑问:线程和服务的区别?
解决:你从新开启一个线程用Thread实现,当Activity再可交互时这个线程优先级变成空进程级别,很容易被杀死。而你开启一个服务,让Activity不在进行交互时,这个进程会变成服务进程,能保证这个进程在在后台长期执行
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void |
onCreate()
服务只会被开启一次,多次通过strartService()开启服务时将会直接执行onStartCommand()方法,服务会在后台一直运行,除非用户手工停止
| ||||||||||
int |
onStartCommand(
Intent intent, int flags, int startId)
当服务运行时被执行,可以运行多次
| ||||||||||
void |
onDestroy()
服务被停止时调用,用户手工停止
| ||||||||||
abstract IBinder |
onBind(
Intent intent)
通过bindService()开启服务是会调用这个方法。
需要把自己自定义的方法提供出去时需要复写这个方法,返回自己的中间人对象
| ||||||||||
final void |
stopSelf()
停止自己这个服务,关闭自己
| ||||||||||
abstract Intent |
registerReceiver(
BroadcastReceiver receiver,
IntentFilter filter)
从context类继承的方法,动态注册广播的方法
| ||||||||||
abstract void |
unregisterReceiver(
BroadcastReceiver receiver)
从context类继承的方法,解绑注册的广播,当Activity中注册了广播时,必须在onDestroy方法中添加这个方法。否则报错
| ||||||||||
abstract ComponentName |
startService(
Intent service)
从context类继承的方法,开启一个服务
| ||||||||||
abstract boolean |
stopService(
Intent service)
从context类继承的方法,关闭一个服务
| ||||||||||
boolean |
bindService(
Intent service,
ServiceConnection conn, int flags)
从context类继承的方法。conn:是开启服务后对服务状态进行监视的连接器,可用这个连接器获取服务中的IBinder对象,从而调用服务对象中的方法;flags:绑定的类型,值在该类成员字段处
| ||||||||||
void |
unbindService(
ServiceConnection conn)
从context类继承的方法,接触绑定服务。
当调用了多次bindService方法时,会绑定多个不同连接器对象,这是再次调用解绑方法会报异常,因为他不知道解绑哪个连接了。conn:传入已绑定服务的连接器对象
|
3.两种开启服务方式的对比
(1)startService()开启服务这种方法,会执行服务生命周期方法的onCreat()和onStartCommand()方法,并且是开启多少次调用多少次onStartCommand()方法。这种开启方式服务会成为服务线程,不手工停止会一直在后台执行
(2)bindService()开启服务这种方法,会执行服务生命周期方法的onCreat()和onBind()方法,你绑定多少次只会调用一次onBind()方法。这种开启方式,服务的生命周期很短,与开启服务的Activity同生共死。就是说只要开启服务的Activity一onDestroy系统就会解除服务的绑定,如果你不手动的解除绑定,会报出一些错误,不过不影响开发
(3)混合模式:当你既需要开启长期运行的服务,又需要调用服务中自定义的方法时,使用这种模式。谷歌推荐的开启流程——1.startService()==>2.bindService()==>3.unbindService()==>4.stopService()(用处:开发音乐播放器、股票、天气预报、闹钟等,需要在后台长时间开启,并且需要提供一些自己私有的功能:如暂停、播放等)
4.进程间通信(ipc)
4.1 名词解释
解释:ipc——inter process communication:进程间通信
申明:当每个应用部署到手机上,这样每个应用都会对应有自己的一个进程,这样进程间通信就是应用间通信
远程服务:运行在其他应用里面的服务
aild:Android interface defintion language——Android接口定义语言
4.2 实现步骤
目的:保证远程服务里面的IBinder对象与本地应用里面的IBinder对象是同一个对象
(1)首先对远程服务里面为了把私有IBinder对象提供出去而建立的接口进行操作,把这个文件的【.java】扩展名改为【.aidl】,并且把该接口中的类和方法的public修饰符去掉(因为接口默认就是共有的,所以aidl不识别public字样)这样就会在远程服务应用中的gen目录中自动生成一个【接口名.java】的文件
(2)然后让远程服务里面的自定义的中间人类继承Stub(Stub类在gen目录自动生成的java文件中定义)
(3)之后在本地应用中新建一个包,包名与远程服务中aidl文件所在包的名称相同,然后把远程服务中的aidl文件拷贝到本地应用新建的这个包中即可(这样Android就会认为这两个应用使用的是同一个aidl文件,并且在本地应用的gen目录中同样会生成一个【接口名.java】的文件,其中包含Stub类)
(4)重要,这时在本地服务你想要开启远程的服务,就要建立Intent对象,Intent对象的参数怎么写?
(5)解答:在远程服务的清单文件中配置服务的<intent-filter>节点,其中配<action android:name="任意值">,然后在本地应用中建立Intent对象,之后setAction(),参数值为远程服务中配置的action值
(6)最后建立ServiceConnection实现类 ,复写方法时这样写==>接口名.Stub.asInterface(service);
ContentProvider内容提供者
意义:主要是让其他应用读取自己的私有数据库
知识点:私有的数据库其他应用是无法读取的,你可以用Linux的指令修改数据库文件的权限【adb shell】==>进入到数据库所在目录==>【chmod 777 数据库名.db】
1. ContentProvider抽象类
意义:自定义的内容提供者要继承这个类,复写她的抽象方法
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
abstract boolean |
onCreate()
内容提供者创建时调用
| ||||||||||
abstract String |
getType(
Uri uri)
Implement this to handle requests for the MIME type of the data at the given URI.
| ||||||||||
abstract Cursor |
query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder)
提供出去的查询方法,返回游标
| ||||||||||
abstract Uri |
insert(
Uri uri,
ContentValues values)
提供出去的添加方法,返回的Uri.parse("主机名"+插入行的序号);
| ||||||||||
abstract int |
update(
Uri uri,
ContentValues values,
String selection,
String[] selectionArgs)
提供出去的更新方法,返回更新了多少行
| ||||||||||
abstract int |
delete(
Uri uri,
String selection,
String[] selectionArgs)
提供出去的删除方法,删除了多少行
|
2.UriMather类
Constants | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
int | NO_MATCH | 状态码:没有连接 |
Public Constructors | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
UriMatcher(int code)
定义匹配规则对象
|
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void |
addURI(
String authority,
String path, int code)
添加匹配规则,authority:主机名,通过主机名可以查找到暴露出去的方法,path:路径,code:匹配码。前两个参数是给解析者中的,最后一个参数是给提供者用的
| ||||||||||
int |
match(
Uri uri)
解析匹配路径通过定义匹配规则,符合谁的匹配规则就返回谁的匹配码,谁都不符合返回成员字段NO_MATCH
|
public class FirstContentProvider extends ContentProvider {
//当匹配任何主机时使用默认匹配值
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int QUERYSUCESS = 1;
private static final int UPDATESUCESS = 2;
private static final int DELETESUCESS = 3;
private static final int ADDESUCESS = 4;
private MyDatabase database;
static{
matcher.addURI("com.heima.firstprovider","query",QUERYSUCESS);
matcher.addURI("com.heima.firstprovider", "update", UPDATESUCESS);
matcher.addURI("com.heima.firstprovider", "delete", DELETESUCESS);
matcher.addURI("com.heima.firstprovider", "add", ADDESUCESS);
}
@Override
public boolean onCreate() {
database = new MyDatabase(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int match = matcher.match(uri);
if(match == QUERYSUCESS){
SQLiteDatabase sdb = database.getReadableDatabase();
return sdb.query("info", projection, selection, selectionArgs, null, null, null);
}else {
throw new IllegalArgumentException("路径不匹配,请检查");
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int match = matcher.match(uri);
if(match == ADDESUCESS){
SQLiteDatabase sdb = database.getReadableDatabase();
//标示插入多少行
long insert = sdb.insert("info", null, values);
return Uri.parse("com.heima.firstprovider"+"_"+insert);
}else {
throw new IllegalArgumentException("路径不匹配,请检查");
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int match = matcher.match(uri);
if(match == DELETESUCESS){
SQLiteDatabase sdb = database.getReadableDatabase();
return sdb.delete("info", selection, selectionArgs);
}else {
throw new IllegalArgumentException("路径不匹配,请检查");
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int match = matcher.match(uri);
if(match == UPDATESUCESS){
SQLiteDatabase sdb = database.getReadableDatabase();
return sdb.update("info", values, selection, selectionArgs);
}else {
throw new IllegalArgumentException("路径不匹配,请检查");
}
}
@Override
public String getType(Uri uri) {
return null;
}
}
3.配置清单文件
<provider
android:name="com.heima.creatprivatedb.FirstContentProvider"
android:authorities="com.heima.firstprovider" ><!-- 内容提供者的主机名 -->
</provider>
4.ContentResolver抽象类
意义:内容解析者,通过上下文对象的方法获取该对象
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
final Cursor |
query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder)
就是内容提供者暴露的查询方法 | ||||||||||
final Uri |
insert(
Uri url,
ContentValues values)
就是内容提供者暴露的添加方法
| ||||||||||
final int |
delete(
Uri url,
String where,
String[] selectionArgs)
就是内容提供者暴露的删除方法
| ||||||||||
final int |
update(
Uri uri,
ContentValues values,
String where,
String[] selectionArgs)
就是内容提供者暴露的更新方法
| ||||||||||
final void |
registerContentObserver(
Uri uri, boolean notifyForDescendents,
ContentObserver observer)
注册一个内容观察者,uri:就是内容提供者定义的uri;notifyForDescendents:true——发送给该主机名下所有path,false——只发送给uri指定的path;observer:注册的观察者对象
| ||||||||||
final void |
unregisterContentObserver(
ContentObserver observer)
解除注册的内容内容观察者
| ||||||||||
void |
notifyChange(
Uri uri,
ContentObserver observer)
提醒观察此uri的观察者自己的数据改变了,写内容提供者的逻辑时需要使用这个方法.observer:可以传入null,表示只要观察了这个uri的观察者都会接受到这个提醒
|
5.ContentObserver抽象类
注:当其他应用中的私有数据库一有改变我们就能察觉,可以使用谷歌定义的API————内容观察者
Public Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void |
onChange(boolean selfChange)
观察的数据改变时调用
|
6.常用数据库查询技巧
(1)短信数据库的提供者:TelephonyProvider-->SmsProvider-->主机名:sms;对sms短信数据表进行增删改查操作path:null,表示全部操作都可以
(2)联系人数据库提供者:ContactsProvider-->ContactProvider2-->主机名:com.android.contacts;path:raw_contacts——查询raw_contacts表的path值,path:data——查询data表的path值。(Contact的提供者path值表示的是操作的表名,可以对这个表进行增删改查)
<1>.data表==>通过raw_contact_id字段值来判断是哪个联系人的数据;通过memetype_id字段值来判断数据的类型是那种,这两个都是外键;data1字段存储数据
<2>.raw_contacts表==>这里记录所有联系人的contact_id,这个字段关联的是data中的raw_contact_id
<3>.mimetypes表==>这里记录了所有数据类型的mimetype,这个字段关联的是data中的mimetype_id
注意:a.对data表和mimetypes进行多表查询是不能的,因为这个数据库已经定义好了一个data_view视图,把这两张表合并成了一个大表供我们查找,视图中含有data1数据字段和mimytype类型值字段
b.当你在手机中删除了某一个联系人后,实际上只是对联系人数据库中的raw_contacts表中的相应记录的contact_id列的值制为了空,没有把data表中数据删除。所以这时当你查询raw_contacts表时一定要判断contact_id是否为空,再获取,否则会报数据库语句解析异常
6.1 联系人查询的实例代码
public static List<Contact> readContact(Context context) {
List<Contact> contactsList = new ArrayList<Contact>();
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
//查询raw_contacts表获取contact_id
Cursor idCursor = context.getContentResolver().query(uri,
new String[] { "contact_id" }, null, null, null);
while (idCursor.moveToNext()) {
Contact contact = new Contact();
String id = idCursor.getString(0);
contact.setId(id);
System.out.println(id);
uri = Uri.parse("content://com.android.contacts/data");
//通过contact_id获取联系人数据,这里查阅的是data_view视图
Cursor dataCursor = context.getContentResolver().query(uri,
new String[] { "mimetype", "data1" }, "raw_contact_id=?",
new String[] { id }, null);
while (dataCursor.moveToNext()) {
String type = dataCursor.getString(0);
if ("vnd.android.cursor.item/email_v2".equals(type)) {
String email = dataCursor.getString(1);
contact.setEmail(email);
} else if ("vnd.android.cursor.item/phone_v2".equals(type)) {
String phone = dataCursor.getString(1);
contact.setPhone(phone);
} else if ("vnd.android.cursor.item/name".equals(type)) {
String name = dataCursor.getString(1);
contact.setName(name);
}
}
contactsList.add(contact);
}
return contactsList;
}