You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

绑定服务状态变更时刷新MainActivity及Looper阻塞问题排查

问题分析与解决方案

首先咱们来拆解你遇到的两个核心问题:ListView刷新逻辑的稳定性和UDPService无法正常退出Looper/线程。先直接说根源,再给你改好的代码和优化建议。


核心问题根源

  1. UDP线程无法终止:你在UDPBroadcastThread里写了while(true)的无限循环,还调用了Looper.prepare()但没有正确退出逻辑——服务销毁时线程还在跑,Looper也一直占用资源,导致服务无法正常销毁。
  2. 回调引用不安全:你通过MainActivity.getContextOfApplication()强转成回调接口,不仅容易出现类型转换异常,还会造成MainActivity的内存泄漏。
  3. UI操作线程问题:notifyDataSetChanged()必须在主线程调用,你当前的回调直接在UDP线程执行,虽然 onCreate 阶段侥幸生效,但后续可能出现崩溃。
  4. 无关类继承错误: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>

关键优化点总结

  1. 线程终止逻辑:通过volatile标志位+Socket关闭,确保服务销毁时UDP线程能正常退出,解决Looper无法停止的问题。
  2. 回调安全管理:在onServiceConnected注册回调,onStop注销回调,避免内存泄漏和空指针。
  3. UI线程保证:用runOnUiThread包裹notifyDataSetChanged(),确保UI操作在主线程执行。
  4. 上下文优化:用全局Application上下文替代静态持有Activity,消除内存泄漏风险。
  5. 数据模型修正:移除不必要的AppCompatActivity继承,让模型类回归本质。

内容的提问来源于stack exchange,提问作者Davide

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:34:46