在调用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);
}
我假设服务侦听器已注册到此类并导致此错误。是这样的吗?如果是,我该如何取消注册,因为服务监听器没有名称?
答案 0 :(得分:1)
你需要在onDestroy()
中调用它BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,sBluetoothA2dp);