在Android中程序中的界面是通过布局文件设定的,在每个应用程序创建时会默认包含一个主界面布局,该布局位于res/layout目录中
实际开发中每个应用程序包含多个界面,而程序默认提供一个主界面布局无法满足需求,所以need添加布局
五大常见布局
以水平(默认)或者垂直方向排列
当控件水平排列时,显示顺序依次为从左到右,当控件垂直排列时,显示顺序为从上到下。
当控件水平排列时,控件属性layout_width只能设置成wrap_content(包裹内容让当前控件根据控件内容大小自动伸缩)
当控件水平排列时,如果控件未占满一行,会留白,此时可以利用layout_weight(权重),通过比例调节布局中所以控件的大小
通过相对定位排列,即以其他控件或者父容器作为参照物,拜访控件位置
在设计相对布局时,要遵守控件之间的依赖关系,后放入的控件的位置依赖于先放入的控件。
为了让程序适配:
名称 | 解释 | 补充 |
---|---|---|
px | 像素 | 在屏幕中可以显示的最小元素单位 |
pt | 磅数 | 一般作为字体的单位显示 |
dp | 基于屏幕密度的抽象单位 | 不同设备由不同的显示效果,根据设备的分辨率的不同来确定控件的尺寸 |
sp | 可伸缩像素 | 推荐设置文字大小时使用 |
开辟空白区域,帧里的控件(层)叠加
为每个加入其中的控件创建一个空白区域(称为一帧,每个控件占据一帧)
所以控件都默认显示在屏幕左上角,按照先后放入的顺序重叠摆放,帧布局的大小由内部最大控件决定
eg:刮刮卡
表格形式排列,通过行和列将界面划分为多个单元格,每个单元格都可以添加控件
表格布局需要配合TableRow使用,每一行都是由TableRow对象组成,所以TableRow的数量决定了表格的行数。表格的列数是由包含最多控件的TableRow决定
表格布局属性
布局属性 | 功能描述 |
---|---|
android:stretchColumns | 该列被拉伸 |
android:shrinkColumns | 该列被收缩 |
android:collapseColumns | 该列被隐藏 |
表格布局控件属性
控件属性 | 功能描述 |
---|---|
android:layout_column | 设置该单元显示位置 |
android:layout_span | 设置该单元格占据几行,默认为1行 |
通过xy坐标排列
控件是界面组成的主要元素,是与用户进行直接交互的
常用控件
EditText继承自TextView,可以进行编辑操作,将用户信息传递给Android程序,还可以为EditText控件设置监听器,用来测试用户输入的内容是否合法。
用于响应用户的一系列点击事件,使得程序更加流畅完整
指定Button的onClick属性方式
首先在layout文件中指定onClick属性
android:onClick="click"
然后再Activity中实现这个click方法
public void click(View v){
Log.i("指定onClick属性方式","button is clicked");
}
独立类方式
首先为按钮设置监听器
btn.setOnClickListener(myListener);
在onCreate() 方法外实现接口
onClickListener myListener=new OnClickListener(){
public void onClick(View v){
Log.i("独立类方式","button is clicked")
}
}
接口方式
匿名内部类方式
在Activity中添加匿名内部类
btn.seOnClickListener(new View.OnClickListener(){
public void onClick(View v)
{Log.i("匿名内部类方式","button is clicked");}
});
为单选按钮,它需要与RadioGroup配合使用,提供两个或多个互斥的选项集
RadioGroup是单选组合框,可容纳多个RadioButton,并把它们组合在一起,实现单选状态
radioGroup.setOnClickedChangeListener(new RadioGroup.OnClickedChangeListener(){
public void onCheckedChange(RadioGroup group,int checkedId)
{
if (checkedId==R.id.rbtn){
textView.setText("您的性别是:男");
}
else
{textView.setText("您的性别是:女");}
}
})
是视图控件,继承自View,其功能是在屏幕中显示图像
对话框是程序与用户交互的一种方式,通常用于显示当前程序提示信息以及相关说明
常见对话框:
一般只会显示提示信息,并具有确定和取消按钮
方法名称 | 功能描述 |
---|---|
setTitle() | 设置对话框标体 |
setIcon() | 设置对话框图标 |
setPositiveButton() | 给对话框添加确定按钮 |
setNegativeButton() | 给对话框添加取消按钮 |
setMessage() | 设置对话框提示信息 |
AlertDialog dialog;
dialog=new AlertDialog.Bulider(this)
.setTitle("Dialog对话框")
.setMessage("是否确定退出")
.setIcon(R.mipmap.ic_launcher)
.setPositiveButton("确定",null)
.setNativeButton("取消",null)
.create();
dialog.show();
单选对话框和RadioButton相似,只能选择一个选项,它是通过AlertDialog对象调用setSingleChoiceItems()方法创建
new AlertDialog.Bulider(this)
.setTitle("请选择性别")
.setIcon(R....)
.setSingleChoiceItems(new String[]{"男""女"},0,new DialogInferface.OnClickListener(){
public void onClick(DialogInferface dialog,int which){
}
})
.setPositiveButton("确定",null)
.show;
多选对话框通常需要勾选多种选项时使用,创建多选对话框调用 setMultiChioseItems()
new AlterDialog.Bulder(this)
.setTitle("请添加兴趣爱好")
.setIcon(R....)
.setMultiChoiceItems(new String[]{"A","B","C"},null,null)
.setPositiveButton("确定",null)
.show
进度条对话框一般在应用程序实现耗时操作时使用
ProgressDialog prodialog;
prodialog=new ProgressDialog(this);
prodialog.setTitle("进度条对话框");
prodialog.setIcon("....jpg");
prodialog.setMessage("正在下载请稍后。。。");
prodialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
prodialog.show;
是轻量级消息提醒机制,显示在应用程序界面的最上层,一段时间后会自动消失不会打断当前操作,也不会获得焦点
Toast.makeTaxt(this,"Hello,Toast",Toast.LENGTH_SHORT).show();
创建自定义对话框的流程:
style是一种包含一种或者多种控件的属性集合,可以指定控件高度,宽度,字体大小及颜色等
采用自定义样式:
theme是应用到整个Acitivity和Application的样式,当设置好主题后,Activity或整个程序中的视图都将使用主题中的属性,当主题和样式中的属性发生冲突时,样式的优先级要高于主题
自定义主题的代码:
引用自定义主题代码:
...
Activity是Andriod程序的四大组件之一,为用户提供可视化界面及操作,一个应用程序通常包含多个Activity,每个Activity负责管理一个用户界面,这些界面可以添加多个控件,每个控件负责实现不同功能
两种创建方式
包名处点击右键选择New-Activity-Empty Activity选择,填写Activity信息,完成创建
包名中点击右键选择New-Java Class 选项,填写Java类名,完成创建,在该类中继承AppCompatActivity,并在清单文件中进行注册,完成Activity的创建
生命周期方法:
生命周期方法 | 用途 |
---|---|
onCreate() | 在Activity即将可见时调用 |
onStart() | 在Activity即将可见时调用 |
onResume() | Activity获取焦点时调用 |
onPause() | 当前Activity被其他Activity覆盖或锁屏时调用 |
onStop() | Activity对用户不可见时调用 |
onRestart() | Activity从停止状态再次启动时调用 |
onDestory() | Activity销毁时调用 |
手机横竖屏切换时,系统会根据AndroidMainfest.xml文件中Activity的configChanges属性值不同而调用相应的生命周期方法
没有设置configChanges属性的值时:
当由竖屏切换为横屏时,依次调用的方法依次是:
onPause(),onStop(),onDestory(),onCreate(),onStart(),onResume()
设置configChanges属性
standard模式是Activity的默认启动方式,每启动一个Activity就会在栈顶创建一个新的实例
singleTop模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶则直接复用,否则创建新的实例
singleTask模式下每次启动该Activity时,都会检查栈中是否存在当前Activity实例,如果存在则直接使用,并把当前Activity之上的所有实例全部出栈
singleInstance模式会启动一个新的任务栈来管理Activity实例,无论哪个任务栈中启动该Activity,该实例在整个系统中只有一个
通过AndrodMenifest.xml文件为Activity指定启动模式
通过Intent中设置标志位(addFlags方法)来为Activity指定启动模式
Intent intent=new Intent();
intent.setClass(ActivityB.this,ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
启动模式的设置
Intent.FLAG_ACTIVITY_NEW_TASK
该标志位标识使用一个新的任务栈来启动一个Activity
Intent.FLAG_ACTIVITY_SINGLE_TOP
该标志位表示使用singleTop模式来启动一个Activity
Intent.FLAG_ACTIVITY_CLEAR_TOP
该标志位表示使用singleTask模式来启动一个Activity
意图,是程序中各组件进行交互的一种重要方式,它不仅可以指定当前组件要执行的动作,还可以在不同组件之间进行数据传递
一般用于启动Activity、Service以及发送广播等,根据开启目标组件的方式不同,Intent可以被分为两种类型:显示意图和隐式意图
可以直接通过指定名称开启指定的目标组件
显示调用需要明确的指出被启动的对象的组件信息、包括包名和类名
Intent intent=new Intent(this,Activity02.class); //创建一个Intent对象,其中第一个参数位Context表示当前的Activity对象,第2个参数表示要启动的目标Activity
startActivity(intent); //调用Activity的startActivity方法启动目标组件
通过指定action和category等属性,系统在分析这些属性信息寻找目标Activity
需要Intent能匹配目标组件的IntentFilter中所设置的过滤信息.如果不匹配将无法启动目标Activity
Intent intent=new Intent();
intent.setAction("cn.itcast.START_ACTIVITY"); //设置action动作,当与清单文件中的action相匹配时,启动目标组件
startActivity(intent);
//设置action动作,当代码中的action与该action相匹配时启动该组件
当发送一个隐式intent后,Android系统会将他和程序中的每一个组件的过滤器进行匹配,匹配的属性有action\data\category,需要这三个属性都匹配成功才能唤醒相应的组件
action:用于指定Intent对象的动作
那么只要Intent中的action能够和Activity过滤规则中的任何一个action相同即可匹配成功。
在清单文件中为activity添加intent-filter标签时,必须添加action属性,否则隐式Intent无法开启该Activity
data:指定数据的URI或者数据MIME类型
隐式Intent携带的data数据只要与IntentFilter中任意一个data声明相同,data属性就匹配成功
catagory:用于Activity添加额外信息
隐式Intent中声明的category必须全部能够与某一个IntentFilter中的category匹配才能算匹配成功
IntentFilter中的category属性数量必须大于等于隐式Intent携带的category数量属性时,才能匹配成功
如果一个隐式Intent没有携带category属性,那么他就可以通过任意一个Intent-filter的category匹配成功
.putExtra
.getStringExtra
Intent intent=new intent(this,Activity02.class);
intent.putExtra("extra_data","Hello Activity02");//在Activity01中放入数据传送给Activity02
startAcivity(intent);
Intent intent=getintent();
String data=intent.getStringExtra("extra_data");//在Activity02中获取Activity01传递来的数据
在MainActivity中将数据传递给ScendActivity
Intent intent=new Intent();
intent.setClass(this,SecondActivity.class);
Bundle bundle=new Bundle(); //创建Bundle对象
bundle.putString("useName","sunhf"); //将用户名封装到Bundle对象中
intent.putExtra(bundle); //将Bundle对象封装到Intent对象中
startActivity(intent);
在SecondActivity中获取数据
Bundle bundle=getIntent().getExtra(); //获取Bundle对象
String accound=bundle.getString("userName"); //获取用户名
Android中的5种数据存储方法
分为内部存储和外部存储
内部存储 | 外部存储 | |
---|---|---|
存储位置 | 将数据以文件的形式存储到应用种 | 将数据以文件的形式存储到外部设备上 |
存储路径 | data/data//目录下 | mnt/sdcard/目录下,使用getExternalStoageDirectory()获得根目录 |
其他应用操纵该文件时 | 需要设置权限 | 不用设置权限,会被其他应用共享 |
删除文件 | 当应用被卸载时,该文件也会被删除 | 该文件可在本应用外删除,使用前需要确认外部设备是否可用 |
操作数据 | 通过openFileOutput()方法和openFileInput()方法获取FileOutputStream和FileInputStream操作对象 | 直接使用FileOutputStream和FileInputStream操作对象 |
FileOutputStream fos=openFileOutput(String name,int mode);
FileInputStrean fis=openFileInput(String name);
mode取值
写入
String fileName="data.txt"; //文件名称
String content="hello";//保存数据
FileOutputStream fos=openFileOutput(fileName,MODE_PRIVATE);
fos.write(content.getBytes());
fos.close;
读取
String content="";
FileInputSream fis=null;
fis=openFileInput("data.txt"); //获得文件输入流对象
byte[] buffer=new byte[fis.available()];
fis.read(buffer);
content=new String(buffer);
fis.close;
由于外部设备可能被移除,丢失或处于其他状态,因此在使用外部设备之前必须使用
**Environment.getExternalStorageState()**方法来确认外部设备是否可用。当外部设备可用并且具有读写权限时,那么就可以通过FileInputStream、FileOutputStream对象来读写外部设备中的文件。
三种解析方法
解析方法 | 补充 | 缺点 |
---|---|---|
DOM解析 | 将XML文件中所有内容以DOM树形式存放到内存中,支持删除,修改等功能 | 消耗内存大 |
SAX解析 | 逐行扫描XML文件,读取文件的同时即可进行解析处理,不必等到文件加载结束 | 无法进行增、删、改等操作 |
PULL解析 | 一个开源的Java项目,既可以用于Android应用,也可用于JavaEE程序,Android中以及集成了PULL解析器 |
XmlPullParser:PULL解析器类
XmlPullParser parser=Xml.newPullParser();//获取PULL解析器
xmlPullParser.setInput(is,"UTF-8");//设置解析器参数
int type=xmlPullParser.getEventType();//获得事件类型
两种解析方式
例如要解析是json数据如下
{"name":"xiaowang","age":18,"married":forse} //json1
JSONObject jsonObj=new JSONObject(json1);
String name=jsonObj.optString("name");
int age=jsonObj.optInt("age");
booleam married.optBoolean("married");
使用org.json解析JSON数组
例如要解析的JSON数据如下
[111,11,1] //json2
JSONArray jsonArray=new JSONArray(json2);
for(int i=0;i
使用Gson库之前,先要将gson.jar添加到项目中,并创建JSON数据对应的实体类Person
使用Gson解析json对象
Gson gson=new Gson();
Person person=gson.fromJson(json1,Person.class);//Person.class与json对应的实体类
使用Gson解析json数组
Gson gson=new Gson();
Type listType=new TypeToken>(){}.getType;//指定数据类型
Listages=gson.fromJson(json2,listType);
getShardPreferences(name,mode)
//第一个参数用于指定该文件的名称,名称不用带后缀
//第二个参数指定文件的操作模式
文件的MODE:
ShardPreferences sp=getShardPreferences("data",MODE_PRIVATE);//得到ShardPreferences对象
ShardPreferences.Editor editor=sp.edit(); //获得ShardPreferences编译器
editor.putString("name","王"); //使用编译器存储数据
editor.putInt("age",18);
editor.commit(); //提交保存数据
读取数据
ShardPreferences sp=getShardPreferences("data",MODE_PRIVATE);
String data=sp.getString("name","");
删除数据
editor.remove("name"); //根据键 删除数据
editor.clear();//删除所有数据
editor.commit;
SQLite特点:
SQLiteOpenHelper类:是SQLiteDatabase的一个帮助类,用于数据库创建和版本的更新,一般是建立一个类去继承他,并实现他的onCrete()和onUpgrade()
方法名 | 方法描述 |
---|---|
SQLiteOpenHelper() | 构造方法 |
onCreate() | 创建数据库时调用 |
onUpgrade() | 版本更新时调用 |
getReadableDatabase() | 创建或打开一个只读数据库 |
getWriteableDatabase() | 创建或打开一个读写数据库 |
(long)insert(String table,String nullCoiumnHack,ContentValues values)
返回:添加的记录条数,不成功则返回-1
参数:数据表名;插入某个字段值为空时,设置字段值为null;键值对形式存储的对象
public void insert(String name,String sNumber){
//获取可读写对象
SQLiteDatabase db=helper.getWriteableDatabase();
//创建ContentValues对象并将数据添加到对象中
ContentValues values=new ContentValues();
values.put("name",name);
values.put("age",sNumber);
//调用insert()将数据添加到数据库中
long id=db.insert("information",null,values);
db.close();
}
(int)update(String table,ContentValues values,Sting whereClause,String[] whereArgs)
返回:更新的记录有几条
参数:表名;更新的值:ContentValues对象;定位条件——占位符;条件的值
publis int update(String name,String sNumber){
SQLiteDatabase db=helper.getWriteableDatabase();
ContentValues values=new ContentValues();
values.put("sNumber",sNumber);
int number=db.update("information",values,"name=?",now String[]{name});
db.close;
return number;
}
(int)delete(String table,String whereClause,String[]whereArgs)
返回:被删除的记录条数
参数:表名;条件;条件的值
public int delete(String sNumber){
SQLiteDatabase db=helper.getWritezbleDatabase();
int number=db.delete("information","sNumber=?",new String[]{sNumber});
db.close();
return number;
}
(Cursor)query(String table,String() columns,String selection,String[]selectionArgs,String groupBy,String having,String orderBy)
返回:结果集
cursor(游标)
方法名称 | 方法描述 |
---|---|
getCount() | 总记录条数 |
isFirst() | 判断是否是第一条记录 |
isLast() | 判断时候是最后一条记录 |
moveToFirst() | 移动到第一条记录 |
moveToLast() | 移动到最后一条记录 |
move(int offset) | 移动到指定的记录 |
moveToNext() | 移动到上一条记录 |
moveToPrevious() | 移动到吓一条记录 |
getColmnsIndex() | 获得指定列的索引值 |
//开启数据库事务
db.beginTransation();
//设置数据库标志位成功,当事务结束时,提交事务
}
catch(Exception e){
Log.i("事务处理失败",e.toString());
}
//关闭数据库事务
db.close();
属性名称 | 功能描述 |
---|---|
android:listSelector | 当条目被点击后,改变条目的背景色 |
android:divider | 设置分割线的颜色 |
android:dividerHeight | 设置分割线的高度 |
android:scrollbars | 是否显示滚动条 |
android:fadingEdge | 去掉上下的黑色阴影 |
BaseAdapter(抽象类)即基本的适配器,自定义适配器继承BaseAdapter,需要实现四个方法
方法名称 | 功能表述 |
---|---|
public int getCount() | 得到Item条目的总数 |
public Object getItem(int position) | 根据postion得到某个Item的对象 |
public long getItemId(int position) | 根据position得到某个Item的id |
public View getView(int position,View convertView,ViewGroup) | 得到相应position对应的Item视图,position是当前Item的位置,convertView用于复用旧视图 |
SimpleAdapter继承自基本适配器,实现了BaseAdapter的四个抽象方法并进行了封装
public SimpleAdapter(Context context,List>data,int resource,String[]from,int[] to)
ArrayAdapter也是基本适配器的子类,通常用于适配TextView控件
两种优化方法
Android 开发的四大组件分别是
广播接收者是通过Binder机制在AMS中进行注册的
广播接收者是通过Binder机制向AMS发送广播
AMS查找符合相关条件的广播接收者,将广播发送到相应的消息循环队列中
执行消息循环时获取到此广播,会回调广播接收者中的onReceive()方法并在该方法中进行相关处理
Android系统中内置了很多广播,例如手机开机完成
为了监听来自系统或应用程序的广播事件,Android系统提供了BroadcastReceiver组件
当Android系统中产生一个广播事件时,可以有多个对应的广播接收者接收并进行处理
创建
在onReceive()中实现操作
public class MyReceiver extends BoastcastReceiver{
public MyReceiver(){
}
public void onReceiver(Context context,Intent intent){
//实现广播接收者的相关操作
throw new UnsupportedOperationException("Not yet implemented");
}
}
静态注册广播:只要设备处于开启状态,广播接收者就能接收到广播
对于应用程序监听SMS Intent广播,首先需要添加RECEIVEE_SMS权限
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
MyReceiver receiver=new Myreceiver();
//实例化过滤器并设备要过滤的广播
String action="android.provider.Telephony.SMS_RECEIVED";
IntentFilter intentFile=new IntentFile(action);
//注册广播
registerReceiver(receiver,intentFile);
}
protected void onDestory(){
super.onDestory();
//当Activity销毁时,取消注册
unregisterReceiver(receiver);
}
当系统提供的广播不能满足实际需求时,可以自定义广播,同时需要编写对应的广播接收者
当自定义广播发送消息时,会存储到公共消息区,而公共消息区中如果存在对应的广播接收者,就会及时接收这条消息
自定义广播
Android8.0+后,限制隐式广播-
广播接收者静态注册的情况下,发送隐式广播需要设置标志位突破隐式广播限制
Intent intent=new intent();
intent.addFlags(0x01000000);
intent.setActin("help,help,help");
sendBroadcast(intent);
广播接收者静态注册的情况下,发送显示广播
Intent intent=new Intent();
intent.setComponent(new ComponentName("广播接收者的包名","广播接收者的完整类名"));
sendBroadcast(intent);
动态注册广播接收者
Intent intent=new Intent();
intent.setAction("help,help,help");
sendBroadcast(intent);
android提供了两种广播类型,有序广播和无需广播
有序广播:按照接收者的优先级结束,只有一个广播接收者能接收消息,在此广播接收者中逻辑执行完毕后,才会继续传递。
//数值越大,优先级越高
//动态注册MyReceiver广播
Myreceiver one=new MyReceiver();
IntentFlier filer=new IntentFiler();
filter.setPriority(100);
发送有序广播:sendOrderedBroadcast()
中断广播:abortBroadcast()
Android 开发的四大组件分别是
service是android四大组件之一,,能够在后台长时间执行操作且不提供用户界面的应用程序组件,service可以和其他组件进行交互,一般是由Activity启动,但是并不依赖与活动。
当活动的生命周期结束时,Service任然会继续运行,直到自己的生命周期结束为止
Service通常被称为“后台服务”,具体是指其本身的运行不依赖于用户可视的UI界面,此外,它的应用场景还有两个,后台运行和跨进程访问。
服务的创建和广播接收者类似,同样在程序包名上点击右键选择new service ,在弹出窗口输入服务的名称即可完成创建
服务创建完成后,android studio会自动在AndroidMainfesr.xml中对服务进行注册
自动创建java类继承Service类,来创建服务
若采用自行创建java类继承Service类的方式创建服务,则需要手动在清单文件中进行注册
清单文件
//表示该服务能够被其他应用程序组件调用
启动方式 | 终止方法 |
---|---|
startService() | 需要自身调用stopSelf()方法或者其他组件调用stopService()方法,服务才能停止 |
bindService() | 需要调用onUnbind()方法接触绑定之后才会销毁 |
使用不同的方法启动服务,其生命周期也是不同的
startService() 方法启动服务,服务会长期在后台运行,并且服务的状态与开启者的状态没有关系,即启动服务的组件已经被销毁,服务会依然运行
开启服务
Intent intent=new Intent(this,MyService.class);
startService(intent);
关闭服务
Intent intent=new Intent(this,MyService.class);
stopService(intent);
通过bindService()方法启动服务时,服务会和组件绑定,当服务中的onUnbind()方法调用时,这个服务就会被销毁
绑定服务
Intent intent=new Intent(this,MyService);
//Intent service用于指定要启动的SC,
//ServiceConnection conn用于监听调用者与SC之间的连接状态,
//int flags用于绑定时是否自动创建Service
bindService(Intent service,ServiceConnection conn,int flags)
ServiceConnection conn:监听器,监听调用者与Service之间的连接
class MyServiceConn implements ServiceConnection{
public void onServiceConnected(ComponentName compoentName,IBinder iBinder){}
public void onServiceDisconnected(ComponentName componentName){}
}
int flags用于绑定时是否自动创建Service,0表示不自动创建,BIND_AUTO_CREATE表示自动创建
解除绑定
unbindServce(conn);
为什么有了startService()还要BindService()?
为了调用服务里的方法
定义IBinder对象
在onBind()方法内返回IBinder对象
bindService()绑定服务,获取IBinder对象,调用服务里方法
startService()方法开启 | bindService()方法开启 |
---|---|
一旦开启,未调用stopService()关闭服务,会一直在后台运行 | 与调用组件同生共死,绑定服务后,返回Binder对象,可调用服务里的方法 |
混合开启服务的步骤:
服务方式 | |
---|---|
本地服务通信 | 本地服务通信是指应用程序内部的通信,需要使用IBinder对象进行本地服务通信 |
远程服务通信 | 远程服务通信是指两个应用程序之间的通信,远程服务通信是通过AIDL实现的 |