玩爆你的手机联系人--T9搜索
自己研究了好几天联系人的T9搜索算法, 先分享出来给大家看看. 欢迎指教.如果有大神有更好的T9搜索算法, 那更好啊,大家一起研究研究,谢谢.
第一部分是比较简单的获取手机联系人.
获取联系人前提要有权限.
<uses-permission android:name="android.permission.READ_CONTACTS" />
因为手机的联系人都存储在数据库里面,所以我们只要把数据库里的信息查询出来即可.
private static final String[] PHONES_PROJECTION = new String[] {
Phone.DISPLAY_NAME, Phone.NUMBER, Photo.SORT_KEY_ALTERNATIVE};
ContentResolver resolver = getBaseContext().getContentResolver();
Cursor phoneCursor = resolver.query(Phone.CONTENT_URI,
PHONES_PROJECTION, null, null, null);
这里我只是简单的说一下而已,后面的遍历phoneCursor 就能把联系人查询出来.(我项目里是开一条线程获取联系人的,以防太多联系人导致页面空白或者卡顿)
第二部分是T9搜索部分
思路是: a只要联系人的号码有包含我输入的数字就add到list进去,
b联系人的姓名拼音有包含我输入的拼音就add到list进去,
c联系人的姓名缩写拼音有包含我输入的拼音就add到list进去
举个例子--联系人姓名:测试,号码:1234567890
情况一:我输入1234567890就能把测试显示出来
情况二:我输入23744(ceshi),因为这个"测试"的拼音,所以也能把测试显示出来
情况三:我输入(27)cs,这是"测试"的拼音缩写,所以也能把测试显示出来
当然我输入23(ce)或者744(shi)什么的,都可以把测试显示出来.
个人是把联系人的号码(1234567890),姓名拼音转成数字(ceshi对应是23744),拼音缩写拼音转成数字(cs对应27),这些信息存储到list里面.便于遍历跟比较.
然后再结合代码讲解一下:
我的流程是这样的:
首先开始发送一条线程去获取联系人
/**
* 刚开始启动程序时,开条线程去获取联系人
*/
new Thread(getContract).start();
这线程里面获取到的联系人存储到mLIst里面;
其中在获取的时候处理了一下,把联系人姓名,号码,拼音,拼音缩写查出来后放到bean里面;
private static final String[] PHONES_PROJECTION = new String[] {
Phone.DISPLAY_NAME, Phone.NUMBER, Photo.SORT_KEY_ALTERNATIVE};
这个是查出联系人的姓名,号码,姓名拼音:测试,1234567890,CE 测 SHI 试
(对Contacts了解就会知道(不了解可以了解下),每个联系人都有一个sort_key字段,如果查询中没有设置sortOrder,默认就会以 sort_key字段为排序依据.名字的检索其实也是根据sort_key来做的(比如拨号盘的模糊匹配:数字转成字母,再到拼音,最后得到汉字).sort_key是根据名字生成的:如果联系人名字中包含字母,sort_key和name保持一致;如果名字是汉字,生成的sort_key,"拼音 汉字-拼音 汉字".其中拼音全大写,中间以空格分割,如:"测试"对应的sort_key:"CE
测 SHI 试")
外语:格式就是以上那样,因为Google有提供一个汉字转拼音的类,然后把"CE 测 SHI 试"存储到数据库里.所以用这个就能查出来.但毕竟是外国人弄的,所以在拼音上有时候会有一些差池.例如咱们常见的"呵呵",咱们习惯是"HE 呵 HE 呵",但它可能会保存成"A 呵 A 呵".
/**
* 将联系人的姓名拼音全部转化为数字
* @param 联系人姓名拼音
* @return 姓名拼音对应数字
*/
public String getNum(String search, boolean status){
String str = "";
for(int i = 0;i<search.length();i++){
String c = search.charAt(i)+"";
if(c.equals("1")){
str = str + "1";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("A")||c.equals("B")||c.equals("C")||c.equals("2")
||c.equals("a")||c.equals("b")||c.equals("c")){
str = str + "2";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("D")||c.equals("E")||c.equals("F")||c.equals("3")
||c.equals("d")||c.equals("e")||c.equals("f")){
str = str + "3";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("G")||c.equals("H")||c.equals("I")||c.equals("4")
||c.equals("g")||c.equals("h")||c.equals("i")){
str = str + "4";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("J")||c.equals("K")||c.equals("L")||c.equals("5")
||c.equals("j")||c.equals("k")||c.equals("l")){
str = str + "5";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("M")||c.equals("N")||c.equals("O")||c.equals("6")
||c.equals("m")||c.equals("n")||c.equals("o")){
str = str + "6";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("P")||c.equals("Q")||c.equals("R")||c.equals("S")||c.equals("7")
||c.equals("p")||c.equals("q")||c.equals("r")||c.equals("s")){
str = str + "7";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("T")||c.equals("U")||c.equals("V")||c.equals("8")
||c.equals("t")||c.equals("u")||c.equals("v")){
str = str + "8";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("W")||c.equals("X")||c.equals("Y")||c.equals("Z")||c.equals("9")
||c.equals("w")||c.equals("x")||c.equals("y")||c.equals("z")){
str = str + "9";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("0")){
str = str + "0";
if(status){
i = i + 1;
}
continue;
}
}
return str;
};
这个方法主要是将拼音转换成数字,例如测试的"ce 测 shi 试"就能转成23744.大家估计还看到我还传了个参数boolean status,这个是用于判读是否为拼音字母缩写的,如果为true的话,他会查出测试的"ce
测 shi 试"的字母缩写,cs转成27.
获取到联系人,再显示出来之后,就是搜索了.
整个的事件监听关键在于edittext的变化监听
inputEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
if (isWrite) {
isWrite = false;
return;
}
isWrite = true;
String inputStr = "";
String newStr = s.toString();
newStr = newStr.replace(" ", "");
int index = 0;
if (true) {
if ((index + 3) < newStr.length()) {
inputStr += (newStr.substring(index, index + 3) + " ");
index += 3;
}
}
while ((index + 4) < newStr.length()) {
inputStr += (newStr.substring(index, index + 4) + " ");
index += 4;
}
inputStr += (newStr.substring(index, newStr.length()));
inputEditText.setText(inputStr);
inputEditText.setSelection(inputStr.length());
if(count == 0){
isAfresh = true;
}else{
isAfresh = false;
}
long currentTime = System.currentTimeMillis();
if ((currentTime - touchTime) >= waitTime) {
touchTime = currentTime;
searchConstract(isAfresh);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
前面一段是为了格式化输入格式.后面调用的方法才是做相应的操作.直接根据用户输入的数字,判断联系人的姓名拼音,姓名缩写,号码有没有包含用户输入的数字,有的话就add到list里面.遍历完整个联系人之后,就刷新adapter.显示结果出来.
各位如果觉得容易看得懂,或者自己能用到的,能不能在页面底下投一下您尊贵的一票呢,谢谢!
以上是我的思路,下面附上一段代码
public class MainT9 extends Activity{
private int[] textViewId = new int[]{R.id.main_num_1,R.id.main_num_2,R.id.main_num_3,R.id.main_num_4,
R.id.main_num_5,R.id.main_num_6,R.id.main_num_7,R.id.main_num_8,R.id.main_num_9,R.id.main_num_left,
R.id.main_num_0,R.id.main_num_right,R.id.main_num_delete};
private TextView[] textView = new TextView[textViewId.length];
private EditText inputEditText;
private ListView mListView;
private List<ContractBean> mList;
private List<ContractBean> list;
private MyAdapter myAdapter;
private static final String[] PHONES_PROJECTION = new String[] {
Phone.DISPLAY_NAME, Phone.NUMBER, Photo.SORT_KEY_ALTERNATIVE};
long waitTime = 300;
long touchTime = 0;
/**
* 判断是否人为输入
*/
private boolean isWrite = false;
private boolean isAfresh = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main_activity);
mList = new ArrayList<ContractBean>();
list = new ArrayList<ContractBean>();
/**
* 刚开始启动程序时,开条线程去获取联系人
*/
new Thread(getContract).start();
for(int i = 0;i<textViewId.length;i++){
textView[i] = (TextView) this.findViewById(textViewId[i]);
textView[i].setOnClickListener(click);
}
inputEditText = (EditText) this.findViewById(R.id.main_num_edit);
// hideSystemKeyBoard(inputEditText);
mListView = (ListView) this.findViewById(R.id.main_contract_listview);
myAdapter = new MyAdapter(this, mList);
mListView.setAdapter(myAdapter);
inputEditText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
if (isWrite) {
isWrite = false;
return;
}
isWrite = true;
String inputStr = "";
String newStr = s.toString();
newStr = newStr.replace(" ", "");
int index = 0;
if (true) {
if ((index + 3) < newStr.length()) {
inputStr += (newStr.substring(index, index + 3) + " ");
index += 3;
}
}
while ((index + 4) < newStr.length()) {
inputStr += (newStr.substring(index, index + 4) + " ");
index += 4;
}
inputStr += (newStr.substring(index, newStr.length()));
inputEditText.setText(inputStr);
inputEditText.setSelection(inputStr.length());
if(count == 0){
isAfresh = true;
}else{
isAfresh = false;
}
long currentTime = System.currentTimeMillis();
if ((currentTime - touchTime) >= waitTime) {
touchTime = currentTime;
searchConstract(isAfresh);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}
private void searchConstract(boolean isAfresh){
if(isAfresh){
if(mList!=null){
myAdapter.updateListView(mList);
}else{
new Thread(getContract).start();
}
}else{
String str1= inputEditText.getText().toString();
str1 = str1.replace(" ", "");
SearchContract search = new SearchContract(str1);
search.start();
}
}
/**
* T9搜索线程
* @author Chillax_KUN
*/
class SearchContract extends Thread{
String body;
SearchContract(String body){
this.body = body;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
Iterator<ContractBean> iterator = mList.iterator();
list = new ArrayList<ContractBean>();
list.clear();
while(iterator.hasNext()){
ContractBean sortModel = iterator.next();
String search = sortModel.getSearch();
String phone = sortModel.getPhone();
phone = phone.replace(" ", "");
String zimu = sortModel.getZimu();
if(phone.contains(body) || search.contains(body)||zimu.contains(body)){
list.add(sortModel);
}
}
h.sendEmptyMessage(1);
}
}
/**
* 开启线程,获取联系人
*/
Runnable getContract = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
mList = getPhoneContacts();
h.sendEmptyMessage(0);
}
};
private List<ContractBean> getPhoneContacts() {
ContentResolver resolver = getBaseContext().getContentResolver();
Cursor phoneCursor = resolver.query(Phone.CONTENT_URI,
PHONES_PROJECTION, null, null, null);
List<ContractBean> mList = new ArrayList<ContractBean>();
if (phoneCursor != null) {
phoneCursor.moveToFirst();
while (!phoneCursor.isAfterLast()) {
ContractBean model = new ContractBean();
String name = phoneCursor.getString(0);
String phone = phoneCursor.getString(1);
String search = phoneCursor.getString(2);
model.setName(name);
model.setPhone(phone);
model.setSearch(getNum(search,false));
String zimu = "";
String str[] = search.split(" ");
if(str.length>0){
for(int i = 0;i<str.length;i++){
if(str[i].length()==1){
zimu = zimu + str[i];
}
}
}
model.setZimu(getNum(zimu,true));
mList.add(model);
phoneCursor.moveToNext();
}
if(phoneCursor!=null){
phoneCursor.close();
}
return mList;
}
return mList;
}
/**
* 将联系人的姓名拼音全部转化为数字
* @param 联系人姓名拼音
* @return 姓名拼音对应数字
*/
public String getNum(String search, boolean status){
String str = "";
for(int i = 0;i<search.length();i++){
String c = search.charAt(i)+"";
if(c.equals("1")){
str = str + "1";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("A")||c.equals("B")||c.equals("C")||c.equals("2")
||c.equals("a")||c.equals("b")||c.equals("c")){
str = str + "2";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("D")||c.equals("E")||c.equals("F")||c.equals("3")
||c.equals("d")||c.equals("e")||c.equals("f")){
str = str + "3";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("G")||c.equals("H")||c.equals("I")||c.equals("4")
||c.equals("g")||c.equals("h")||c.equals("i")){
str = str + "4";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("J")||c.equals("K")||c.equals("L")||c.equals("5")
||c.equals("j")||c.equals("k")||c.equals("l")){
str = str + "5";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("M")||c.equals("N")||c.equals("O")||c.equals("6")
||c.equals("m")||c.equals("n")||c.equals("o")){
str = str + "6";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("P")||c.equals("Q")||c.equals("R")||c.equals("S")||c.equals("7")
||c.equals("p")||c.equals("q")||c.equals("r")||c.equals("s")){
str = str + "7";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("T")||c.equals("U")||c.equals("V")||c.equals("8")
||c.equals("t")||c.equals("u")||c.equals("v")){
str = str + "8";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("W")||c.equals("X")||c.equals("Y")||c.equals("Z")||c.equals("9")
||c.equals("w")||c.equals("x")||c.equals("y")||c.equals("z")){
str = str + "9";
if(status){
i = i + 1;
}
continue;
}else if(c.equals("0")){
str = str + "0";
if(status){
i = i + 1;
}
continue;
}
}
return str;
};
Handler h = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if(msg.what==0){
if(mList!=null && myAdapter!=null){
myAdapter.updateListView(mList);
myAdapter.notifyDataSetChanged();
}
} else if(msg.what==1){
myAdapter.updateListView(list);
}
}
};
OnClickListener click = new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.main_num_1:
inputEditText.setText(inputEditText.getText()+"1");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_2:
inputEditText.setText(inputEditText.getText()+"2");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_3:
inputEditText.setText(inputEditText.getText()+"3");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_4:
inputEditText.setText(inputEditText.getText()+"4");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_5:
inputEditText.setText(inputEditText.getText()+"5");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_6:
inputEditText.setText(inputEditText.getText()+"6");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_7:
inputEditText.setText(inputEditText.getText()+"7");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_8:
inputEditText.setText(inputEditText.getText()+"8");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_9:
inputEditText.setText(inputEditText.getText()+"9");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_0:
inputEditText.setText(inputEditText.getText()+"0");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_left:
inputEditText.setText(inputEditText.getText()+"*");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_right:
inputEditText.setText(inputEditText.getText()+"#");
inputEditText.setSelection(inputEditText.getText().toString().length());
break;
case R.id.main_num_delete:
String string = inputEditText.getText().toString();
if(!string.equals("")){
inputEditText.setText(string.subSequence(0, string.length()-1));
inputEditText.setSelection(inputEditText.getText().toString().length());
}
break;
default:
break;
}
}
};
/**
* 通过反射调用setShowSoftInputOnFocus(false) 来隐藏键盘。 用 InputType.TYPE_NULL方法,无法显示光标。
*/
private void hideSystemKeyBoard(View view) {
this.getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
try {
Class<EditText> cls = EditText.class;
Method setSoftInputShownOnFocus;
// 此方法为隐藏的需用java反射调用
setSoftInputShownOnFocus = cls.getMethod("setShowSoftInputOnFocus",
boolean.class);
setSoftInputShownOnFocus.setAccessible(true);
setSoftInputShownOnFocus.invoke((EditText) view, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
源码地址:(暂时被我删除了,因为好像上传错了demo,晚上回去改哈)
个人觉得以上的算法还不是最高效的,如果有更好的算法,麻烦指点指点.
尊重原创, 转载请注明出处:http://blog.csdn.net/chillax_li/article/details/29380615
玩爆你的手机联系人--T9搜索的更多相关文章
- 玩爆你的手机联系人--T9搜索(一)
自己研究了好几天联系人的T9搜索算法, 先分享出来给大家看看. 欢迎不吝赐教.假设有大神有更好的T9搜索算法, 那更好啊,大家一起研究研究,谢谢. 第一部分是比較简单的获取手机联系人. 获取 ...
- 你真的会玩SQL吗?玩爆你的数据报表之存储过程编写(上)
你真的会玩SQL吗?系列目录 你真的会玩SQL吗?之逻辑查询处理阶段 你真的会玩SQL吗?和平大使 内连接.外连接 你真的会玩SQL吗?三范式.数据完整性 你真的会玩SQL吗?查询指定节点及其所有父节 ...
- Android-AsyncTask异步任务(获取手机联系人)
本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信. 一.Android当中的多线程 在Android当中,当一个应用程序的组件启动的时候,并且没有 ...
- android131 360 05 手势触摸滑动,sim卡,开机启动的广播,手机联系人,SharedPreferences,拦截短信
安卓手势触摸滑动: package com.itheima52.mobilesafe.activity; import android.app.Activity; import android.con ...
- 手势识别=读取手机联系人=ContentResolver-Day3
手势识别=读取手机联系人=ContentResolverDay32 mobile3.0 手机设置向导页面完成 选择器没有做完成 样式提取完成 自定义控件的优化继续 抽取父类Activity 完成 手机 ...
- android利用ContentResolver访问者获取手机联系人信息
转载自:http://www.jb51.net/article/106379.htm 首先需要在AndroidManifest.xml文件中添加权限: <uses-permission andr ...
- Expo大作战(三十九)--expo sdk api之 DocumentPicker,Contacts(获取手机联系人信息),Branch
简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...
- 记录一次手机联系人整理(XML文件格式处理)
场景:1.IOS手机和Android手机联系人同步时有部分重复联系人. 2.很早以前的HTC手机导出的联系人中备注信息有大量乱码,且很多联系人生日被设置为1970-01-01,导致生日提醒软件产生骚扰 ...
- 读取手机联系人,并用listview显示
读取手机联系人,用到的就是一个contentprovider. 数据库里面有三张重要的表 raw_contact 里面有所有联系人的数据 data 每个联系人的所有数据 mime-type 每条数据的 ...
- Android 读取手机联系人、拨号、发送短信及长按菜单的操作
本示例实现了读取手机联系人,拨号.发送短信及长按出现菜单选项的操作↓ 1.Andrid项目结构图↓主要操作图中红色方框内的文件. 2.首先布局代码如下↓ a, main.xml 程序运行的主界面,主要 ...
随机推荐
- BizWorks助力企业应用的高效开发与复用
简介: BizWorks作为企业级云原生应用数字工作台,能很好地支撑企业数字中台建设.云原生应用开发.企业资产运营管理等场景.本文不会全面介绍BizWorks平台的能力,而是着重介绍BizWorks在 ...
- 走进RDS|说说关系型数据库与Serverless
简介:看到如今Serverless在云计算行业喷薄欲出的态势,像极了<星星之火,可以燎原>中的描述:虽然不能预测未来的发展和变化,但对于云计算来说这是个相对确定的方向.本文将和大家说说关 ...
- 阿里云消息队列 RocketMQ 5.0 全新升级:消息、事件、流融合处理平台
简介: RocketMQ5.0 的发布标志着阿里云消息从消息领域正式迈向了"消息.事件.流"场景大融合的新局面.未来阿里云消息产品的演进也将继续围绕消息.事件.流核心场景而开展. ...
- [FAQ] curl SSL_connect: SSL_ERROR_SYSCALL / wget Unable to establish SSL connection
当客户端访问 https 网站时遇到这些错误提示,通常问题出在服务器,而不是客户端. 因为你换一个 https 网站进行请求,可以验证这一点. 通过浏览器访问正常,大多数浏览器通过重试较低的 TLS ...
- [FE] G2Plot 在 Vue 中使用 CDN 方式避免构建时增大 js 体积
使用 npm.yarn 方式安装的包,虽方便使用,但是会极大增加 vendor.xx.js 体积,拖慢网站运行速度. 以 G2Plot 为例,实际在 build 构建时,会下载一些额外字体到 vend ...
- dotnet 8 破坏性改动 在 AssemblyInformationalVersionAttribute 添加上 git 的 commit 号
我在一个 WPF 项目里面,在界面显示应用的版本号,更新到 dotnet 8 的 SDK 之后,发现我的界面布局损坏了.本质上这个破坏性改动和 WPF 没有什么关系,是 dotnet 的 SDK 或编 ...
- dotnet C# 通过 Vortice 使用 Direct2D 的 ID2D1CommandList 入门
本文将告诉大家如何通过 Vortice 使用 D2D 的 CommandList 功能 本文属于 DirectX 系列博客,更多 DirectX 和 D2D 以及 Vortice 库的博客,请参阅我的 ...
- dotnetCampus.UITest.WPF 一个支持中文用例的界面单元测试框架
本文来安利大家一个支持使用中文做用例名的 WPF 界面 UI 单元测试框架 卖点 有没有觉得命名太难?有没有觉得单元测试的命名更难?没错,这是一个业界的大问题.很多团队都会因为单元测试的用例函数命名太 ...
- Etcd 可视化管理工具,GUI 客户端。
Etcd Assistant--Etcd 可视化管理工具,GUI 客户端. 下载地址:http://www.redisant.cn/etcd 主要功能: 支持多标签页,同时连接到多个集群 以漂亮的格式 ...
- K8s应用---Service代理和kube-proxy转发(9)
一.k8s为什么要用Service四层代理 1.1 四层负载均衡Service: 概念.原理解读 1.pod ip 经常变化,service 是 pod 的代理,我们客户端访问,只需要访问 servi ...