闪退是每次按back键时候发生的。
back之后,程序的回调应该是onPause,onStop,onDestroy,而我重写了onDestroy来释放资源如下
@Override
public void onDestroy(){
super.onDestroy();
Log.e(TAG, "onDestroy: " );
socket.disconnect();
thread.stop();
}
这里问题就处在thread.stop()上,这是很不安全的一种做法,但是在5.1上就没有出现闪退的问题,应该是6.0的安全机制更强了,这里并没有去翻看源码考证,只是笔者的猜测。下面我们来分析一下Thread
Thread.stop()方法时,会发生下面两件事:
1. 即刻抛出ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catch或finally语句中。
2. 释放该线程所持有的所有的锁
那么如何正确的停止呢?
关于如何正确停止线程,这篇文章(how to stop thread)给出了一个很好的答案, 总结起来就下面3点(在停止线程时):
1. 使用violate boolean变量来标识线程是否停止
2. 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性
3. 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO
由于我们要慢慢的来实现长连接,首先就得要用service啊,不能简单只是单开一个线程。
说来也是惭愧,虽然学过这些方面的知识不过还是真的第一次做service的项目开发,之前的水平也只是停留在做一个startservice,stopservice的demo上。
public class ChatService extends Service {
private static final String TAG = "ChatService";
private MyBinder binder=new MyBinder();
private Socket socket=null;
private boolean result=true;
@Override
public void onCreate(){
super.onCreate();
/*
*在这里初始化socket链接
*/
try {
socket= IO.socket("http://115.159.38.75:3000");
socket.connect();
}catch (URISyntaxException e){
Log.e(TAG, "run: "+"error" );
e.printStackTrace();
}
while (!socket.connected()){}
}
@Override
public int onStartCommand(Intent i,int flags,int startId){
Log.e(TAG, "onStartCommand: " );
return super.onStartCommand(i,flags,startId);
}
@Override
public void onDestroy(){
/*
*关闭连接,停止监听
*/
super.onDestroy();
while (socket.connected()){
socket.disconnect();
}
socket.off();
Log.e(TAG, "onDestroy: "+socket.connected());
}
@Override
public IBinder onBind(Intent intent){
return binder;
}
/*
*在获取信息和获取登陆结果用了两个interface来回调结果
*/
public class MyBinder extends Binder{
/*
*用于获取新信息
*/
public void getMsg(final I_onMessageGet i_onMessageGet){
socket.on("newMsg", new Emitter.Listener() {
@Override
public void call(Object... args) {
String newcontent=args[1].toString();
i_onMessageGet.newMsg(newcontent);
}
});
}
public void sendMSg(String msg){
socket.emit("postMsg",msg);
}
public void login(String name){
socket.emit("login",name);
}
/*
*获取登陆结果
*/
public void getLoginResult(final I_loginResult i_loginResult){
socket.on("nickExisted", new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.e(TAG, "nickExisted" );
i_loginResult.loginFailed();
result=false;
}
});
socket.on("loginSuccess",new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.e(TAG, "loginSuccess:"+Thread.currentThread().getId());
i_loginResult.loginSuccess();
}
});
}
}
}
代码不长也都写了功能注释。
写个登陆界面用于取个名字而已。后面想配上对应的密码,不过还要在服务器端部署很多东西,正在努力补相关知识。与service的绑定也在这个activity中实现。
public class LoginActivity extends Activity {
@BindView(R.id.btn_login)Button button;
@BindView(R.id.edt_username)EditText editText;
private static final String TAG = "LoginActivity";
private ChatService.MyBinder myBinder;
private ServiceConnection serviceConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtil.log(TAG,"onServiceConnected");
myBinder=(ChatService.MyBinder)service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtil.log(TAG,"onServiceDisconnected");
}
};
@Override
protected void onCreate(Bundle s){
super.onCreate(s);
setContentView(R.layout.loginactivity);
ButterKnife.bind(this);
Intent service=new Intent(this,ChatService.class);
bindService(service,serviceConnection,BIND_AUTO_CREATE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name=editText.getText().toString();
myBinder.login(name);
myBinder.getLoginResult(new I_loginResult() {
@Override
public void loginSuccess() {
Intent intent=new Intent(LoginActivity.this,ChatActivity.class);
startActivity(intent);
finish();
}
@Override
public void loginFailed() {
handler.obtainMessage().sendToTarget();
}
});
}
});
}
Handler handler=new Handler(){
@Override
public void handleMessage(Message message){
editText.setText("");
Toast.makeText(LoginActivity.this,"用户名重复",Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onStart(){
LogUtil.log(TAG,"onStart");
super.onStart();
}
@Override
protected void onResume(){
LogUtil.log(TAG,"onResume");
super.onResume();
}
}
另外在写这个activity的界面的时候用了constraintlayout,参考一下郭神的文章。http://blog.csdn.net/guolin_blog/article/details/53122387 调界面真的是相当方便啊。不过一些负责的界面还是要自己动手啊,简单的用这个constraintlayout真的是再合适不过了。
再附ChatActivity的相关改动。
public class ChatActivity extends AppCompatActivity {
private static final String TAG = "ChatActivity";
@BindView(R.id.text_list)RecyclerView recyclerView;
@BindView(R.id.send_btn)Button button;
@BindView(R.id.chat_edit)EditText editText;
private static String IPAddress="115.159.38.75";
private static int PORT=4000;
private Socket socket=null;
private ChatRecyclerAdpter adpter;
private ArrayList msg_list=new ArrayList<>();
private DBHelper dbHelper;
private Cursor cursor;
private Thread thread;
private NotificationManager notificationManager;
private ChatService.MyBinder binder;
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtil.log("from util","onServiceConnected: "+name.toString());
binder=(ChatService.MyBinder)service;
binder.getMsg(new I_onMessageGet() {
@Override
public void newMsg(String s) {
Log.e(TAG, "newMsg: "+s );
SendNotification(s);
Msg msg=new Msg();
msg.setContent(s);
msg.setType(0);
msg_list.add(msg);
dbHelper.insert(msg);
cursor.requery();
Message.obtain(handler).sendToTarget();
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtil.log(TAG, "onServiceDisconnected: "+name.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chatactivity);
ButterKnife.bind(this);
notificationManager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
dbHelper=new DBHelper(this);
cursor=dbHelper.select();
for (int i=0;inew Msg();
msg.setContent(cursor.getString(1));
msg.setType(cursor.getInt(2));
msg_list.add(msg);
}
adpter=new ChatRecyclerAdpter(this,msg_list);
LinearLayoutManager manager=new LinearLayoutManager(this);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adpter);
//绑定service
Intent startservice=new Intent(this,ChatService.class);
bindService(startservice,connection,BIND_AUTO_CREATE);
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
recyclerView.scrollToPosition(msg_list.size()-1);
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String msg_send=editText.getText().toString();
binder.sendMSg(msg_send);
Msg msg=new Msg();
msg.setType(1);
msg.setContent(msg_send);
msg_list.add(msg);
dbHelper.insert(msg);
cursor.requery();
Message.obtain(handler).sendToTarget();
editText.setText("");
}
});
}
Handler handler=new Handler(){
@Override
public void handleMessage(Message message){
adpter.notifyDataSetChanged();
recyclerView.scrollToPosition(msg_list.size()-1);
}
};
private void SendNotification(String content){
LogUtil.log(TAG, "SendNotification: "+content );
/*这里要注意一个细节。
*如果当前Activity存在的话,不应该再create一个新的活动,应该是回到当前活动。
* 所以要给Intent添加对应的flag
*/
Intent i=new Intent(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_LAUNCHER);
i.setComponent(new ComponentName(this,ChatActivity.class));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
PendingIntent p=PendingIntent.getActivity(this,0,i,0);
Notification notification=new Notification.Builder(this)
.setAutoCancel(true)
.setContentText(content)
.setContentTitle("新消息")
.setTicker("Ticker")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setDefaults(Notification.DEFAULT_SOUND)
.setContentIntent(p)
.build();
notificationManager.notify(0,notification);
}
@Override
public void onDestroy(){
unbindService(connection);
super.onDestroy();
Log.e(TAG, "onDestroy");
}
}
目前问题:
1.在魅族手机上提示了该app耗电过高??
2.掉线问题
下一部任务目标: