android real-life architecture
DESCRIPTION
Speaker: Lars Röwekamp MobileTechCon 14.3.2013 Apps mit Android 4.x zu programmieren, ist bekanntlich denkbar einfach. Dies gilt dank ActionBar, Fragments & Co. auch dann noch, wenn sie auf verschiedensten Devices visuell ansprechend sein sollen. Wie kommt es dann aber, dass so viele Anwendungen den "Real Life"-Check der Nutzer nicht bestehen und entsprechend schlecht bewertet werden? Eine ergonomisch gute App benötigt deutlich mehr als nur schöne UIs - das Zauberwort heißt "Architektur". Die Session zeigt anhand eines Beispiels klassische Anti-Patterns des Android Application Designs und hilft, sie zukünftig durch eine gut durchdachte Architektur zu vermeiden.TRANSCRIPT
Ph
oto
cre
dit:
Sa
ntiM
B .
/ F
ote
r.co
m /
CC
BY
-NC
-ND
Real-Life Architecture
@mobileLarson @_openKnowledge
Lars Röwekamp | CIO New Technologies
Real-Life Architecture Android 4+
MTC2013
Eigentlich ist ja alles ganz einfach ...
Real-Life Architecture Android 4+
MTC2013
... auch wenn es kompliziert(er) wird.
Android 4+
Real-Life Architecture Android 4+
MTC2013
Wo liegt das ...
Problem?
Es war einmal eine App ...Real-Life Architecture
MTC2013
Splash Overview Share
Preferences
Map
Es war einmal eine App ...Real-Life Architecture
MTC2013
Es war einmal eine App ...Real-Life Architecture
MTC2013
Anforderungen easy Version
‣klassische Android Anwendung‣Kunden CI und White Label‣Anbindung von (Web) Services‣Vorlieben/Einstellungen merken‣Time-to-Market
Es war einmal eine App ...Real-Life Architecture
MTC2013
Anforderungen eXtended Edition
‣Smartphone & Tablet Support‣Android 2.3 & Android 4.x Support‣Multi-Language Support‣Multi-User Support‣Localisation Support
Es war einmal eine App ...Real-Life Architecture
MTC2013
Anforderungen Directors Cut
‣Daten immer aktuell‣Daten auch offline ‣Daten auch für Dritte‣batterieschonend ‣und natürlich „Security“
... und die hatte eine ArchitekturReal-Life Architecture
MTC2013
... und die hatte eine ArchitekturReal-Life Architecture
MTC2013
Es war einmal eine App ...Real-Life Architecture
MTC2013
Let‘s go
‣klein starten‣schrittweise erweitern‣stets lauffähig und sinnvoll
‣Refactoring ist ein Zeichen von Stärke
Schritt 1: POI sendenFriend Finder App
MTC2013
MTC2013
POI senden Best Practices
‣UI und Logik trennen‣Kommunikation in Lib auslagern
Friend Finder App
Schritt 1: POI senden
MTC2013 Friend Finder App
Schritt 1: POI senden
MTC2013
POI senden Pitfalls
‣Network on Main Thread‣Strict Mode
Friend Finder App
Schritt 1: POI senden
MTC2013
Netzwerkzugriff nur via ...
Async
Friend Finder App
Schritt 1: POI senden
MTC2013
class PostToFriendFinder extends AsyncTask<Void, Integer, String> {
private PointOfInterest poi; private String note;
public PostToFriendFinder(PointOfInterest poi, String note) { ... }
// @Overrideprotected String doInBackground(Void... params) { try {
getFriendFinder().sharePointOfInterstVisit(poi, note); return "Sending point of interest visitation was
successfull."; } catch (FriendFinderException ex) {
return "Sending point of interest visitation failed."; }
}
@Overrideprotected void onProgressUpdate(Integer... values) { ... }
@Overrideprotected void onPostExecute(String result) { ... }
}
Async Async
Friend Finder App
Schritt 1: POI senden
MTC2013
// onClick handler to collect input data, create a new Point of Interest// and share it async via related service public void onClick(View view) {
float latitude = ...;float longitude = ...;String note = ...;
// create new point of interestPointOfInterest poi = new PointOfInterest(longitude, latitude, 0.00F);
// create async tasks to communicate with the cloud servicenew PostToFriendFinder(poi, note).execute();
}
Friend Finder App
Schritt 1: POI senden
Real-Life ArchitectureMTC2013
Schritt 2: Einstellungen merken
Schritt 2: Einstellungen merkenMTC2013
Einstellungen merken Best Practices
‣Einstellungen zentral verwalten‣Einstellungen gruppieren ‣UI zur Bearbeitung bereit stellen‣Settings Design Guide beachten
Friend Finder App
MTC2013
Einstellungen merken Pitfalls
‣Hierarchie von Einstellungen‣Einstellungen können sich ändern‣Zugriff auf Einstellungen an mehreren Stellen
‣Android 2.x vs. Android 4.x
Friend Finder App
Schritt 2: Einstellungen merken
MTC2013
Einstellungen via ...
Preferences
Friend Finder App
Schritt 2: Einstellungen merken
MTC2013
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!-- opens a subscreen of settings --> <PreferenceScreen android:key="button_voicemail_category_key" android:title="@string/voicemail" android:persistent="false"> <ListPreference android:key="button_voicemail_provider_key" android:title="@string/voicemail_provider" ... /> <!-- opens another nested subscreen --> <PreferenceScreen android:key="button_voicemail_setting_key" android:title="@string/voicemail_settings" android:persistent="false"> ... </PreferenceScreen> <RingtonePreference android:key="button_voicemail_ringtone_key" android:title="@string/voicemail_ringtone_title" android:ringtoneType="notification" ... /> ... </PreferenceScreen> ...</PreferenceScreen>
Friend Finder App
Schritt 2: Einstellungen merken
MTC2013
public static class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// Load the preferences from an XML resource addPreferencesFromResource(R.xml.preferences); } ...}
public class SettingsActivity extends PreferenceActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// Display the fragment as the main content. getFragmentManager().beginTransaction() .replace(android.R.id.content, new SettingsFragment()) .commit(); }}
Friend Finder App
Schritt 2: Einstellungen merken
MTC2013
public class FriendFinderApplication extends Application implements OnSharedPreferenceChangeListener {
SharedPreferences preferences; ...
@Overridepublic void onCreate() {
super.onCreate();this.preferences = PreferenceManager.getDefaultSharedPreferences(this);
// recommanded in onResume (register) / onPause (unregister)this.preferences.registerOnSharedPreferenceChangeListener(this);
// use preferences to initialize app data ...
} ...
@Overridepublic void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {// force reload of preferences and reinitialization of global
data...
}}
Friend Finder App
Schritt 2: Einstellungen merken
MTC2013 Friend Finder App
Schritt 2: Einstellungen merken
Real-Life ArchitectureMTC2013
Schritt 3: POIs abgleichen
Schritt 3: POIs abgleichenReal-Life Architecture
MTC2013
MTC2013
POIs abgleichen Best Practices
‣POIs regelmäßig abgleichen‣POIs so aktuell wie möglich halten‣POIs im Hintergrund laden
Friend Finder App
Schritt 3: POIs abgleichen
MTC2013
POIs abgleichen Pitfalls
‣regelmäßig‣aktuell‣im Hintergrund
Friend Finder App
Schritt 3: POIs abgleichen
MTC2013
Hintergrundaufgaben via ...
Service
Friend Finder App
Schritt 3: POIs abgleichen
MTC2013 Friend Finder App
Schritt 3: POIs abgleichen
MTC2013
public class UpdaterService extends Service {
private Updater updater; ...
public IBinder onBind(Intent intent) { ... }public void onCreate() { ... }public int onStartCommand(Intent intent, int flags, int startId) { .. } public void onDestroy() { ... }
private class Updater extends Thread {
public Updater() { ... }
public void run() {UpdaterService updaterService = UpdaterService.this;while (updaterService.running) {
try { ... // do some work Thread.sleep(DELAY);
} catch (InterruptedException ex) {updaterService.running = false;
}}
}}
}
Friend Finder App Starten & Stoppen? Starten & Stoppen?
Online vs. Offline? Online vs. Offline?
Schritt 3: POIs abgleichen
MTC2013 Friend Finder App
Schritt 3: POIs abgleichen
Real-Life ArchitectureMTC2013
Schritt 4: Offline-Modus
Schritt 4: Offline-ModusReal-Life Architecture
MTC2013
MTC2013
Offline Modus Best Practices
‣POIs via Online-Modus abgleichen‣POIs für Offline-Modus speichern‣POI UI aktuell halten
‣Datenzugriff kapseln ‣Datenzugriff optimieren
Friend Finder App
Schritt 4: Offline-Modus
MTC2013
Offline-Modus Pitfalls
‣Online vs. Offline ‣Read vs. Write ‣UI aktuell halten
‣Testen
Friend Finder App
Schritt 4: Offline-Modus
MTC2013
Daten verfügbar machen via ...
SQL & Adapter
Friend Finder App
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
?
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
PoiVisitations
username, name, note,longitude, ...
...
Adapter
FROM TO
username tx_username
... ...
<Row-Layout />
<ListView>
</ListView>
<Row-Layout />
<Row-Layout />
<LinearLayout>
</Linearlayout>
t_timestamp
tx_name
tx_note
res/layout/row.xmlres/layout/mylist.xml
src/MyAdapter.java src/MyDbHelper.java
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
public class PositionOverviewActivity extends Activity {
Cursor cursor;ListView listView;PositionOverviewAdapter adapter;FriendFinderData friendFinderData;
static final String[] FROM = { ... };static final int[] TO = { ... };
public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_position_overview);
// lookup list viewlistView = (ListView) findViewById(R.id.list_position_overview);
// lookup datafriendFinderData = ((FriendFinderApplication)
getApplication()).getFriendFinderData();
}...
}
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
public class PositionOverviewActivity extends Activity {
Cursor cursor;ListView listView;PositionOverviewAdapter adapter;FriendFinderData friendFinderData;
...
public void onResume() {
super.onResume(); cursor = friendFinderData.getPoiVisitations();
// start managing the cursorstartManagingCursor(cursor);
// created special adapteradapter = new PositionOverviewAdapter(this, cursor);listView.setAdapter(adapter);
} ...}
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
public class PositionOverviewActivity extends Activity {
Cursor cursor;ListView listView;PositionOverviewAdapter adapter;FriendFinderData friendFinderData;
...
public void onResume() {
super.onResume(); cursor = friendFinderData.getPoiVisitations();
// deprecated in Android 4.x startManagingCursor(cursor);
// created special adapteradapter = new PositionOverviewAdapter(this, cursor);listView.setAdapter(adapter);
} ...}
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
Schritt 4: Offline-Modus
MTC2013
Das kleine Loader 1x1
‣asynchrones Laden von Daten‣verfügbar in Activities und Fragments‣Content-Change-Monitoring der Datensource‣Reconnection zu vorheriger Position
‣CursorLoader (für ContentProvider)‣Loader oder AsyncTaskLoader
Friend Finder App
Schritt 4: Offline-Modus
MTC2013
// Activity implementing Loader Callbackspublic class MyActivity extends Activity implements LoaderManager.LoaderCallback<Cursor> {
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); ... adapter = new SimpleCursorAdapter(...) setListAdapter(adapter) getLoaderManager().initLoader(0, null, this); } ...
}
Friend Finder App
Schritt 4: Offline-Modus
MTC2013
// Activity implementing Loader Callbackspublic class MyActivity extends Activity implements LoaderManager.LoaderCallback<Cursor> {
... // loader was created and is ready to work public Loader<Cursor> onCreateLoader(int id, Bundle args){ // set content provider query URI etc. ... // access content provider return new CursorLoader(this, cpQueryUri, projection, where, whereArgs, sortOrder); } ...}
Friend Finder App
Schritt 4: Offline-Modus
MTC2013
// Activity implementing Loader Callbackspublic class MyActivity extends Activity implements LoaderManager.LoaderCallback<Cursor> {
... // loader finished loading - data is available public void onLoadFinished(Loader<Cursor> l, Cursor c){ adapter.swapCursor(c); }
// loader was reseted - its data is unavailable public void onLoaderReset(Loader<Cursor> l){ adapter.swapCursor(null); }}
Friend Finder App
Schritt 4: Offline-Modus
MTC2013
Loader und ...
‣Content Provider für Lau‣SQLite via eigenem AsyncTaskLoader<Cursor>
Friend Finder App
Schritt 4: Offline-Modus
MTC2013 Friend Finder App
Schritt 4: Offline-Modus
Real-Life ArchitectureMTC2013
Schritt 5: Mobile Intelligenz
Schritt 5: Mobile IntelligenzReal-Life Architecture
MTC2013
MTC2013
Mobile Intelligenz Best Practices
‣POIs nur senden/abfragen, wenn Internet‣POIs nur senden/abfragen, wenn Strom‣UI aktualisieren, wenn neue Daten‣...
Friend Finder App
Schritt 5: Mobile Intelligenz
MTC2013
Mobile Intelligenz Pitfalls
‣POIs im günstigsten Moment abfragen‣Umgebungsänderungen feststellen‣Zugriffe ausreichend absichern
Friend Finder App
Schritt 5: Mobile Intelligenz
MTC2013
Mobile Intelligenz via ...
BroadcastReceiver
Friend Finder App
Schritt 5: Mobile Intelligenz
MTC2013
Was sind Broadcast Receiver?
‣Publisher-Subscriber-Pattern‣Observer-Pattern
‣App / System sendet Nachricht‣Broadcast Receiver hört auf Nachricht
Friend Finder App
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
Schritt 5: Mobile Intelligenz
MTC2013
Was helfen uns die Broadcast Receiver?
‣Service starten sobald Device gebootet ist
‣auf Internet-Verfügbarkeit reagieren ‣auf Batteriestatus reagieren
‣auf neue POIs reagieren
Friend Finder App
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = BootReceiver.class.getSimpleName();
// start update service after system start automatically @Overridepublic void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, UpdaterService.class));
Log.d(TAG, "onReceived"); }
}
‣Service starten sobald Devices gebootet ist
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
<!-- AndroidManifest.xml -->
...
<receiver android:name=".BootReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter></receiver>
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
public class NetworkReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
// check if network is availableboolean isNetworkDown =
intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (isNetworkDown) {context.stopService(new Intent(context,
UpdaterService.class)); } else {
context.startService(new Intent(context, UpdaterService.class));
}}
}‣auf Internet-Verfügbarkeit reagieren
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
<!-- AndroidManifest.xml -->
...
<receiver android:name=".NetworkReceiver" > <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter></receiver>
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
...
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
public class PositionOverviewActivity extends BaseActivity {
static final String SEND_LOCATION_UPDATE_NOTIFICATION = "de.openknowledge.mtc.ff.SEND_LOCATION_UPDATE_NOTIFICATION"; ...
class PositionOverviewReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {cursor = ... .getPoiVisitations();adapter.changeCursor(cursor);
}}
}
‣auf neue POIs reagieren
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
<!-- AndroidManifest.xml -->
...
<permission android:name="de.openknowledge.mtc.ff.SEND_LOCATION_UPDATE_NOTIFICATION" android:description="@string/send_location_update_notification_permission_description" android:label="@string/send_location_update_notification_permission_label" android:permissionGroup="android.permission-group.PERSONAL_INFO" android:protectionLevel="normal" />
<permission android:name="de.openknowledge.mtc.ff.RECEIVE_LOCATION_UPDATE_NOTIFICATION" android:description="@string/receive_location_update_notification_permission_description" android:label="@string/receive_location_update_notification_permission_label" android:permissionGroup="android.permission-group.PERSONAL_INFO" android:protectionLevel="normal" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="de.openknowledge.mtc.ff.SEND_LOCATION_UPDATE_NOTIFICATION" /> <uses-permission android:name="de.openknowledge.mtc.ff.RECEIVE_LOCATION_UPDATE_NOTIFICATION" />
...
Schritt 5: Mobile Intelligenz
MTC2013 Friend Finder App
Schritt 5: Mobile Intelligenz
Real-Life ArchitectureMTC2013
Schritt 6: Externer Datenzugriff
Schritt 6: Externer DatenzugriffReal-Life Architecture
MTC2013
MTC2013
Externer Datenzugriff Best Practices
‣Content Provider zur Datenbereitstellung‣Widget / App zur Datennutzung
Friend Finder App
Schritt 6: Externer Datenzugriff
MTC2013
Externer Datenzugriff Pitfalls
‣Lesen vs. Schreiben‣Security Defaults‣Android 2.x vs. Android 4.x
Friend Finder App
Schritt 6: Externer Datenzugriff
MTC2013
Daten verfügbar machen via ...
ContentProvider
Friend Finder App
Schritt 6: Externer Datenzugriff
MTC2013 Friend Finder App
Schritt 6: Externer Datenzugriff
MTC2013 Friend Finder App
<!-- AndroidManifest.xml -->
...
<provider android:name =".PositionProvider" android:authorities ="de.openknowledge.mtc.ff.poi" />
...
Öffentlich? Öffentlich?
Lesen vs. Schreiben? Lesen vs. Schreiben?
Schritt 6: Externer Datenzugriff
MTC2013 Friend Finder App
<!-- AndroidManifest.xml -->
...
<provider android:name =".PositionProvider" android:authorities ="de.openknowledge.mtc.ff.poi" /> android:exported ="true" android:readPermission ="de.openknowledge.mtc.ff.poi.READ_DATA" ...
Schritt 6: Externer Datenzugriff
MTC2013 Friend Finder App
Schritt 6: Externer Datenzugriff
Real-Life ArchitectureMTC2013
Schritt 7: Google Maps API v2
Real-Life ArchitectureMTC2013
Schritt 7: Google Maps API v2
MTC2013
Google Maps v2 Pitfalls
‣API laden via Google Play Service‣Google Maps API Key generieren‣Manifest.xml anpassen ‣Map Fragment „bauen“‣Map Activity implementieren
‣Fehler suchen ;-)
Friend Finder App
Schritt 7: Google Maps API v2
MTC2013 Friend Finder App
Schritt 7: Google Maps API v2
Ph
oto
cre
dit:
Sa
ntiM
B .
/ F
ote
r.co
m /
CC
BY
-NC
-ND
Real-Life Architecture
@mobileLarson @_openKnowledge
Lars Röwekamp | CIO New Technologies