Następny odcinek z cyklu poświęconemu systemowi Android. Po obejrzeniu kolejnego szkolenia mającego być wprowadzeniem, tym razem starszego wychwyciłem kilka rzeczy dotąd nieporuszanych jak architektura systemu i API, ustawienia aplikacji, obsługa menu i okien dialogowych oraz przygotowanie aplikacji do publikacji w sklepie.
Oczywiście nie mogę obejść się bez odrobiny publicystyki, tak więc i dziś zanotuję kilka swoich subiektywnych wrażeń oraz porównań.
Preferencje - taki odpowiednik ApplicationData.LocalSettings plus panel ustawień jak dla mnie, przy czym przynajmniej na razie nie zauważyłem odpowiednika RoamingSettings. Jak zwykle trochę przekombinowana obsługa, choć kreator i automatyczny interfejs dla różnego rodzaju ustawień może być ciekawą opcją.
Menu - obsługa kontekstowego menu jak zwykle przekombinowana, nie rozumiem upodobania do nadpisywania metod w aktywności, brakuje mi swobody jaką dają kontrolki XAML czy HTML5. Pomysł na wybieralne menu czy z checkami jakiś taki też nadmiarowy, patrząc tak wizualnie
Okna dialogowe - tutaj znowu się powtórzę, po kij nadpisywać metody w aktywności, definiowanie callbacków i akcji nawet zgrabne, choć podejście z async w WinRT bardziej mi się podoba. Dialog progress? Czy nie jest to wizualny przerost formy nad treścią, zwłaszcza jeśli nie można go anulować ? IMHO wystarczyłby sam pasek czy kółko, ale Android ma swoje wytyczne. Obsługa znów mało banalna, coś notyfikuj, coś nadpisz, odpal coś jawnie w innym wątku itp. Niestandardowy dialog ma choćby odpowiednika w ContentDialog w WP 8.1. W Android mimo deklaratywnego XML i tak nadal mamy w cenie imperatywne sterowanie elementami UI, czyli odszukanie ich z poziomu code-behind, by coś na nich wykonać.
Publikacja - w szczegółach różna, ale jednak coś podobnego. Podpisywanie, pakowanie, sklep brzmi dość znajomo. Przy czym tu jakby sami bawimy się w obfuskację, w Windows Phone Microsoft zabezpiecza nasze app-ki za nas, w Windows nie ma zabezpieczeń, ale mają być wprowadzone.
Zaznaczam że to starsze szkolenie, więc jak dotrę do nowszych materiałów to być może zweryfikuję, to co tutaj napisałem. Ale czasami od czegoś trzeba zacząć.
Architektura
- Jądro Linux
- abstrakcja sprzętu, sterowniki
- Biblioteki
- Standardowa biblioteka C
- Biblioteki multimedialne
- Menadżery surfowania (m.in dostęp do wyświetlacza)
- LibWebCore (WebKit)
- SGL (2D)
- Biblioteki 3D (OpenGL, ES)
- FreeType (renderowanie czcionek)
- SQLLite
- Android Runtime
- podstawowe biblioteki zawierające większość standardowych bibliotek Java
- nie używa JVM, a Dalvik VM
- wykonuje pliki .dex
- każda aplikacja uruchamia swoją własną VM
- Framework dla aplikacji
- pełny zbiór serwisów napisanych w Javie
- widoki i okna
- zasoby, dostawcy zawartości
- serwisy telefonu / API
- notyfikacje
- cykl życia
- Aplikacje
- jeden lub więcej
- aktywności
- serwisów
- broadcast receiver-ów
- content provider-ów (odpytywanie zbiorów danych np. SQL)
- jeden lub więcej
Inne rodzaje API
- serwisy lokalizacji
- serwisy telefoniczne
- audio & video
- przeglądarka webowa
- google maps
Bezpieczeństwo i uprawnienia
- Każda aplikacja wykonuje się ze swoim własnym Linux user ID
- Wszystkie aplikacje wykonują się domyślnie w piaskownicy
- Uprawnienia
- Aplikacje deklarują w manifeście, czego potrzebują (w tym możliwe definicje własnych)
- W czasie instalacji użytkownik decyduje o zgodzie na wymagane uprawnienia
- Aplikacje są podpisywane lokalnie (pliki APK)
Instrumentacja
- możliwość określenia w manifeście
- junit
public class MainActivity extends Activity {
@InjectView
private TextView textView;
}
Layout
Layout absolutny
<AbsoluteLayout …
android:layout_width=”fill_parent” android:layout_height=”fill_parent”>
<Button android:text=”Button” android:layout_width=”wrap_content” android:layout_height=”wrap_content”
… android:layout_x=”126dip” android:layout_y=”61dip”></Button>
</AbsoluteLayout>
Table Layout, List View, Grid View, Tab View
Date/Time Picker, Dropdown (Spinner), Auto Complete, Gallery, Frame Layout, Google Map, Web View
android:textSize=”16sp”
Preferencje
Współdzielone preferencje
- Sposób na przechowywanie informacji dla aplikacji
- Dozwolone jest używanie wielu zbiorów preferencji
- Dwa poziomy
- aktywność
- aplikacja
- To coś innego niż zapisywanie stanu
- Dlaczego używać
- Małe ilości danych, które potrzebujemy zapisać
- Łatwiejsze niż zapisywanie do pliku lub bazy danych
- Ustawienia użytkownika
- Kiedy stosować
- przechowywanie danych pomiędzy wywołaniami aplikacji
- przechowywanie danych pomiędzy aktywnościami w aplikacji
- odpowiadanie na zmiany preferencji za pomocą handlerów
public class XApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
Editor editor = preferences.edit();
editor.putString(“test”, “xxx”);
editor.commit();
}
}
Nazwę własnej klasy aplikacji umieszczamy w manifeście
public class MainActivity extends Activity {
…
private OnClickListener button1Listener = new OnClickListener() {
@Override
public void onClick(View arg0) {
SharedPreferences preferences = getSharedPreferences(“default”, MODE_PRIVATE);
string value = preferences.getString(“test2”, “”);
…
}
}
…
@Override
public void onCreate(Bundle savedInstanceState) {
…
//SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
//String value = preferences.getString(“test”, “”);
…
SharedPreferences preferences = getSharedPreferences(“default”, MODE_PRIVATE / MODE_WORLD_READABLE / MODE_WORLD_WRITEABLE);
Editor editor = preferences.edit();
editor.putString(“test2”, “yyy”);
editor.commit();
}
}
Ekran ustawień
Eclipse
- New Android XML File -> Preference
- Add: CheckBoxPreference, EditTextPreference, ListPreference, Preference, PreferenceCategory, PreferenceScreen, RingtonePreference
- Attributes for EditTextPreference
- Attributes from DialogPreference
- Attributes from Preference: Key, …
<PreferenceScreen …>
<EditTextPreference android:dialogMessage=”Xxxx” android:key=”test3” android:dialogTitle=”Yyy” android:title=”Zzz”></EditTextPreference>
</PreferenceScreen>
public class MainPreferenceActivity extends PreferenceActivity {
private OnPreferenceChangeListener onPreferenceChangeListener = new OnPrefeenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference arg0, Object arg1) {
return false;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.main_preferences);
Preference p;
p.setOnPreferenceChangeListener(onPreferenceChangeListener);
}
}
Manifest
- Deklaracja MainPreferenceActivity
Inne opcje przechowywania danych
- Internal Storage
- jak isolated storage
- usuwany z aplikacją
- proste pisanie do pliku
- External Storage
- jak internal, ale może nim być karta SD lub inny nośnik dostępny dla użytkownika
- może być zamontowany przez USB
- należy sprawdzić dostępność przed użyciem
- dostępne specjalne foldery Androida
- Bazy danych
- SQLLite
- Sqllite3 tool
- kursory do odpytywania danych
Menu
Option menu
- wyświetlane po naciśnięciu sprzętowego przycisku menu
- 6 pozycji, pozycja “more”
- Zwykle link do ustawień aplikacji
- Specyficzne dla aktywności
Context menu
- dostępne po dłuższym dotknięciu elementu
- przykłady: usuwanie, kopiowanie, wycinanie, zmiana nazwy
Submenu
- tylko jeden poziom zagnieżdżenia
Pozycje z checkiem
- wszystkie pozycje menu mogą mieć checka
- tylko menu kontekstowe wyświetla checki
- mogą być pojedyncze lub grupa
- pojedynczy wybór
- multi-select
Eclipse
- New Android XML File -> Menu
- Checkable behavior
<menu …>
<group android:id=”@+id/group1”>
<item android:id=”@+id/item1”></item>
<item android:id=”@+id/item2”></item>
</group>
</menu>
public class MainActivity extends Activity {
…
@Override
public void onCreate(Bundle savedInstanceState) {
…
registerForContextMenu(button);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context_menu), menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
…
return super.onContextItemSelected(item);
}
}
<menu …>
<group …>
<item …></item>
<item …>
<menu>
<item …></item>
<item …></item>
</menu>
</item>
</group>
</menu>
Dialogi
showDialog
onCreateDialog
onPrepareDialog
dismissDialog (pozostaje w pamięci, jedynie potem onPrepareDialog)
removeDialog
Alert dialog
- Zastosowanie
- Ogólne powiadomienie
- Pytania OK/Anuluj
- Proste wybory
- 1, 2 lub 3 przyciski
- Możliwość używania list z wyborem
Progress dialog
- Dwa tryby
- Spinner (kółko)
- Horizontal (pasek)
- Bez Cancel
Custom dialog
- ten sam layout, co aktywności
- może wchodzić w interakcję z widokiem w layout
- zawsze pokazuje tytuł
- zarządzanie jak w Alert Dialog
public class MainActivity extends Activity {
final private int RESET_DIALOG = 0;
…
private OnClickListener resetButtonListener = new OnClickListener() {
@Override
public void onClick(View arg0) {
showDialog(RESET_DIALOG);
//lub dla progress dialog (drugi sposób)
dialog = ProgressDialog.show(MainActivity.this, “Waiting”, “Doing something…”);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Thread.sleep(3000);
handler.sendEmptyMessage(0)
}
});
thread.start();
}
}
protected android.app.Dialog onCreateDialog(int id) {
/*
switch(id) {
case RESET_DIALOG:
AlertDialog.Builder builder = new Builder(this);
return builder
.setMessage(“Xxx …”)
.setNegativeButton(“No”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Toast.makeText(MainActivity.this, “…”, 5).show();
}
})
.setPositiveButton(“Yes”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Toast.makeText(MainActivity.this, “…”, 5).show();
}
})
.create();
} */
/*
switch(id) {
case RESET_DIALOG:
AlertDialog.Builder builder = new Builder(this);
return builder
.setTitle(“Xxx …”)
.setNegativeButton(“No”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Toast.makeText(MainActivity.this, “…”, 5).show();
}
})
.setPositiveButton(“Yes”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
Toast.makeText(MainActivity.this, “…”, 5).show();
}
})
.setMultiChoiceItems(new CharSequence[] { “A”, “B”, “C”}, new boolean[] { false, false, false}, null)
.create();
} */
/*
switch(id) {
case RESET_DIALOG:
ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setMessage(“…”);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Thread.sleep(3000);
handler.sendEmptyMessage(0)
}
});
dialog = progressDialog;
thread.start();
return dialog;
} */
switch (id) {
case RESET_DIALOG:
dialog = new Dialog(this);
dialog.setContentView(R.layout.custom_dialog);
dialog.setTitle(“…”);
TextView textView = (TextView) dialog.findViewById(R.id.textView1);
textView.setText(“…”);
return dialog;
}
return null;
}
private Dialog dialog = null;
private Handler handler = new Handler() {
public void handleMessage (android.os.Message msg) {
dialog.dismiss();
}
}
…
}
Deployment
Podpisywanie aplikacji
- wszystkie aplikacje muszą być podpisane
- wersja debug jest podpisywana kluczem debug
- certyfikaty muszą wygasać po 22.10.2033
- własne podpisywanie (bez centrum autoryzacyjnego)
- ten sam certyfikat dla
- aktualizacji
- współdzielenia kodu
Eclipse
- Projekt w Package Explorer (menu kontekstowe)
- –> Android Tools –> Export (Un)Signed Application Package…
- -> Export: Android/Export Android Application
Własne podpisywanie
- {Program Files}\Java\{wersja}\bin\keytool -genkey -v -keystore ścieżka -alias Demo -keyalg RSA -keysize 2048 -validity 10000
- jarsigner -verbose -keystore ścieżkaCert ścieżkaPakietu Demo
- jarsigner -verify -verbose ścieżkaPakietu
- {android_sdk\tools}zipalign -v 4 ścieżkaPakietu plikPakietu
Proguard
- obfuscator
- narzędzie open source w ADT
- redukuje rozmiar kodu
- License Manager nie jest wystarczający
- trudny do konfiguracji z zewnętrznymi bibliotekami
- trudne debugowanie po zdeployowaniu
- {android_sdk}\tools\proguard\bin
Android License Manager
- tylko dla płatnych aplikacji
- urządzenie lub emulator z API level 8+
Aplikacja – LVL(License Veryfication Library) –> Market App –> Market License Server
Android SDK and AVD Manager
Third party Add-ons
- Google Inc. (dl-ssl.google.com)
- Google Market Licensing package, revision 1
Eclipse
- Kreowanie projektu z biblioteki
Manifest
<uses-permission android:name=”com.android.vending.CHECK_LICENSE” />
public class MainActivity extends Activity {
private static final byte[] SALT = new byte[] { … };
private static final String GOOGLE_KEY = “ABC”;
…
private LicenseCheckerCallback licenseCheckerCallback;
private LicenseChecker licenseChecker;
…
private OnClickListener helpButtonListener = new OnClickListener() {
@Override
public void onClick(View arg0) {
licenseChecker.checkAccess(licenseCheckerCallback);
}
}
…
@Override
public void onCreate(Bundle savedInstanceState) {
…
licenseCheckerCallback = new LicenseCheckerCallbackImpl();
String deviceId = Secure.getString(this.getContentResolver(), Secure.ANDROID_ID);
licenseChecker = new LicenseChecker(getApplicationContext(),
new ServerManagedPolicy(getApplicationContext(), new AESObfuscator(SALT, getPackageName(), deviceId),
GOOGLE_KEY);
}
…
private class LicenseCheckerCallbackImpl implements LicenseCheckerCallback {
@Override
public void allow() {
if (isFinishing())
return;
ShowMessage(“…”);
}
@Override
public void dontAllow() {
if (isFinishing())
return;
ShowMessage(“…”);
}
@Override
public void applicationError(ApplicationErrorCode errorCode) {
}
}
private void ShowMessage(String message) {
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, message, 5).show();
}
}
}
}
Brak komentarzy:
Prześlij komentarz