W oczekiwaniu na Windows 9 Preview (wg. nieoficjalnych informacji pod koniec września) drugi post analizujący przeciwnika - Androida. Z kolejnego obejrzanego szkolenia wynotowałem sobie to i owo (szczegóły poniżej). Wrażenia? Aktywności dają może i większą symetrię, ale też i duże rozdrobnienie, w Windows cykl życia obejmuje wielostronicową aplikację jako całość, w Androidzie aktywność to jakby jedna strona w tej samej lub innej aplikacji. Layouty te najpotrzebniejsze raczej są. Pisząc w XAML najczęściej używa się StackPanel i Grid w połączeniu z relatywnym podejściem (np. marginesy, padding, wyrównanie, rozciąganie). W Androidzie przy bardziej złożonych układach zagnieżdżanie liniowych layoutów w sobie może być mało wydajne, zaleca się więc jawny relatywny layout. Grida na początku nie było. Brak bindowania między kodem a XML przenosi nas w dawne czasy, ale idzie wytrzymać. Czasami daje się też zauważyć mniejszą wygodę w języku Java w stosunku do C#. Wrażenia ze studiów wciąż pozostają aktualne, aczkolwiek teraz do imitacji delegatów można używać anonimowych implementacji ze –> więc nie jest już tak źle. Trzeba pisać zwykle więcej kodu niż w C#. Nie ma nowoczesnego async, brakuje prostoty asynchronicznego WinRT API. Sam cykl życia w porównaniu do Windows z jednej strony podobny, z drugiej czasami wołanie przez system trzech metod przy starcie aplikacji wydaje się trochę sztuczne. Okno dialogowe w Androidzie już wpływa na życie aktywności, a także …. obrócenie ekranu! Nie wiem czym zostało to podyktowane, aby po obrocie odtwarzać od zera całą aktywność, może to wynika z jakiejś dawniejszej chęci oszczędzania lub braku wiary w automatyczne przeskalowanie layoutu po obrocie? Plusem może być automatyczne zapamiętywanie stanu radiobuttonów (czemu akurat ich?) pomiędzy różnymi życiami aktywności. Przekazywanie parametrów między różnymi ekranami w tej samej aplikacji bardziej skomplikowane niż w Windows (gdzie mamy po prostu nawigację z obiektem), ale za to stanowi również odpowiednik komunikacji między różnymi aplikacjami (w Windows mamy od tego np. kontrakty, rozszerzenia, taski, uruchamianie aplikacji z poziomu innej). Mechanizm ten jest w Androidzie uniwersalny dla aktywności wbudowanych i własnych. Jednak wstawienie obrazka z kamery jest trochę bardziej skomplikowane i mniej intuicyjne niż w Windows (choć idea zamiany pliku na Uri jest wspólna). Ewidentnym plusem jest natomiast możliwość użycia kamerki internetowej do emulacji kamery w emulatorze (póki co w Windows nie ma takiej możliwości). A i na koniec - Android Studio może i da się polubić (bardziej przypomina Visual Studio niż Eclipse, całkiem sporo kreatorów, kontekstowych poleceń, podpowiedzi, analiz).
Aktywności
aktywność – intro
- główna klasa UI, zwykle full-screen
- status & navigation bar (zarządzane przez system)
- anatomia
- action bar
- action overflow (przycisk menu)
- obszar layout-u (obszar, w którym umieszczamy kontrolki)
- widoki (= kontrolki)
- zawartość pochodzi z zasobów (ładowanie w nadpisanej metodzie)
- onCreateOptionsMenu (res\menu main.xml)
- onCreate - widoki (res\layout activity_main.xml)
- tłumaczenia (res\values strings.xml)
- context – this (zwykle do metod np. Toast.makeText(this, “aaa”, Toast.LENGTH_LONG))
- menu onClick
- menu XML: android:onClick(metoda), metoda(MenuItem item) w aktywności (klasie Activity, co stwarza trudności przy fragmentach), kompilator nie weryfikuje
- nadpisanie metody onOptionsItemSelected(MenuItem item), zwracamy true by zaznaczyć, że przechwyciliśmy element menu, item.getItemId(), R.id.action_other
- wyjście z aktywności: finish()
resource
- @ - odwołanie do zasobu z XML
- @id - zasób typu id
- @+id – zasób zostanie utworzony, jeśli do tej pory nie istniał
- title – dymek jako @string/nazwa (właściwość Android Studio, większa stabilność w finalnej wersji)
Android Studio
- Code –> Override methods
- Design/Text – przełączanie designera dla zasobu XML na tryb tekstowy
- żarówka/Alt + Enter –> Extract string resource
- Alt+Enter –> dodanie importu
Java
- @override – adnotacja (= atrybut) informująca kompilator, że mamy zamiar nadpisać metodę (zostanie sprawdzone, czy taka metoda jest dostępna w hierarchii klas)
aktywności
- aplikacje w praktyce składają się z wielu aktywności (każda z nich ma swój layout oraz menu)
- w głównej aktywności opcje w menu otwierające inne aktywności (nie można wyświetlić ich bezpośrednio, odpowiada za to zawsze Package Manager, nawet jeśli obie aktywności znajdują się w tej samej aplikacji)
- często wyświetlane są ekrany innych aplikacji, by wykonać jakiegoś taska
- wszystkie aktywności muszą być wymienione w AndroidManifest.xml (nawet jak są spoza)
- <activity android:name=”.MainActivity” …/>
- <activity android:name=”.Activity2” …/>
- podczas instalacji tworzona jest tablica wejść
- wyświetlanie aktywności – metoda startActivity(pakiet, komponent)
Android Studio
- Dodawanie nowej aktywności do stworzonego już projektu (nazwa pakietu –> menu kontekstowe –> New –> Android Component)
- typy komponentów: Activity, Application, Service, Broadcast Receiver, Remote Interface
- label - nazwa na pasku akcyjnym aktywności, trzymana w manifeście aktywności
- onCreate(Bundle savedInstanceState), w wygenerowanym kodzie nie jak w głównej aktywności setContentView czy nadpisanej metody onCreateOptionsMenu (nie ma kodu ładującego layout i zasoby menu)
- tworzymy ręcznie plik zasobów layoutu (layout –> menu kontekstowe –> New –> Layout resource file), klikając na “…” w property gridzie tworzymy resource z napisem
- możliwość refaktoryzacji nazwy klucza w XML (menu kontekstowe –> Refactor –> Rename)
- ręcznie dopisujemy w kodzie aktywności setContentView
- w AndroidManifest.xml zostały dopisane nowo dodane aktywności
- do dodatkowych aktywności tworzymy ręcznie pliki z zasobami dla menu
- Podwójne kliknięcie zamyka zakładkę eksplorera
Intents – sposób dostępu do komponentów Androida
Intent intent = new Intent(this, Activity2.class);
startActivity(intent);
Java
- .class - jak typeof() w C#
Klawisz/przycisk Back - powrót do poprzedniej aktywności
finish() - zamyka bieżącą aktywność, następuje powrót do aktywności, którą ją otworzyła, przy pierwszej aktywności mamy wyjście z aplikacji (proces jednak trwa, bo jest zarządzany przez system)
Życie
- w Androidzie nie myślimy o procesie, tylko o życiu komponentów np. aktywności
- komponenty wykonują się w ramach procesu
- nie zarządzamy życiem procesu, tylko życiem komponentów
Layouty i widoki
Widok - komponent UI np. Button, TextView, EditText
ViewGroup - złożony z wielu View np. ListView, ScrollView
Layout – specjalizowany ViewGroup
- opisuje pozycjonowanie widoków View
- zwykle jest korzeniem UI
- często jest warstwą w ramach innego
- definiowanie
- zasoby (preferowane)
- kod
- pozycjonowanie
- generalized, relative
- unikamy absolutnego!
<LinearLayout …>
<TextView>
</LinearLayout>
lub
LinearLayout layout = new LinearLayout(…); setContentView(layout);
Rodzaje
- LinearLayout - horyzontalnie lub wertykalnie
- kontener: orientation=horizontal
- elementy
- layout_gravity=left (ułożenie po lewej, ułożenie samego elementu, natomiast właściwość gravity – sposób ułożenia zawartości elementu np. wyrównanie tekstu do prawej w TextView), layout_weight=1
- layout_gravity=right, layout_weight=4
- totalna waga layoutu: 1 + 4 = 5, proporcje w wypełnieniu szerokości kontenera: 20% : 80%
- layout:height/width=fill_parent/match_parent(synonim fill_parent od API 8)/wrap_content(tyle przestrzeni, ile potrzeba), w przypadku ustawienia weight ustawiamy na zero
- zagnieżdżenie większej liczby liniowych layoutów może spodowodować trudności z renderowaniem, lepiej użyć RelativeLayout
- RelativeLayout - pozycjonuje widoki relatywnie w stosunku do innych
- elementy
- id=@+id/view1, layout_alignParentTop=true, layout_alignParentLeft=true (umieszczenie w lewym górnym rogu)
- id=@+id/view2, layout_alignParentTop=true, layout_toRightOf=@id/view1 (umieszczenie u góry, z prawej strony elementu view1)
- id=@+id/view3, layout_centerHorizontal=true, layout_below=@id/view1 (umieszczenie w drugim rzędzie na całej szerokości)
- layout_marginTop=”12dp” [… –> Resources –> Dimension (values/dimens.xml)]
- layout_margin [all, left, top, right, bottom]
- elementy
- GridLayout - dodany w API Level 14 (Android 4)
- kontener: columnCount=8, rowCount=7
- element
- layout_row=2 (od zera), layout_column=5, layout_rowSpan=1, layout_columnSpan=2
Android Studio
- przy tworzeniu nowego pliku zasobu z layoutem możliwość wyboru rodzaju roota (np. RelativeLayout)
- element z przybornika przeciągać można na designer (mała precyzja) albo na drzewo layoutu po prawej
- po podwójnym kliknięciu elementu TextView na designerze możemy ustawić id oraz text, możliwość utworzenia zasobu
- nie dostarcza automatycznego bindowania między UI a kodem
//wołane w onCreate() aktywności
void setupUiEvents() {
Button button = (Button) findViewById(R.id.topSectionButton);
button.setOnClickListener(/*implementacja interfejsu View.OnClickListener - może cała aktywność*/this);
}
void handleButton1Click(Button button) {
//…
textView.setText(“aaa”);
}
@Override
public void onClick(View view) { //obsługuje wiele przycisków
Button button = (Button) view;
int id = button.getId();
switch(id)
handleButton1Click((Button) view);
}
Można lepiej.
Java nie ma delegatów jak w C#, ale można je zasymulować.
Większość przypadków obsługujemy za pomocą klas zagnieżdżonych (trzymają referencję – i instancyjny wskaźnik this - do klas je zawierających i mogą wywoływać ich metody) i anonimowych (prosta składnia do implementacji interfejsu – Java przechwytuje callbacki przy pomocy interfejsu, który w wielu przypadkach zawiera jedną metodę).
button.setOnClickListener(new View.OnClickListener() {
@override
public void onClick(View view) {
handleButton1Click((Button)view);
}
});
Cykl życia aktywności
Zarządzanie zasobami
- limity pamięci (bez stronicowania)
- oszczędność baterii
- na poziomie komponentów
- dostęp aktywności do zasobów zależy od interakcji z użytkownikiem)
- aktywność traci dostęp do procesora, jeśli użytkownik zacznie korzystać z innej aktywności
- aktywność może stracić dostęp do zasobów pamięci, jeśli użytkownik zacznie korzystać z innej aktywności
- aplikacja (z jedną lub wieloma aktywnościami) wykonuje się w ramach procesu, jeśli uruchomiony zostanie inny proces i stwierdzone zostanie, że w dotychczasowym procesie żaden komponent nie ma dostępu do zasobów, to jest on zamykany.
- historia ekranów jak stos, możliwy powrót za pomocą klawisza Back (działający poprzednio proces może zostać ponownie uruchomiony, a ostatnio używana jego aktywność jest notyfikowana o przywrócenie zasobów)
- dostęp aktywności do zasobów zależy od interakcji z użytkownikiem)
Stany aktywności
- Running (znany także jako Active lub Resumed)
- aktywność jest na foregroundzie
- pełny dostęp do zasobów
- Paused (np. częściowe przykrycie przez okno dialogowe)
- aktywność jest widoczna, ale nie jest w foregroundzie
- zasoby pamięci są zachowane
- ograniczona zdolność do wykonywania się
- brak możliwości zniszczenia
- Stopped (całościowe przysłonięcie przez inną aktywność)
- aktywność nie jest widoczna
- możliwość utraty zasobów pamięci
- ograniczona zdolność do wykonywania się
- duże prawdopodobieństwo zniszczenia
Callbacki dla stanów aktywności
- onCreate –> onStart –> onResume –> Running - uruchamianie aktywności
- Running –> onPause (co zwalniamy?) -> Paused –> onResume (co przywracamy ?) –> Running
- Running -> onPause –> Paused (szybko przy przysłonięciu inną aktywnością) –> czy jest możliwy restart
- tak: onSaveInstanceState –> onStop –> Stopped
- nie: onStop –> Stopped
- Stopped
- powrót: onRestart –> onStart –> onResume –> Running
- zniszczenie: onDestroy
@Override
protected void onStart() {
}
@Override
protected void onResume() {
}
@Override
protected void onPause() {
}
@Override
protected void onSaveInstanceState(Bundle outState) {
}
@Override
protected void onStop() {
}
@Override
protected void onDestroy() {
}
Android Studio
odpowiednik #region w VS
//<editor-fold desc=”aaa”>
…
//<editor-fold>
Uruchomienie aplikacji z główną aktywnością
- MainActivity: onCreate –> MainActivity: onStart –> MainActivity: onResume
Przełączanie aktywności
- MainActivity: onPause –> Activity2: onCreate –> Activity2: onStart –> Activity2: onResume –> MainActivity: onSaveInstanceState –> MainActivity: onStop
Powrót do poprzedniej aktywności
- Activity3: onPause –> Activity2: onStart –> Activity2: onResume –> Activity3: onStop –> Activity3: onDestroy
Wpływ orientacji ekranu urządzenia na stan aktywności
- całkowite zniszczenie aktywności (konieczność zapisania jej stanu) [running –> paused –> stopped –> destroyed]
- całkowite odtworzenie aktywności (konieczność przywrócenia stanu)
Obrót emulatora: Ctrl + F11
Jeszcze krótsze podpięcie zdarzenia
button1.setOnClickListener((view) –> { onButton1Click((Button) view); });
Ustawienie tekstu
mTextView.setText(“abc”);
Ożywienie kontrolki
button.setEnabled(true);
Java
- pole static final - imitacja const w C#
Zapamiętanie informacji o stanie przycisku
outState.putBoolean(button1EnabledState, button1.isEnabled());
Odtwarzanie stanu w onCreate (przy ponownym odtworzeniu aktywności)
if (savedInstanceState != null) {
boolean isEnabled = savedInstanceState.getBoolean(button1EnabledState, false);
…
}
Android Studio
- Alt + Enter - wygenerowanie metody z kontekstowego menu
- Tab - możliwość zmian nazw, typów w świeżo wygenerowanej metodzie
- Code –> Generate –> Getter (dla zaznaczonego pola w klasie)
Ze względu na niewiadomy czas życia należy używać getterów dla elementów ekranu:
TextView mTextView;
public TextView getTextView() {
if (mTextView == null)
mTextView = (TextView) findViewById(R.id.textView);
return mTextView;
}
Radio-buttony automatycznie pamiętają swój stan pomiędzy różnymi życiami aktywności.
Aktywności z rezultatami (własne i wbudowane np. kamera)
Emulator
- emulacja kamery za pomocą kamery internetowej
startActivityForResult - wyświetlenie aktywności i zapytanie o wynik
callback onActivityResult w aktywności wywołującej
setResult - ustawienie wyniku w aktywności wołanej
finish – zamknięcie wywołanej aktywności i wywołanie callbacku z wynikiem w aktywności wywołującej
Ten sam mechanizm dla aktywności naszych i wbudowanych
ImageView – do wyświetlania obrazka
Wywołanie aktywności zwracającej wynik:
Intent intent = new Intent(this, Activity2.class);
startActivityForResult(intent, ACTIVITY2_REQUEST_CODE /*int 1000*/);
EditText - pole tekstowe
- android:inputType (np. textPersonName, textEmailAddress)
- android:hint
Android Studio
- Ctrl + Shift + Enter - próba sensownego zakończenia linii np. ;, if () {}
Zwracanie wyniku z aktywności
Extras - wartości dodane do Intent, parametry
public static final String PERSON_NAME_EXTRA = “personNameExtra”;
…
private void Xxx(Button view) {
String personName = getEditTextValue(R.id.name_edittext);
Intent resultIntent = new Intent();
resultIntent.putExtra(PERSON_NAME_EXTRA, personName);
setResult(RESULT_OK, resultIntent);
finish();
}
Android Studio
- Code –> Override methods
Obsługa wyniku z wywołanej aktywności
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) {
switch(requestCode) {
case ACTIVITY2_REQUEST_CODE:
handleActivity2Result(resultCode, resultIntent);
break;
}
}
private void handleActivity2Result(int resultCode, Intent resultIntent) {
if (resultCode == RESULT_OK) {
String personName = resultIntent.getStringExtra(Activity2.PERSON_NAME_EXTRA);
…
}
else {
… //cancel
}
}
Naciśnięcie przycisku Back na otworzonej aktywności z rezultatem powoduje zwrócenie statusu RESULT_CANCEL.
Korzystanie z wbudowanych aktywności z rezultatem
http://developer.android.com/reference/android/provider/MediaStore.html
Przekazanie zdjęcia z kamery
PhotoHelper:
public static Uri generateTimeStampPhotoFileUri() {
Uri photoFileUri = null;
File outputDir = getPhotoDirectory();
if (outputDir != null) {
…
File photoFile = new File(outputDir, photoFileName);
photoFileUri = Uri.fromFile(photoFile);
}
return photoFileUri;
}
public static File getPhotoDirectory() {
File outputDir = null;
//karta SD
String externalStorageState = Environment.getExternalStorageState();
if (externalStorageState.equals(Environment.MEDIA_MOUNTED)) {
File pictureDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
outputDir = new File(pictureDir, “xxx”);
if (!outputDir.exists()) {
if (!outputDir.mkdirs()) {
outputDir = null;
}
}
}
return outputDir;
}
public static void addPhotoToMediaStoreAndDisplayThumbnail(String pathName, Activity activity, ImageView imageView) {
//cloujure dla callbacków
final ImageView thumbnailImageView = imageView;
final Activity thumbnailActivity = activity;
String[] filesToScan = {pathName};
MediaScannerConnection.scanFile(thumbnailActivity, filesToScan, null,
(filePath, uri) –> {
//wątek nie-UI
long id = ContentUris.parseId(uri);
ContentResolver contentResolver = thumbnailActivity.getContentResolver();
final Bitmap thumbnail = MediaStore.Images.Thumbnails.getThumbnail(
contentResolver, id, MediaStore.Images.Thumbnails.MINI_KIND, null);
thumbnailActivity.runOnUiThread(() –> {
thumbnailImageView.setImageBitmap(thumbnail);
});
});
}
Aktywność wywołująca:
void handleTakePictureButton(Button button) {
mPhotoPathUri = PhotoHelper.generateTimeStampPhotoFileUri();
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPhotoPathUri);
startActivityForResult(intent, TAKE_PICTURE_REQUEST_CODE /*1010*/);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) {
switch(requestCode) {
case TAKE_PICTURE_REQUEST_CODE:
handleTakePictureResult(resultCode, resultIntent);
break;
}
}
private void handleTakePictureResult(int resultCode, Intent resultIntent) {
if (resultCode == RESULT_OK) {
String photoPathName = mPhotoPathUri.getPath();
PhotoHelper.addPhotoToMediaStoreAndDisplayThumbnail(photoPathName, this, getThumbnailImageView());
}
else {
mPhotoPathUri = null;
… //cancel
}
}
Konfiguracja kamery w emulatorze
Android Virtual Device Manager –> Android Virtual Devices –> Edit: Front Camera i Back Camera (None, Emulated, Webcam())
Brak komentarzy:
Prześlij komentarz