android之仿qq
仿qq
1、环境准备
开发环境:
1.1、前端:Android Studio3.5.3、spark客户端
1.2、后台:IDEA2019.2.4、openfire
- Android Studio :qq页面;
- spark :与qq进行聊天的另一客户端;
- IDEA2019.2.4,采用springboot框架;
- openfire :实现即时聊天的服务器;
2、功能
2.1 应用功能
登录
手机号短信验证+账号进行注册
手机号短信验证+密码找回
与图灵机器人聊天
在线即时聊天
qq页面设计(Navigation、Drawerable、Toolbar、Adapter、switch button…)
动态栏页面列表的选择展示
意见反馈
2.2 采用的接口
图灵机器人
mob短信验证
openfire的xmpp协议
okhttp–基于websocket
3、项目结构
- 表user:
- 表friend:
- 表feedback:
- 表collect:
4、gradle准备环境+其他准备
4.1. 短信验证
AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
app:
classpath 'com.mob.sdk:MobSDK:+'// 注册MobSDK
apply plugin: 'com.mob.sdk'
MobSDK {
appKey "xxxxx"
appSecret "xxxxxx"
SMSSDK {}
}
4.2. okhttp
- 我们一直使用的http协议只能由客户端发起,服务端无法直接进行推送,这就导致了如果服务端有持续的变化客户端想要获知就比较麻烦。WebSocket协议就是为了解决这个问题应运而生。
- WebSocket协议,客户端和服务端都可以主动的推送消息,可以是文本也可以是二进制数据。而且没有同源策略的限制,不存在跨域问题。协议的标识符就是
ws
。像https一样如果加密的话就是wxs
AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
dependencies :
implementation "com.squareup.okhttp:okhttp:2.7.5"
implementation 'com.squareup.okio:okio:2.4.1'
implementation 'com.alibaba:fastjson:1.2.62'
4.3、openfire和smack
3.1【 openfire的安装和准备 】
3.2 smack配置:
5、适配器+listview(此处以fragment为例)
private ArrayList<String> qqpic;//头像
private ArrayList<Object> qqname;//昵称
private ArrayList<Object> qqqm;//个性签名
private ListView friendList;
private View root;
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
root = inflater.inflate(R.layout.fragment_dashboard, container, false);
friendList = (ListView)root.findViewById(R.id.friendList);
dataInit();
ArrayAdapter<Object> tt = newMyAdapter(getActivity(),R.drawable.qq1,qqname,qqqm);
friendList.setAdapter(tt);
tt.notifyDataSetChanged();
return root;
}
private void dataInit(){
qqname = new ArrayList<Object>();
qqname.add("qly");
qqname.add("ljx");
qqname.add("lucy");
qqname.add("lily");
qqname.add("lolo");
qqname.add("fancy");
qqname.add("koko");
qqname.add("wwa");
qqname.add("polo");
qqqm = new ArrayList<Object>();
qqqm.add("哗啦啦");
qqqm.add("1111");
qqqm.add("呜呜呜呜");
qqqm.add("啊啊啊啊啊");
qqqm.add("热热热热热热");
qqqm.add("嗷嗷嗷啊啊");
qqqm.add("踩踩踩从");
qqqm.add("吱吱吱吱在");
qqqm.add("十四说四十是");
qqpic = new ArrayList<String>();
qqpic.add(String.valueOf(R.drawable.qq1));
qqpic.add(String.valueOf(R.drawable.qq2));
qqpic.add(String.valueOf(R.drawable.qq3));
qqpic.add(String.valueOf(R.drawable.qq4));
qqpic.add(String.valueOf(R.drawable.qq5));
qqpic.add(String.valueOf(R.drawable.qq6));
qqpic.add(String.valueOf(R.drawable.qq7));
qqpic.add(String.valueOf(R.drawable.qq8));
qqpic.add(String.valueOf(R.drawable.qq9));
}
public class MyAdapter extends ArrayAdapter<Object> {//自定义适配器
private Context context;
private int resource;
List<Object> object1;
List<Object> object2;
public MyAdapter(Context context, int resource, List<Object> object1, List<Object> object2) {
super(context, resource, object1);
this.context = context;
this.resource = resource;
this.object1 = object1;
this.object2 = object2;
}
public MyAdapter(Context context, int resource) {
super(context, resource);
}
public View getView(final int pos, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = LayoutInflater.from(getActivity()).inflate(R.layout.friend, null);
ImageView iv = (ImageView) convertView.findViewById(R.id.qqImg);
iv.setImageResource(Integer.parseInt(qqpic.get(pos)));
TextView name = (TextView) convertView.findViewById(R.id.qqName);
name.setText(qqname.get(pos).toString());
TextView qm = (TextView) convertView.findViewById(R.id.qqQm);
qm.setText(qqqm.get(pos).toString());
final Button bt = (Button) convertView.findViewById(R.id.chatBtn);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getActivity(), "click\n", Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
}
ListView:
效果:
6、抽屉栏和导航栏
继承自AppCompatActivity的Activity:
/////DrawerLayout/////////////////////////////
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
AppBarConfiguration m1 = new AppBarConfiguration.Builder(R.id.activity,
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
R.id.nav_tools,R.id.feedback)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, m1);
NavigationUI.setupWithNavController(navigationView, navController);
/////NavigationView + DrawerLayout////////////////
View headerView = navigationView.getHeaderView(0);//获取头布局
infoBtn = headerView.findViewById(R.id.infobtn);
infoBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
System.out.println("-----------------");
Intent intent1 = new Intent(MainActivity.this,MyInfoActivity.class);
startActivity(intent1);
}
});
////////////抽屉点击////////////////////
navigationView.setNavigationItemSelectedListener(new OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.activity://点我了解会员
Intent intent = new Intent(MainActivity.this, ShareAC.class);
startActivity(intent);
break;
......
}
return true;
}
});
/////////bottom//////////////////////////
BottomNavigationView navView = findViewById(R.id.nav_view_main);
AppBarConfiguration m2 = new AppBarConfiguration.Builder(
R.id.navigation_dashboard, R.id.navigation_home, R.id.navigation_notifications)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, m2);
NavigationUI.setupWithNavController(navView, navController);
activity_main_drawer.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single"
android:clickable="true">
<item
android:id="@+id/activity"
android:icon="@drawable/vip"
android:title="点我了解会员" />
<item
android:id="@+id/nav_home"
android:icon="@drawable/wallet"
android:title="@string/menu_home" />
<item
android:id="@+id/nav_gallery"
android:icon="@drawable/fav"
android:title="@string/menu_gallery" />
<item
android:id="@+id/nav_slideshow"
android:icon="@drawable/file"
android:title="@string/menu_slideshow" />
<item
android:id="@+id/nav_tools"
android:icon="@drawable/plug"
android:title="@string/menu_tools" />
<item
android:id="@+id/feedback"
android:icon="@drawable/feedback"
android:title="意见反馈" />
</group>
</menu>
效果图:
7、转换按钮
dependencies添加:
implementation 'com.github.zcweng:switch-button:0.0.3@aar'
extends AppCompatActivity implements SwitchButton.OnCheckedChangeListener:
SwitchButton qzoneBtn1;
qzoneBtn1 = (SwitchButton) findViewById(R.id.qzoneBtn1);
qzoneBtn1.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener(){
@Override
public void onCheckedChanged(SwitchButton view, boolean isChecked) {
....
}
});
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/headdtsz"
android:layout_width="401dp"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="66dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="74dp"
android:layout_height="match_parent"
app:srcCompat="@drawable/qzone" />
<TextView
android:id="@+id/textView"
android:layout_width="202dp"
android:layout_height="33dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="94dp"
android:layout_marginLeft="94dp"
android:layout_marginTop="17dp"
android:layout_marginBottom="16dp"
android:text="好友动态"
android:textSize="12sp" />
<com.suke.widget.SwitchButton
android:id="@+id/qzoneBtn1"
android:layout_width="64dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="15dp"
android:layout_marginBottom="12dp"
app:sb_background="@color/smssdk_common_line_gray"
app:sb_checkline_color="@color/smssdk_bg_gray"
app:sb_show_indicator="false" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="66dp">
<ImageView
android:id="@+id/imageView2"
android:layout_width="74dp"
android:layout_height="match_parent"
app:srcCompat="@drawable/tencent_news" />
<TextView
android:id="@+id/textView2"
android:layout_width="202dp"
android:layout_height="33dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginStart="95dp"
android:layout_marginLeft="95dp"
android:layout_marginTop="16dp"
android:text="腾讯新闻"
android:textSize="12sp" />
<com.suke.widget.SwitchButton
android:id="@+id/newsBtn1"
android:layout_width="64dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="15dp"
android:layout_marginBottom="12dp"
app:sb_background="@color/smssdk_common_line_gray"
app:sb_checkline_color="@color/smssdk_bg_gray"
app:sb_show_indicator="false" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="66dp">
<ImageView
android:id="@+id/imageView3"
android:layout_width="74dp"
android:layout_height="wrap_content"
app:srcCompat="@drawable/baidumap" />
<TextView
android:id="@+id/textView3"
android:layout_width="202dp"
android:layout_height="33dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="91dp"
android:layout_marginLeft="91dp"
android:layout_marginBottom="15dp"
android:text="百度地图"
android:textSize="12sp" />
<com.suke.widget.SwitchButton
android:id="@+id/mapBtn1"
android:layout_width="64dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="15dp"
android:layout_marginBottom="12dp"
app:sb_background="@color/smssdk_common_line_gray"
app:sb_checkline_color="@color/smssdk_bg_gray"
app:sb_show_indicator="false" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="66dp">
<ImageView
android:id="@+id/imageView4"
android:layout_width="74dp"
android:layout_height="wrap_content"
app:srcCompat="@drawable/baidu" />
<TextView
android:id="@+id/textView4"
android:layout_width="202dp"
android:layout_height="33dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="90dp"
android:layout_marginLeft="90dp"
android:layout_marginBottom="16dp"
android:text="百度"
android:textSize="12sp" />
<com.suke.widget.SwitchButton
android:id="@+id/searchBtn1"
android:layout_width="64dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="15dp"
android:layout_marginBottom="12dp"
app:sb_background="@color/smssdk_common_line_gray"
app:sb_checkline_color="@color/smssdk_bg_gray"
app:sb_show_indicator="false" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
效果图:
8、okhttp的使用<以反馈为例>
在获取联系方式和反馈内容后,传给后台(post/get),通过handler获得反馈后的结果。(因为这里采用了线程)
//注意使用ip而非localhost或127.0.1
String feedbackUrl = "http://xxx.xxx.xxx.xxx:端口/feedback/insert?";
private static Handler handler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.advice);
submit = (Button)findViewById(R.id.btn_submit);
submit.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
advice = (EditText)findViewById(R.id.et_advice);
final String content = advice.getText().toString();
myPhone = (EditText)findViewById(R.id.phone);
String phone = myPhone.getText().toString();
final String addr = feedbackUrl + "phone=" + phone + "&content=" + content;
System.out.println(addr);
postFeedback(addr, content);
}
});
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
// super.handleMessage(msg);
switch (msg.what) {
case 0:
String ans = msg.getData().get("hasFeedback").toString();
if(ans.equals("true")) {
Toast.makeText(FeedbackActivity.this, "反馈成功", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(FeedbackActivity.this, MainActivity.class);
startActivity(intent);
}
break;
default:
Toast.makeText(FeedbackActivity.this, "反馈失败,请稍后再试", Toast.LENGTH_SHORT).show();
}
}
};
}
//这里采用了post,若是get则去掉.post()或换成.get()
private void postFeedback(final String addr, final String jsonString){
final boolean flag = false;
new Thread(new Runnable() {
@Override
public void run() {
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(mediaType, jsonString);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);
Request request = new Request.Builder()
.url(addr)
.post(body)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
System.out.println("111-------fail-------------------------------------------");
}
@Override
public void onResponse(Response response) throws IOException {
Message msg=new Message();
msg.what=0;
Bundle bundle=new Bundle();
bundle.putString("hasFeedback",response.header("hasFeedback"));
msg.setData(bundle);
handler.sendMessage(msg);
System.out.println("feedback:"+response.header("hasFeedback")+"222----------success------------------------------------------");
}
});
}
}).start();
}
9、短信验证
10、图灵机器人
11、与openfire交互
public class OpenfireConnect{
private static XMPPTCPConnectionConfiguration config;
private static XMPPTCPConnection conn;//AbstractXMPPConnection
private static HttpServletResponse resp;
private static String isLogin = "false";
private ArrayList<String> msg_ans = new ArrayList<String>();
private static int flag = 1;
static BASE64Encoder encoder = new sun.misc.BASE64Encoder();
static BASE64Decoder decoder = new sun.misc.BASE64Decoder();
static {
try {
config = XMPPTCPConnectionConfiguration.builder()
.setXmppDomain("自设服务器名称")
.setHost("自设主机名称")
.setPort(5222)//.setDebuggerEnabled(true)
.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
.build();
conn = new XMPPTCPConnection(config);
conn.connect();
} catch (Exception e) {
e.printStackTrace();
}
}
@PostMapping("/conn")
public static void login(@Param("username") String username,@Param("password") String password){
try {
if(conn!=null){
conn.login(username, password);
isLogin = "true";
System.out.println("user "+username+" login successfully.");
}
} catch (XMPPException e) {
e.printStackTrace();
} catch (SmackException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@GetMapping("/send")
public void sendMessage(@Param("username") String username, @Param("password") String password, @Param("friend")String friend, @Param("msg") String msg, HttpServletResponse response){
if(isLogin.equals("false"))
login(username, password);
flag = 0;
//再构建聊天室
ChatManager cm = ChatManager.getInstanceFor(conn);
cm.addIncomingListener((from, message, chat) -> {
String ans = message.getBody();
flag = 1;
response.addHeader("msg",ans);
msg_ans.add(ans);
System.out.println(friend +":response-------,msg----------"+response.getHeader("msg"));
});
if(flag == 0){
response.addHeader("msg",null);
msg_ans.clear();
}
else if(flag == 1){
response.addHeader("msg",msg_ans.toString());
}
System.out.println("response,msg----------"+response.getHeader("msg"));
try {
EntityBareJid jid = JidCreate.entityBareFrom(friend+"@fancycom.qq");//这里就是Xmpp地址,用户名@域名
Chat chat = cm.chatWith(jid);
Message message = new Message();
message.setBody(msg);
chat.send(message);
// while (true);
} catch (XmppStringprepException e) {
e.printStackTrace();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}