Android WYKŁAD 4
Agenda Bazy danych SQLite Zestawienie mapowania w LiteORM Wyświetlanie danych w ListView Pobieranie danych z sieci Volley Zapytania GET i POST Zapytania JSON Przeglądanie obiektów JSON Wątki Handler i AsyncTask Zakonczenie
Konsultacje projektów DATY: 5.04 12.04 26.04 10.05 24.05 14.06 GODZINA: 15:00 16:30 MIEJSCE: B5-605
SQLite Silnik relacyjnych baz danych oparty na plikach Wspierane przez narzędzia ORM takie jak Hibernate czy ORMLite Idealny do składowania strukturalnych danych będących w relacjach (np. listy obiektów) Wiele narzędzi do projektowania/podglądu i edycji: DB Browser for SQLite SQLiteStudio SQLite Expert
SQLite z ORM w Android Przykład: Aplikacja do ewidencji punktów w grze Zależności (app/build.gradle): dependencies { compile filetree(dir: 'libs', include: ['*.jar']) testcompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.0' compile 'com.j256.ormlite:ormlite-android:4.48' Gradle pobierze bibliotekę automatycznie z repozytorium, skompiluje i włączy do projektu
ORMLite Model + mapowanie Modele public class Game { @DatabaseField(generatedId = true) public int id; @DatabaseField() public Date date; @DatabaseField() public int score; @DatabaseField(canBeNull = false, foreign = true, foreignautorefresh = true) public Player player; public Game() { Automatyczne generowanie ID Klucz obcy Pusty konstruktor jest wymagany! public class Player { @DatabaseField(generatedId = true) public int id; @DatabaseField(index = true) public String name; public Player(String name) { this.name = name; public Player() { public Game(Date date, int score, Player player) { this.date = date; this.score = score; this.player = player;
ORMLite Model + mapowanie Skanowanie adnotacji jest bardzo kosztowne czasowo szczególnie przy dużych bazach danych Skrypt należy uruchomić ręcznie w celu wygenerowania statycznej konfiguracji public class DatabaseConfigUtil extends OrmLiteConfigUtil { private static final Class<?>[] classes = new Class[] { Player.class, Game.class ; public static void main(string[] args) throws SQLException, IOException { writeconfigfile(new File("[SCIEZKA_DO_PROJEKTU]\\app\\src\\main\\res\\raw\\orml ite_config.txt"), classes); Plik res/raw/ormlite_config.txt zostanie utworzony automatycznie na podstawie adnotacji w klasach Czynność powtarzamy po każdej zmianie w modelu
OrmLiteSqliteOpenHelper Klasa ułatwiająca obsługę podstawowych czynności wykonywanych na bazie danych Wymaga implementacji następujących metod: Konstruktor Ustawienie nazwy bazy, wersji, pliku z konfiguracją oncreate Zdarzenie towarzyszące stworzeniu bazy danych, czyli jakie tabele stworzyć i co mapować onupgrade Zdarzenie towarzyszące aktualizacji bazy (kiedy zmienia się jej schemat). Miejsce na wykonanie migracji schematu.
OrmLiteSqliteOpenHelper - przykład Plik z bazą public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final String DATABASE_NAME = "scores.db"; private static final int DATABASE_VERSION = 2; private Dao<Game, Integer> gamedao; private Dao<Player, Integer> playerdao; Data Access Object Dao<Klasa, Index> public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION, R.raw.ormlite_config); public void oncreate(sqlitedatabase sqlitedatabase, ConnectionSource connectionsource) { try { TableUtils.createTable(connectionSource, Game.class); TableUtils.createTable(connectionSource, Player.class); Przy każdej zmianie w modelach (mapowanych klasach JAVY) zmień tą wartość o jeden! catch (SQLException e) { Log.e(DatabaseHelper.class.getName(), "Unable to create datbases", e);
OrmLiteSqliteOpenHelper przykład cd public void onupgrade(sqlitedatabase sqlitedatabase, ConnectionSource connectionsource, int oldver, int newver) { try { TableUtils.dropTable(connectionSource, Game.class, true); TableUtils.dropTable(connectionSource, Player.class, true); oncreate(sqlitedatabase, connectionsource); catch (SQLException e) { Usuń tabele ignoruj błędy Stwórz na nowo tabele Log.e(DatabaseHelper.class.getName(), "Unable to upgrade database from version " + oldver + " to new " + newver, e); public Dao<Game, Integer> getgamedao() throws SQLException { if (gamedao == null) { gamedao = getdao(game.class); return gamedao; Akcesory dla DAO (w razie potrzeby same inicjalizują obiekt) public Dao<Player, Integer> getplayerdao() throws SQLException { if (playerdao == null) { playerdao = getdao(player.class); return playerdao;
Wizualizacja wpisów z bazy LISTVIEW SQLITE ADAPTER Pojedynczy wpis Adapter łączy obiekty bazodanowe DAO z pojedynczym wpisem na ListView Adapter jest podpięty do ListView w Activity
list_item pojedynczy wpis w ListView <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TableRow android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:background="#6f6c67" > <TextView android:id="@+id/player_tv" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_margin="1px" android:layout_weight="1" android:background="#fff" android:gravity="center" android:textsize="15sp" /> Wymóg aby każdy widok zajął 1/3 miejsca dostępnego na szerokość <TextView android:id="@+id/date_tv" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:background="#fff" android:gravity="center" android:layout_margin="1px" android:textsize="15sp" /> <TextView android:id="@+id/score_tv" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:background="#fff" android:gravity="center" android:layout_margin="1px" android:textsize="15sp" /> </TableRow> </LinearLayout>
ORMLite klasa Adapter Klasa łącząca widok (ListView) z obiektami DAO public class DatabaseAdapter extends ArrayAdapter<Game> { private List<Game> records; private Dao<Game, Integer> gamedao; public DatabaseAdapter(Context context, int itemview, List objects, Dao<Game, Integer> gamedao) { super(context, itemview, objects); this.records = objects; this.gamedao = gamedao;
Adapter cd. public View getview(int position, View view, ViewGroup parent) { final Game game = records.get(position); try { gamedao.refresh(game); catch (Exception e) { e.printstacktrace(); W przypadku relacji odświeżamy również obiekty dołączone przez klucze obce Renderujemy pojedynczy widok same kontrolki if (view == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false); TextView playertv = (TextView) view.findviewbyid(r.id.player_tv); playertv.settext(game.player.name); SimpleDateFormat dateformat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); TextView datetv = (TextView) view.findviewbyid(r.id.date_tv); datetv.settext(dateformat.format(game.date)); TextView scoretv = (TextView) view.findviewbyid(r.id.score_tv); scoretv.settext(string.valueof(game.score)); Wstawiamy dane w widoku na layoucie reprezentującym jeden wpis na liście return view;
MainActivity - widok TableLayout z ListView do wyświetlania danych <TableLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tablelayout" android:layout_below="@+id/button"> <ListView android:id="@+id/scorelv" android:layout_width="match_parent" android:layout_height="wrap_content"/> </TableLayout>
MainActivity dostęp do bazy Metoda dołącza Adapter do ListView private void connecttodatabase(){ Dao<Game, Integer> gamedao = null; try { gamedao = gethelper().getgamedao(); games = gamedao.queryforall(); ListView lvscore = (ListView) findviewbyid(r.id.scorelv); lvscore.setadapter(new DatabaseAdapter(MainActivity.this, R.layout.list_item, games, gamedao)); catch (SQLException e) { e.printstacktrace(); private DatabaseHelper gethelper() { Helper do helpera if (databasehelper == null) { databasehelper = OpenHelperManager.getHelper(this,DatabaseHelper.class); return databasehelper; protected void ondestroy() { super.ondestroy(); Zwolnienie dostępu do bazy po opuszczeniu Activity if (databasehelper!= null) { OpenHelperManager.releaseHelper(); databasehelper = null;
MainActivity dodawanie rekordów public void addnewplayer(view view) { EditText playernameet = (EditText) findviewbyid(r.id.playernameet); String playername = playernameet.gettext().tostring(); int score = new Random().nextInt(100); Losowe punkty Date date = new Date(); try { final Dao<Player, Integer> playerdao = gethelper().getplayerdao(); List<Player> playerslist = playerdao.querybuilder().where().eq("name", playername).query(); Pobranie danych z widoków Szukamy gracza o podanym imieniu Player player = null; if(playerslist.size() == 0){ player = new Player(playerName); playerdao.create(player); else { player = playerslist.get(0); Jeśli go znajdziemy, to dopisujemy mu wynik Jeśli nie, to tworzymy takiego gracza final Dao<Game, Integer> gamedao = gethelper().getgamedao(); Game game = new Game(date, score, player); gamedao.create(game); refreshdatabase(); Dodajemy catch (SQLException ex){ Log.i("MainActivity", ex.getmessage());
MainActivity odświeżanie ListView private void refreshdatabase() throws SQLException{ final Dao<Game, Integer> gamedao = gethelper().getgamedao(); games = gamedao.queryforall(); ListView lvscore = (ListView) findviewbyid(r.id.scorelv); final DatabaseAdapter adapter = (DatabaseAdapter) lvscore.getadapter(); adapter.clear(); adapter.addall(games); Odświeżenie adaptera po dodaniu nowego wpisu
Efekt
Uruchamianie Android Device Monitor Lub bezpośrednio przez: <ANDROID_SDK>\android-sdk\tools\lib\monitor-x86_64\ w moim przypadku: c:\users\krzysztof\appdata\local\android\android-sdk\tools\lib\monitor-x86_64
Pobieranie bazy danych na komputer /data/data/[namespace]/databases/[nazwa.db]
SQLite DBBrowser podgląd baz danych
JSON JavaScript Object Notation Lekki format serializacji obiektów. Bardzo popularny w zastosowaniach sieciowych Zbudowany na zasadzie: "klucz" : "wartość", każdy element oddzielony przecinkami Podstawowe oznaczenia: [ ] lista, elementy oddzielone przecinkami { obiekt [{, {, { ] TEKST "name": "Krzysztof", "surname": "Bzowski", "classes": ["Programowanie w JAVA", "Systemy Wbudowane", "Inżynieria internetu"], "experiance": 5 "name": "Gorzegorz", "surname": "Smyk", "classes": ["Programowanie obiektowe"], "experiance": 5 "name": "Mateusz", "surname": "Sitko", "classes": ["Podstawy programowania", "Modelowanie wieloskalowe"], "experiance": 5 STRUKTURA
Volley Biblioteka sieciowa przeznaczona do efektywnej i szybkiej komunikacji poprzez protokół HTTP Pozwala na asynchroniczną pracę (bez blokowania GUI) Mnóstwo innych zalet: http://developer.android.com/training/volley/index.html Docelowo ma zastąpić przestarzałe klasy: HttpUrlConnection i HttpClient Uprawnienia do dostępu do sieci: AndroidManifest.xml <uses-permission android:name="android.permission.internet"/> Zależności do projektu: \app\build.gradle: dependencies { compile filetree(dir: 'libs', include: ['*.jar']) testcompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.0' compile 'com.mcxiaoke.volley:library:1.0.19' Gradle sam pobierze i skompiluje zależności!
Volley pierwsze starcie pobranie strony www public void connectbtnclick(view view) { RequestQueue queue = Volley.newRequestQueue(this); String url = "http://www.google.com"; GET, POST, PUT, DELETE StringRequest stringrequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { public void onresponse(string response) { // metoda uruchomi się asnychronicznie jeśli treść strony zostanie pobrana // zawartość (HTML) znajduje się w zmiennej response, new Response.ErrorListener() { public void onerrorresponse(volleyerror error) { // Miejsce na obsługę błędów ); queue.add(stringrequest);
Volley wysyłanie danych metodą POST RequestQueue queue = Volley.newRequestQueue(this); String url = "[ADRES_URL]"; StringRequest postrequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() { public void onresponse(string response) { // Odpowiedź serwera pozytywna, new Response.ErrorListener() { public void onerrorresponse(volleyerror error) { // Coś poszło nie tak, błąd! ) { protected Map<String,String> getparams(){ Map<String,String> params = new HashMap<String, String>(); params.put("value1", "4"); params.put("value2", "druga zmienna"); return params; ; queue.add(postrequest);
Volley pobieranie obrazków public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxwidth, int maxheight, ScaleType scaletype, Config decodeconfig, Response.ErrorListener errorlistener); RequestQueue queue = Volley.newRequestQueue(this); final ImageView mimageview; String url = "http://i.imgur.com/7spzg.png"; mimageview = (ImageView) findviewbyid(r.id.imageview); ImageRequest postrequest = new ImageRequest(url, new Response.Listener<Bitmap>() { public void onresponse(bitmap bitmap) { mimageview.setimagebitmap(bitmap); // response zawiera obraz gotowy do wstawienia, 0, 0, ImageView.ScaleType.CENTER, Bitmap.Config.RGB_565, new Response.ErrorListener() { public void onerrorresponse(volleyerror error) { // Błąd! mimageview.setimageresource(android.r.drawable.stat_notify_error); ) { ; queue.add(postrequest);
Volley wysyłanie i odbieranie obiektów JSON public JsonObjectRequest(int method, String url, JSONObject jsonrequest, Listener<JSONObject> listener, ErrorListener errorlistener) Map<String, String> jsonparams = new HashMap<>(); jsonparams.put("key1", "value1"); jsonparams.put("key2", "value2"); JSONObject postdata = new JSONObject(jsonParams); RequestQueue queue = Volley.newRequestQueue(this); String url = "[ADRES_URL]"; JsonObjectRequest postrequest = new JsonObjectRequest (Request.Method.POST, url, postdata, new Response.Listener<JSONObject>() { public void onresponse(jsonobject response) { // response zawiera odpowiedź w postaci obiektu JSON, new Response.ErrorListener() { public void onerrorresponse(volleyerror error) { // Błąd! ) { ; queue.add(postrequest);
Volley wysyłanie i odbieranie tablic obiektów JSON RequestQueue queue = Volley.newRequestQueue(this); String url = "[ADRES_URL]"; JsonArrayRequest postrequest = new JsonArrayRequest (Request.Method.POST, url, new Response.Listener<JSONArray>() { public void onresponse(jsonarray response) { // response zawiera odpowiedź w postaci listy obiektów JSON, new Response.ErrorListener() { public void onerrorresponse(volleyerror error) { // Błąd! ) { ; queue.add(postrequest);
Co zrobić z JSONem? Deserializacja Manualnie metody JSONArray i JSONObject getjsonobject(int i) pobiera i-ty element z tablicy JSON getjsonarray(string key) pobiera tablice o nazwie danej kluczem getdouble(string key) pobiera typ double o nazwie danej kluczem getboolean(string key) pobiera typ boolean o nazwie danej kluczem getint(string key) pobiera typ int o nazwie danej kluczem getstring(string key) pobiera typ String o nazwie danej kluczem Automatycznie google-gson automatyczna serializacja/deserializacja obiektów do formatu JSON app\build.gradle (biblioteka sama się pobierze i zainstaluje) dependencies { compile filetree(include: ['*.jar'], dir: 'libs') testcompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.0' compile 'com.mcxiaoke.volley:library:1.0.19' compile 'com.google.code.gson:gson:2.6.2'
GSON (de)serializacja obiektów JSON Model public class Teacher { public String name; public String surname; public List<String> classes; public int experiance; Deserializacja Obiekt typu JSONArray lub JSONObject Gson gson = new Gson(); String rawjson = response.tostring(); List<Teacher> teachers = gson.fromjson(rawjson, new TypeToken<List<Teacher>>(){.getType()); Teacher teacher = gson.fromjson(rawjson, Teacher.class); Problematyczne z niestrukturalnymi danymi! JSON: [{ "name": "Krzysztof", "surname": "Bzowski", "classes": ["Programowanie w JAVA", "Systemy Wbudowane", "Inżynieria internetu"], "experiance": 5, { "name": "Gorzegorz", "surname": "Smyk", "classes": ["Programowanie obiektowe"], "experiance": 5, { "name": "Mateusz", "surname": "Sitko", "classes": ["Podstawy programowania", "Modelowanie wieloskalowe"], "experiance": 5 ]
Po co wątki? Wszystkie metody uruchomione w Activity działają w ramach wątku GUI Czasochłonne metody (sieć, baza danych, obliczenia itd.) zablokują aplikację public void runbtn(view view) { final ProgressBar pb = (ProgressBar) findviewbyid(r.id.progressbar); for (int i = 0; i <= 100; i++) { timeconsumingwork(); pb.setprogress(i); private void timeconsumingwork() { try { Thread.sleep(100); catch (InterruptedException e) { e.printstacktrace(); ProgressBar
Sposób 1: Handler public void runbtn(view view) { final ProgressBar pb = (ProgressBar) findviewbyid(r.id.progressbar); Runnable runnable = new Runnable() { public void run() { for (int i = 0; i <= 100; i++) { final int value = i; timeconsumingwork(); pb.post(new Runnable() { public void run() { pb.setprogress(value); ); ; new Thread(runnable).start(); Czasochłonną operację umieszczamy w anonimowej klasie implementującej interfejs Runnable Progress raportujemy za pomocą metody post widoku ProgressBar do której również przesyłamy obiekt Runnable
Sposób 2: AsyncTask Klasa działająca asynchronicznie, musi dziedziczyć po AsyncTask<Params, Progress, Result> private class ProgressAsync extends AsyncTask<Void, Integer, Void> { private ProgressBar pb; public ProgressAsync(ProgressBar pb) { this.pb = pb; protected Void doinbackground(void... params) { for (int i = 0; i <= 100; i++) { timeconsumingwork(); publishprogress(i); return null; Miejsce na czasochłonne zadania protected void onprogressupdate(integer... values) { super.onprogressupdate(values); pb.setprogress(values[0]); AsyncTask pozwala na uruchomienie i nie czeka na zakończenie metody execute Miejsce na raportowanie postępu public void runbtn(view view) { ProgressBar pb = (ProgressBar) findviewbyid(r.id.progressbar); ProgressAsync pa = new ProgressAsync(pb); pa.execute();
Metody AsyncTask onpreexecute() uruchamiane z wątku GUI przed uruchomieniem doinbackground. Miejsce przygotowanie widoków np. pokazanie ProgressBar doinbackground(params...) uruchamiane w osobnym wątki zaraz po zakończeniu onpreexecute(). Miejsce na czasochłonne operacje. publishprogress(progress...) metoda służąca raportowaniu postępu wewnątrz doinbackground(). onprogressupdate(progress...), uruchamiane z wątku GUI samoczynnie po wykonaniu publishprogress(progress...). Miejsce na wizualizację postępu zadania, np. poprzez zmianę wartości widoku. onpostexecute(result), uruchamiane z wątku GUI po zakończeniu zadania. Jako parametr otrzymuje wynik działania doinbackground(). Miejsce na wyświetlenie podsumowania lub przekazanie sterowania dalej. onpostexecute onpreexecute doinbackground publishprogress onprogressupdate
To tyle Co warto poczytać: Navigation Drawer http://developer.android.com/training/implementing-navigation/nav-drawer.html Niestandardowe widoki https://github.com/wasabeef/awesome-android-ui Powiadomienia http://developer.android.com/guide/topics/ui/notifiers/notifications.html Obsługa sensorów (żyroskop, akcelerometr i inne) http://developer.android.com/guide/topics/sensors/sensors_overview.html Obsługa GPS http://javapapers.com/android/android-location-tracker-with-google-maps/ Usługi działające w tle http://developer.android.com/reference/android/app/service.html Cloud messaging powiadomienia PUSH https://developers.google.com/cloud-messaging/ Google API dostęp do Map, Kalendarza itp. https://developers.google.com/google-apps/calendar/quickstart/android Google Analytics (statystyki naszej aplikacji) https://developers.google.com/analytics/devguides/collection/android/v4/ Tworzenie gier: http://docs.unity3d.com/manual/android-gettingstarted.html https://github.com/libgdx/libgdx/ https://software.intel.com/en-us/android/blogs/2012/03/13/game-engines-for-android Umieszczenie aplikacji w Play Store http://www.android-development-tutorial.net/uploading-android-app-to-google-playstore-video-tutorial/