Obsługa połączenia Bluetooth w Androidzie a wiele Aktywności

0

Witajcie.

Mam problem z Bluetoothem w Androidzie. Czytałem dokumentację ze strony http://developer.android.com/guide/topics/connectivity/bluetooth.html i wiem jak to się odbywa, ale nie potrafię umieścić przykładowego kodu w mojej aplikacji, ponieważ w 1 aktywności chcę nawiązać połączenie, a w innej je obsłużyć.

Połączenie jest nawiązywane w 1 aktywności, która uruchamiana jest buttonem w Menu głównym. Obejmuje ona listę urządzeń sparowanych i tych, które da się wyszukać. Po kliknięciu na listę aplikacja ma połączyć się z wybranym urządzeniem i następuje powrót do Menu. Tam wybieram kolejną aktywność, która ma jedynie kilka TextView. Chciałbym, aby w momencie wyświetlania aktywności automatycznie były wysyłane komendy Bluetooth do połączonego urządzenia i odpowiedzi wyświetlane w TextView. Mam kilka takich aktywności. Zbiór komend wysyłanych jest stały i zależny od tego, która aktywność jest w danej chwili wyświetlana. Nie wiem gdzie wywoływać poszczególne klasy tym bardziej, że wg dokumentacji w klasie ConnectThread jest tworzony obiekt klasy ConnectedThread, a ja chciałbym to rozdzielić. Czytałem, że można stworzyć klasę rozszerzającą Application i tam tworzyć obiekt klasy ConnectThread, a w poszczególnych aktywnościach się do niego odwoływać. To rozumiem, ale nadal nie do końca zrozumiałem tworzenie tego 2 obiektu w tej klasie. Miałem tylko kilka zajęć z Javy, gdzie wątki tworzyłem poprzez new Thread(new Runnable() {tutaj metoda run itd});

1
mbar254 napisał(a):

[...]
Czytałem, że można stworzyć klasę rozszerzającą Application i tam tworzyć obiekt klasy ConnectThread, a w poszczególnych aktywnościach się do niego odwoływać. To rozumiem, ale nadal nie do końca zrozumiałem tworzenie tego 2 obiektu w tej klasie.
[...]

Dokładnie o to chodzi. Tworzysz ten obiekt jeden raz w klasie dziedziczącej po Application np. w metodzie onCreate(). Następnie przypisujesz ten obiekt do jakiegoś atrybutu (zmiennej) w tej klasie i tworzysz metodę publiczną typu ConnectThread getConnnectThread(). Potem definiujesz tę klasę w Manifeście w atrybucie name w tagu <application>.

Można ten problem rozwiązać jeszcze w inny sposób tworząc klasę typu Singleton (ręcznie lub za pomocą annotacji @Singleton jeśli korzystamy z biblioteki Google Guice), wstrzykując tę klasę do poszczególnych klas typu Activity i wykonywać w klasie typu Singleton te same operacje, co w klasie dziedziczącej po Application.

Niemniej jednak, pierwsze rozwiązanie z klasą dziedziczącą po Application jest chyba najprostsze i zalecane w większości przypadków.

0

W ConnectedThread jest używany Handler

mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();

. Mojego Handlera mam stworzyć w klasie dziedziczącej po Application? Nie do końca rozumiem jak mam tą wiadomość przechwytywać. Mam je zczytać do jakiejść zmiennej? W każdej aktywności mam kilka TextView, w któych będą wyświetlane wartości zwracane przez moduł Bluetooth. Mam też klasę Dane, gdzie są wszystkie zmienne, które mają być wyświetlone w Aktywnościach i metody set/get. Zmienna, w której będą dane z Handlera ma ustawiać mi zmienne w klasie Dane? Mógłby ktoś to wyjaśnić/ew. jakiś szkielet kodu?

1

Nie widziałem Twojego kodu, ale wydaje mi się, że połączenie Bluetooth powinno być inicjowane w metodzie onResume() w Activity, w którym nawiązujesz połączenie. Możesz ten kod wsadzić do jakiejś oddzielnej klasy.Twój handler najlepiej stwórz w osobnym pliku. Obiekt handlera możesz tworzyć w klasie dziedziczącej po Application lub w metodzie onResume() w Activity przed nawiązaniem połączenia Bluetooth.
Handler, to pętla, która odczytuje wiadomości wysyłane gdzieś w projekcie i przechwytuje je w wątku UI.

Możesz zrobić w ten sposób:

Najpierw tworzysz interfejs:

public interface GenericActivitySignalContract {
    void setMessageFromDevice(String string);
}

Tego interfejsu użyjesz w celu przekazania sygnału z Handlera do Activity.
Twoje Activity powinno implementować ten interfejs.

class MyActivity extends Activity implements GenericActivitySignalContract {
    // ...
    private void setMessageFromDevice(String string) {
        myTextView.setText("message from bluetooth device: " + string);
    }
    // ...

    @Override
    public void onResume() {
        GenericApplication.setActivityViewContract(this); // przypięcie bieżącego activity do Handlera - wyjaśnienie jest w dalszej części wypowiedzi
        GenericApplication.initializeHandler();           // inicjacja handlera - metoda jest opisana w dalszej części wypowiedzi
    }
}

W Twojej klasie handlera możesz stworzyć taką metodę:

    public void setActivityViewContract(GenericActivitySignalContract activityForUiUpdates) {
        this.activityViewContract = activityForUiUpdates;
    }

Ta metoda będzie służyła do ustawiania Activity dla Handlera. Dzięki temu zabiegowi będziemy mogli ustawiać różne klasy Activity implementujące interfejs GenericActivitySignalContract dla Handlera.

Cała klasa Handlera może wyglądać następująco:

public final class MyHandler extends Handler {

    private final static MESSAGE_ONE = "one";
    private final static MESSAGE_TWO = "two";

    public void setActivityViewContract(GenericActivitySignalContract activityForUiUpdates) {
        this.activityViewContract = activityForUiUpdates;
    }
    
    @Override
    public void handleMessage(Message msg) {
       if(msg.what == MESSAGE_ONE) {                             // nie pamiętam, który dokładnie parametr powinien posiadać dane odczytywane z urządzenia
           activityViewContract.setMessageFromDevice(msg.what);  // możesz poeksperymentować z msg.what, msg.arg1, msg.arg2, etc.
       } else if(msg.what == MESSAGE_TWO) {                      // IDE podpowie Ci, jakie metody są dostępne
           activityViewContract.setMessageFromDevice(msg.what);  // tutaj można też wywołać inną metodę z interfejsu, jeśli zostanie stworzona
       }
    }
}

Oczywiście zamiast tych if-ów możesz sobie zrobić instrukcję switch, a najlepiej HashMapę i obiekty dla każdego sygnału (wiadomości).
Można to bardziej elegancko napisać, ale nie bawiłem się w takie rzeczy dla celów tego przykładu, żeby nie zaciemniać obrazu.
Wartości w zmiennych statycznych są przykładowe. Nie wiem, jakie wiadomości wysyła Twoje urządzenie. Ten element trzeba dostosować do projektu.
Możesz też w ogóle zrezygnować z rozdzielania wiadomości na typy, jeśli obsługujesz tylko jeden typ lub jeśli chcesz za każdym razem robić to samo (np. odczytać jednego Stringa i przekazać go do jednego TextView).
Nadpisana metoda handleMessage() służy do przechwytywania wiadomości z urządzenia. Następnie za pomocą interfejsu, będziesz mógł przekazać tę wiadomość do Activity.
Nie wiem, dlaczego w przykładzie ze strony Google jest wywołanie metody obtainMessage(). Nie jest to metoda wbudowana w generyczną klasę Handler. Być może w tym przykładowym programie dane są przekazywane w inny sposób lub jest to rozwiązane inaczej.

Następnie w klasie dziedziczącej po Application (nazwijmy ją GenericApplication) tworzymy taką metodę:

class GenericApplication extends Application {
    private MyHandler mHandler;

    public initializeHandler() {
        this.mHandler= new MyHandler();
    }

    public void setActivityViewContract(GenericActivitySignalContract activity) {
        mHandler.setActivityViewContract(activity);
    }
}

Teraz po odebraniu wiadomości z urządzenia Bluetooth, w Activity w wątku UI zostanie wywołana metoda void setMessageFromDevice(String string) i w niej będziesz mógł obsłużyć odebraną wiadomość. Np. wstawić ją do TextView lub zrobić cokolwiek innego w wątku graficznym.

Pisałem to na poczekaniu. Możliwe, że gdzieś w mojej wypowiedzi są jakieś drobne błędy. W każdym razie, mam nadzieję, że to pomoże, choć jeśli piszesz na Androida od niedawna, to nie wszystko może być jasne. Zawsze pozostaje Google, StackOverflow i eksperymenty. ;)

0

Podziękowałem na PW, ale dzięki jeszcze raz. W takim razie obiekt klasy ConnectThread mam stworzyć w metodzie onResume() Activity, w którym obsługuje Bluetootha, czy w klasie dziedziczącej po Application i w Activity Bluetootha podpiąć wskaźnik na obiekt klasy ConnectThread? Nie rozumiem jeszcze tej kwestii. Mógłby ktoś pomóc?

Tutaj kod Activity, w którym obsługuje Bluetooth:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
import java.util.UUID;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class Bluetooth extends Activity 
{
    private static final String TAG = "BTD";
    private static final boolean D = true;

    // Return Intent extra
    public static String EXTRA_DEVICE_ADDRESS = "device_address";

    // Member fields
    private BluetoothAdapter mBtAdapter;
    private BluetoothDevice mBtDevice;
    private ArrayAdapter<String> mPairedDevicesArrayAdapter;
    private ArrayAdapter<String> mNewDevicesArrayAdapter;
    private ListView pairedListView, newDevicesListView;
    private String address;
    public static final int MESSAGE_READ = 2;
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
    private BluetoothSocket socket;
    private BluetoothDevice connect_device;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth);
        // Set result CANCELED incase the user backs out
        setResult(Activity.RESULT_CANCELED);

         //Initialize the button to perform device discovery
        Button scanButton = (Button) findViewById(R.id.button1);
        scanButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) 
            {
                    doDiscovery();
            }
        });

        // Initialize array adapters. One for already paired devices and
        // one for newly discovered devices
        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.simplerow);
        mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.simplerow);

        // Find and set up the ListView for paired devices
        pairedListView = (ListView) findViewById(R.id.listView1);
        pairedListView.setAdapter(mPairedDevicesArrayAdapter);
        pairedListView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                    long arg3) {
                 mBtAdapter.cancelDiscovery();

                    // Get the device MAC address, which is the last 17 chars in the View
                    String info = ((TextView) arg1).getText().toString();
                    address = info.substring(info.length() - 17);
                    int itemPosition     = arg2;
                    
                    // ListView Clicked item value
                    String  itemValue    = (String) pairedListView.getItemAtPosition(arg2);
                    Toast.makeText(getApplicationContext(),
                            "Position :"+itemPosition+"  ListItem : " +itemValue , Toast.LENGTH_LONG)
                            .show();
                    // Create the result Intent and include the MAC address
                    Intent intent = new Intent();
                    intent.putExtra(EXTRA_DEVICE_ADDRESS, address);

                    // Set result and finish this Activity
                    setResult(Activity.RESULT_OK, intent);
                    finish();
                
            }
        });

        // Find and set up the ListView for newly discovered devices
        newDevicesListView = (ListView) findViewById(R.id.listView2);
        newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
        newDevicesListView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                    long arg3) {
                     mBtAdapter.cancelDiscovery();

                    // Get the device MAC address, which is the last 17 chars in the View
                    String info = ((TextView) arg1).getText().toString();
                    String address = info.substring(info.length() - 17);
                    int itemPosition     = arg2;
                    
                    
                    // ListView Clicked item value
                    String  itemValue    = (String) newDevicesListView.getItemAtPosition(arg2);
                    Toast.makeText(getApplicationContext(),
                            "Position :"+itemPosition+"  ListItem : " +itemValue , Toast.LENGTH_LONG)
                            .show();
                    // Create the result Intent and include the MAC address
                    Intent intent = new Intent();
                    intent.putExtra(EXTRA_DEVICE_ADDRESS, address);

                    // Set result and finish this Activity
                    setResult(Activity.RESULT_OK, intent);
                    finish();
                
            }
        });

        // Register for broadcasts when a device is discovered
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);

        // Register for broadcasts when discovery has finished
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        this.registerReceiver(mReceiver, filter);

        // Get the local Bluetooth adapter
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();

        // Get a set of currently paired devices
        Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();

        // If there are paired devices, add each one to the ArrayAdapter
        if (pairedDevices.size() > 0) {
            findViewById(R.id.textView1).setVisibility(View.VISIBLE);
            for (BluetoothDevice device : pairedDevices) {
                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        } else {
            String noDevices = "no devices";
            mPairedDevicesArrayAdapter.add(noDevices);
        }
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // Make sure we're not doing discovery anymore
        if (mBtAdapter != null) {
            mBtAdapter.cancelDiscovery();
        }

        // Unregister broadcast listeners
        this.unregisterReceiver(mReceiver);
    }

    /**
     * Start device discover with the BluetoothAdapter
     */
    private void doDiscovery() {
        if (D) Log.d(TAG, "doDiscovery()");

        // Indicate scanning in the title
        setTitle("Scanning... Please wait");

        // Turn on sub-title for new devices
        findViewById(R.id.textView2).setVisibility(View.VISIBLE);

        // If we're already discovering, stop it
        if (mBtAdapter.isDiscovering()) {
            mBtAdapter.cancelDiscovery();
        }

        // Request discover from BluetoothAdapter
        mBtAdapter.startDiscovery();
    }

  
    // The BroadcastReceiver that listens for discovered devices and
    // changes the title when discovery is finished
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            // When discovery finds a device
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // If it's already paired, skip it, because it's been listed already
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
            // When discovery is finished, change the Activity title
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                setProgressBarIndeterminateVisibility(false);
                setTitle("Select device");
                if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = "none found";
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };
    
    
    
}

Póki co to wersja robocza z nie usuniętymi wszystkimi niepotrzebnymi rzeczami. 

Doczytałem trochę właśnie i wydaje mi się, że obiekt klasy ConnectThread powinienem utworzyć w klasie GenericApplication dziedzidziącej po Application, natomiast w obsłudze listy urządzeń Bluetooth, po wybraniu jakiegoś urządzenia powinien się utworzyć obiekt klasy GenericApplication z parametrem będącym urządzeniem, z którym chcę się połączyć. Mógłby ktoś to potwierdzić/nakierować mnie na inną drogę?

0

Możesz spróbować stworzyć obiekt ConnectThread w metodzie onCreate() w klasie dziedziczącej po Application (tutaj GenericApplication) i później w klasach typu Activity odwoływać się do tego obiektu.
Gdyby to nie zaskoczyło, możesz spróbować pokombinować z tworzeniem tego obiektu w metodzie onResume() w Activity.

1 użytkowników online, w tym zalogowanych: 0, gości: 1