绑定服务状态变更时刷新MainActivity及Looper阻塞问题排查
问题分析与解决方案
首先咱们来拆解你遇到的两个核心问题:ListView刷新逻辑的稳定性和UDPService无法正常退出Looper/线程。先直接说根源,再给你改好的代码和优化建议。
核心问题根源
- UDP线程无法终止:你在
UDPBroadcastThread里写了while(true)的无限循环,还调用了Looper.prepare()但没有正确退出逻辑——服务销毁时线程还在跑,Looper也一直占用资源,导致服务无法正常销毁。 - 回调引用不安全:你通过
MainActivity.getContextOfApplication()强转成回调接口,不仅容易出现类型转换异常,还会造成MainActivity的内存泄漏。 - UI操作线程问题:
notifyDataSetChanged()必须在主线程调用,你当前的回调直接在UDP线程执行,虽然 onCreate 阶段侥幸生效,但后续可能出现崩溃。 - 无关类继承错误:
LampManager和Lamp继承了AppCompatActivity,这完全没必要,会导致不必要的上下文持有和内存泄漏。
具体修复方案
1. 修复UDPService:让线程能正常停止,移除无用Looper
public class UDPService extends Service { private final IBinder myBinder = new MyLocalBinder(); LampManager lm = LampManager.getInstance(); int port = 4096; public String URL; private DatagramSocket udpSocket; private DatagramPacket packet; String text; static boolean bol = false; private OnHeadlineSelectedListener mCallback; // 新增:线程终止标志,volatile保证多线程可见性 private volatile boolean isListening = true; int i=2; private void listenAndWaitAndThrowIntent() throws Exception { byte[] message = new byte[5120]; if(udpSocket == null || udpSocket.isClosed()){ udpSocket = new DatagramSocket(port); } packet = new DatagramPacket(message, message.length); Log.i("UDP client: ", "about to wait to receive"); udpSocket.receive(packet); String text = new String(message, 0, packet.getLength()); this.text = text; Log.d("Received data", text); URL = packet.getAddress().toString().replace("/", ""); Log.d("Address", String.valueOf(packet.getAddress())); Log.d("Port", String.valueOf(packet.getPort())); } Thread UDPBroadcastThread; void startListenForUDPBroadcast(){ UDPBroadcastThread = new Thread(new Runnable() { @Override public void run() { // 移除无用的Looper.prepare()——后台线程不需要消息循环 while (isListening) { try { listenAndWaitAndThrowIntent(); bol = lm.addLamp(URL,text); // 空指针防护:只有回调存在时才调用 if (mCallback != null) { mCallback.onArticleSelected(i); } Thread.sleep(1000); } catch (SocketException e) { // Socket关闭时,直接跳出循环终止线程 Log.i("UDP", "Socket closed, stopping listener"); break; } catch (Exception e) { Log.i("UDP", e.getMessage()); } } Log.i("UDP", "Listener thread stopped"); } }); UDPBroadcastThread.start(); } private void stopListen() { // 先设置终止标志 isListening = false; // 关闭Socket,唤醒阻塞的receive()方法 if (udpSocket != null && !udpSocket.isClosed()) { udpSocket.close(); } // 等待线程安全结束(可选,避免内存泄漏) try { if (UDPBroadcastThread != null) { UDPBroadcastThread.join(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onDestroy() { stopListen(); super.onDestroy(); } @Override public void onCreate(){ super.onCreate(); startListenForUDPBroadcast(); } @Override public IBinder onBind(Intent intent) { Log.i("UDP", "Service Bound"); return myBinder; } // 新增:注册/注销回调的方法,安全管理引用 public void setCallback(OnHeadlineSelectedListener callback) { this.mCallback = callback; } public void removeCallback() { this.mCallback = null; } public interface OnHeadlineSelectedListener { void onArticleSelected(int position); } public class MyLocalBinder extends Binder { UDPService getService() { return UDPService.this; } } }
2. 修复MainActivity:安全注册回调,确保UI操作在主线程
public class MainActivity extends AppCompatActivity implements UDPService.OnHeadlineSelectedListener{ private UDPService myService; private BaseAdapter baseAdapter; protected void onStart() { super.onStart(); Intent intent = new Intent(this, UDPService.class); bindService(intent, myConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); // 注销回调,避免内存泄漏 if (myService != null) { myService.removeCallback(); } unbindService(myConnection); } private ServiceConnection myConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { UDPService.MyLocalBinder binder = (UDPService.MyLocalBinder) service; myService = binder.getService(); // 绑定成功后注册回调 myService.setCallback(MainActivity.this); } public void onServiceDisconnected(ComponentName className) { myService = null; } }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化你的BaseAdapter示例 // baseAdapter = new YourCustomAdapter(...); // listView.setAdapter(baseAdapter); } // 移除静态的getContextOfApplication方法,改用全局Application上下文 @Override public void onArticleSelected(int position) { // 必须在主线程刷新UI! runOnUiThread(new Runnable() { @Override public void run() { baseAdapter.notifyDataSetChanged(); } }); } }
3. 修复数据模型类:移除不必要的Activity继承
LampManager 修改:
public class LampManager { private static final LampManager ourInstance = new LampManager(); private List<Lamp> lista = new ArrayList<>(); public static LampManager getInstance() { return ourInstance; } // 单例构造方法私有化 private LampManager() {} public boolean addLamp(String URL, String name) { String ip = URL.replace("/", ""); Log.i("UDP", "Messagio ricevuto!"); boolean b = true; System.out.println(getLamps().size()); Lamp l = null; try { for(int i=0; i<getLamps().size();i++) { System.out.println(getLamp(i).getURL()); char c = name.charAt(name.length()-1); // 移除阻塞线程的Thread.sleep——完全没必要 if (lista.get(i).getName().equals(name)) { b = false; break; // 找到重复直接退出循环,提升效率 } else if(c>='0' && c<='9' && name.contains("LAMP_")) { b=true; } else { b=false; } } if(b){ l = new Lamp(URL, name); // 使用全局Application上下文,避免持有Activity new TcpClient(l, MyApplication.getInstance().getApplicationContext()).execute(); lista.add(l); return b; } }catch (Exception e){ Log.d("ERR",e.getMessage()); } return b; } public List<Lamp> getLamps() { return lista; } public Lamp getLamp(int index) { return lista.get(index); } }
Lamp 修改:
public class Lamp { private String name; private int rgb; private Bitmap image; private int intensity; private Boolean state; public String URL; public Lamp(String URL, String name){ this.name=name; this.URL=URL; // 使用全局Application上下文 Context appContext = MyApplication.getInstance().getApplicationContext(); SharedPreferences sh = PreferenceManager.getDefaultSharedPreferences(appContext); SharedPreferences.Editor editor = sh.edit(); setName(name); editor.putString(getName()+":URL", URL); editor.apply(); } // 补充必要的getter/setter public String getName() { return name; } public void setName(String name) { this.name = name; } }
4. 新增全局Application类(获取安全上下文)
创建MyApplication类,在Manifest中注册:
public class MyApplication extends Application { private static MyApplication instance; @Override public void onCreate() { super.onCreate(); instance = this; } public static MyApplication getInstance() { return instance; } }
在AndroidManifest.xml中注册:
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.YourApp"> <!-- 你的Activity声明 --> </application>
关键优化点总结
- 线程终止逻辑:通过
volatile标志位+Socket关闭,确保服务销毁时UDP线程能正常退出,解决Looper无法停止的问题。 - 回调安全管理:在
onServiceConnected注册回调,onStop注销回调,避免内存泄漏和空指针。 - UI线程保证:用
runOnUiThread包裹notifyDataSetChanged(),确保UI操作在主线程执行。 - 上下文优化:用全局Application上下文替代静态持有Activity,消除内存泄漏风险。
- 数据模型修正:移除不必要的
AppCompatActivity继承,让模型类回归本质。
内容的提问来源于stack exchange,提问作者Davide
相关产品推荐
相关产品推荐

