如何摆脱Activity已泄露服务连接错误

时间:2015-01-13 09:27:57

标签: java android memory-leaks

在调用onDestroy()之后,logcat显示Activity已泄漏服务连接错误。这是logcat的一部分:

01-13 18:14:50.281: D/MYLOG(16984): in passStop()
01-13 18:14:51.471: D/MYLOG(16984): onPause() called
01-13 18:14:52.001: D/MYLOG(16984): onDestroy() called
01-13 18:14:52.021: E/ActivityThread(16984): Activity com.example.mypassthrough.MainActivity has leaked ServiceConnection android.bluetooth.BluetoothA2dp$2@41e8cd68 that was originally bound here
01-13 18:14:52.021: E/ActivityThread(16984): android.app.ServiceConnectionLeaked: Activity com.example.mypassthrough.MainActivity has leaked ServiceConnection android.bluetooth.BluetoothA2dp$2@41e8cd68 that was originally bound here
01-13 18:14:52.021: E/ActivityThread(16984):    at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:970)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:864)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1569)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.app.ContextImpl.bindService(ContextImpl.java:1552)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.content.ContextWrapper.bindService(ContextWrapper.java:517)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.bluetooth.BluetoothA2dp.doBind(BluetoothA2dp.java:165)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.bluetooth.BluetoothA2dp.<init>(BluetoothA2dp.java:158)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.bluetooth.BluetoothAdapter.getProfileProxy(BluetoothAdapter.java:1364)
01-13 18:14:52.021: E/ActivityThread(16984):    at com.example.mypassthrough.BluetoothA2DPRequester.request(BluetoothA2DPRequester.java:52)
01-13 18:14:52.021: E/ActivityThread(16984):    at com.example.mypassthrough.MainActivity.btnA2dpStart_onClick(MainActivity.java:307)
01-13 18:14:52.021: E/ActivityThread(16984):    at java.lang.reflect.Method.invokeNative(Native Method)
01-13 18:14:52.021: E/ActivityThread(16984):    at java.lang.reflect.Method.invoke(Method.java:515)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.view.View$1.onClick(View.java:3818)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.view.View.performClick(View.java:4438)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.view.View$PerformClick.run(View.java:18422)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.os.Handler.handleCallback(Handler.java:733)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.os.Handler.dispatchMessage(Handler.java:95)
01-13 18:14:52.021: E/ActivityThread(16984):    at android.os.Looper.loop(Looper.java:136)  

这是MainActivity文件:

// public class MainActivity extends Activity {
// public class MainActivity extends Activity implements BluetoothBroadcastReceiver.Callback, BluetoothA2DPRequester.Callback {
public class MainActivity extends Activity implements BluetoothA2DPRequester.Callback {  

    AudioManager am = null;
    AudioRecord record =null;
    AudioTrack track =null;
    BluetoothHeadset mBluetoothHeadset;
    // BluetoothA2dp mBluetoothSpeaker;
    BluetoothDevice device;
    BluetoothDevice mConnectedHeadset;

    ConnectionClass mConnectionClass;

    final int SAMPLE_FREQUENCY = 16000;
    final int SIZE_OF_RECORD_ARRAY = 1024;  // 1024 ORIGINAL
    final int WAV_SAMPLE_MULTIPLICATION_FACTOR = 1;
    int i= 0;
    boolean isPlaying = false;

    // Local Bluetooth adapter
    private BluetoothAdapter mBluetoothAdapter = null;

    // Intent request codes
    private static final int REQUEST_CONNECT_DEVICE = 1;
    private static final int REQUEST_ENABLE_BT = 2;

    private static final String HTC_MEDIA = "CSR8670-CNS 10001v4-STEREO";



    class MyThread extends Thread{
        private volatile boolean passThroughMode = true;

        MyThread(){
            super();
        }

        MyThread(boolean newPTV){
            this.passThroughMode = newPTV;
        }

        @Override
        public void run(){
            short[] lin = new short[SIZE_OF_RECORD_ARRAY];
            int num = 0;
            // record.startRecording();
            // track.play();
            while (passThroughMode) {
            // while (!isInterrupted()) {
                num = record.read(lin, 0, SIZE_OF_RECORD_ARRAY);
                for(i=0;i<lin.length;i++)
                    lin[i] *= WAV_SAMPLE_MULTIPLICATION_FACTOR; 
                track.write(lin, 0, num);
            }

            record.stop();
            track.stop();
//          record.release();
//          track.release();
        }

        public void stopThread(){
            passThroughMode = false;
        }

        boolean getpassThroughMode(){
            return passThroughMode;
        }

        void setpassThroughMode(boolean value){
            this.passThroughMode = value;
        }

    }   // class MyThread extends Thread{   CLOSED

    MyThread newThread;

    private void init() {
        Log.d("MYLOG", "MainActivity init() called");
        int min = AudioRecord.getMinBufferSize(SAMPLE_FREQUENCY, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        record = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, SAMPLE_FREQUENCY, AudioFormat.CHANNEL_IN_MONO,
                                 AudioFormat.ENCODING_PCM_16BIT, min);
        int maxJitter = AudioTrack.getMinBufferSize(SAMPLE_FREQUENCY, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        track = new AudioTrack(AudioManager.MODE_IN_COMMUNICATION, SAMPLE_FREQUENCY, AudioFormat.CHANNEL_OUT_MONO,
                               AudioFormat.ENCODING_PCM_16BIT, maxJitter, AudioTrack.MODE_STREAM);
        am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
        am.setMode(AudioManager.MODE_IN_COMMUNICATION);

    }

    private void ensureDiscoverable() {
        Log.d("MYLOG", "MainActivity ensure discoverable");
        if (mBluetoothAdapter.getScanMode() !=
            BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
            startActivity(discoverableIntent);
        }
    }


    @SuppressLint({ "InlinedApi", "NewApi" })
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setVolumeControlStream(AudioManager.MODE_IN_COMMUNICATION);
        // init(); // -> Moved this to onResume();
        Log.d("MYLOG", "MainActivity onCreate() called");
        // Get local Bluetooth adapter
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // If the adapter is null, then Bluetooth is not supported
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
            finish();
            // return;
        }
        Log.d("MYLOG", "getProfileProxy() called");

    }

    @Override
    public void onStart() {
        super.onStart();
        Log.e("MYLOG", "++ ON START ++");

        // If BT is not on, request that it be enabled.
        // setupChat() will then be called during onActivityResult
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
            // Otherwise, setup the chat session
        } 

        // mConnectionClass = new ConnectionClass();
        mConnectionClass = new ConnectionClass(this);
     }

    @Override
    protected void onResume(){
        super.onResume();
        // newThread.stopThread();
        Log.d("MYLOG", "onResume() called");
        init();
        newThread = new MyThread(true);         // ORIGINAL -> true
        newThread.start(); 
    }

    @Override
    protected void onPause(){
        super.onPause();
        Log.d("MYLOG", "onPause() called");
        newThread.stopThread();
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.scan:
            // Launch the DeviceListActivity to see devices and do scan
            Intent serverIntent = new Intent(this, DeviceListActivity.class);
            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
            return true;
        case R.id.discoverable:
            // Ensure this device is discoverable by others
            ensureDiscoverable();
            return true;
        }
        return false;
    }   

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d("MYLOG", "MainActivity onActivityResult " + resultCode);
        switch (requestCode) {
        case REQUEST_CONNECT_DEVICE:
            // When DeviceListActivity returns with a device to connect
            if (resultCode == Activity.RESULT_OK) {
                // Get the device MAC address
                String address = data.getExtras()
                                     .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                // Get the BLuetoothDevice object
                // BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
                device = mBluetoothAdapter.getRemoteDevice(address);
                // Attempt to connect to the device
                // mChatService.connect(device);
                Log.d("MYLOG", "ABOUT TO CONNECT");
                mConnectionClass.connect(device);
            }
            break;
        case REQUEST_ENABLE_BT:
            // When the request to enable Bluetooth returns
            if (resultCode == Activity.RESULT_OK) {
                // Bluetooth is now enabled, so set up a chat session
                // setupChat();
                Toast.makeText(this, "BLUETOOTH ENABLED", Toast.LENGTH_LONG).show();
            } else {
                // User did not enable Bluetooth or an error occured
                Log.d("MYLOG", "BT not enabled");
                Toast.makeText(this, "BLUETOOTH NOT ENABLED", Toast.LENGTH_LONG).show();
                // finish();
            }
        }
    }


    public void passStop(View view){
        Button playBtn = (Button) findViewById(R.id.playBtn);
        Log.d("MYLOG", "in passStop()");

        if(!isPlaying){
            record.startRecording();
            track.play();
            isPlaying = true;
            playBtn.setText("Pause");
        }
        else{
           record.stop();
           track.pause();
           isPlaying=false;
           playBtn.setText("Start");
        }
    }


    public void btnA2dpStart_onClick(View view){
//      onBluetoothConnected();
        new BluetoothA2DPRequester(this).request(this, mBluetoothAdapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        newThread.stopThread();
        record.release();
        track.release();
//      unregisterReceiver(mReceiver);
        // mBluetoothAdapter.disable();
         Log.d("MYLOG", "onDestroy() called");
    }

    @Override
    public void onA2DPProxyReceived (BluetoothA2dp proxy) {
        Log.d("MYLOG", "MainActivity onA2DPProxyReceived(BluetoothA2dp proxy) called");
        Method connect = getConnectMethod();
        BluetoothDevice device = findBondedDeviceByName(mBluetoothAdapter, HTC_MEDIA);

        //If either is null, just return. The errors have already been logged
        if (connect == null || device == null) {
            return;
        }

        try {
            connect.setAccessible(true);
            connect.invoke(proxy, device);
        } catch (InvocationTargetException ex) {
            Log.e("MYLOG", "Unable to invoke connect(BluetoothDevice) method on proxy. " + ex.toString());
        } catch (IllegalAccessException ex) {
            Log.e("MYLOG", "Illegal Access! " + ex.toString());
        }
    }

    /**
     * Wrapper around some reflection code to get the hidden 'connect()' method
     * @return the connect(BluetoothDevice) method, or null if it could not be found
     */
    @SuppressLint("NewApi")
    private Method getConnectMethod () {
        Log.d("MYLOG", "MainActivity getConnectMethod() called");
        try {
            return BluetoothA2dp.class.getDeclaredMethod("connect", BluetoothDevice.class);
        } catch (NoSuchMethodException ex) {
            Log.e("MYLOG", "Unable to find connect(BluetoothDevice) method in BluetoothA2dp proxy.");
            return null;
        }
    }

    /**
     * Search the set of bonded devices in the BluetoothAdapter for one that matches
     * the given name
     * @param adapter the BluetoothAdapter whose bonded devices should be queried
     * @param name the name of the device to search for
     * @return the BluetoothDevice by the given name (if found); null if it was not found
     */
    private static BluetoothDevice findBondedDeviceByName (BluetoothAdapter adapter, String name) {
        Log.d("MYLOG", "MainActivity findBondedDeviceByName(BluetoothAdapter adapter, String name) called");
        for (BluetoothDevice device : getBondedDevices(adapter)) {
            if (name.matches(device.getName())) {
                Log.v("MYLOG", String.format("Found device with name %s and address %s.", device.getName(), device.getAddress()));
                return device;
            }
        }
        Log.w("MYLOG", String.format("Unable to find device with name %s.", name));
        return null;
    }

    /**
     * Safety wrapper around BluetoothAdapter#getBondedDevices() that is guaranteed
     * to return a non-null result
     * @param adapter the BluetoothAdapter whose bonded devices should be obtained
     * @return the set of all bonded devices to the adapter; an empty set if there was an error
     */
    private static Set<BluetoothDevice> getBondedDevices (BluetoothAdapter adapter) {
        Log.d("MYLOG", "MainActivity getBondedDevices(BluetoothAdapter adapter) called");
        Set<BluetoothDevice> results = adapter.getBondedDevices();
        if (results == null) {
            results = new HashSet<BluetoothDevice>();
        }
        return results;
    }   

}

BluetoothA2DPRequester类在这里:

@SuppressLint("NewApi")
public class BluetoothA2DPRequester implements BluetoothProfile.ServiceListener {
    private Callback mCallback;

    /**
     * Creates a new instance of an A2DP Proxy requester with the
     * callback that should receive the proxy once it is acquired
     * @param callback the callback that should receive the proxy
     */
    public BluetoothA2DPRequester(Callback callback) {
        Log.d("MYLOG", "BluetoothA2DPRequester Constructor; Action: mCallback = callback");
        mCallback = callback;
    }

    /**
     * Start an asynchronous request to acquire the A2DP proxy. The callback
     * will be notified when the proxy is acquired
     * @param c the context used to obtain the proxy
     * @param adapter the BluetoothAdapter that should receive the request for proxy
     */
    @SuppressLint("NewApi")
    public void request (Context c, BluetoothAdapter adapter) {
        Log.d("MYLOG", "BluetoothA2DPRequester request(Context c, BluetoothAdapter adapter) called");
        adapter.getProfileProxy(c, this, BluetoothProfile.A2DP);
    }

    @Override
    public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
        Log.d("MYLOG", "BluetoothA2DPRequester onServiceConnected(int i, BluetoothProfile bluetoothProfile) called");
        if (mCallback != null) {
            mCallback.onA2DPProxyReceived((BluetoothA2dp) bluetoothProfile);
        }
    }

    @Override
    public void onServiceDisconnected(int i) {
        //It's a one-off connection attempt; we don't care about the disconnection event.
    }

    public static interface Callback {
        public void onA2DPProxyReceived (BluetoothA2dp proxy);
    }
}

我从here获得的。

为什么服务连接泄露错误发生,我该如何解决?它只出现在onDestroy之后,我还没有看到应用程序崩溃(尚未)。可以安全地忽略它吗?

我的猜测:

BluetoothA2DPRequester类实现了BluetoothProfile.ServiceListener,并在此处创建此类的无名临时对象时

    public void btnA2dpStart_onClick(View view){
//      onBluetoothConnected();
        new BluetoothA2DPRequester(this).request(this, mBluetoothAdapter);
    }

我假设服务侦听器已注册到此类并导致此错误。是这样的吗?如果是,我该如何取消注册,因为服务监听器没有名称?

1 个答案:

答案 0 :(得分:1)

你需要在onDestroy()

中调用它
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,sBluetoothA2dp);