Android Architecture Design

Preview:

DESCRIPTION

Android Architecture Design Speaker: Lars Röwekamp Wie heißt es doch so schön: "Es könnte alles so einfach sein - ist es aber nicht!". Dieses Gefühl kennt wahrscheinlich jeder Android-Entwickler, der schon einmal versucht hat, eine etwas komplexere Android-App zu bauen, die sowohl auf Smartphones als auch auf Tablets gleichermaßen gut funktioniert und dabei noch mit einer ansprechenden Ergonomie aufwartet. Ganz zu schweigen von den Herausforderungen, unterschiedlichste OS-Versionen zu unterstützen oder aber auch im Tunnel bzw. im Flugzeug noch sinnvolle Funktionen zu bieten. Die Session zeigt, dass all dies mit einer gut durchdachten Architektur durchaus möglich ist.

Citation preview

Pho

to c

redi

t: S

antiM

B .

/ Fot

er.c

om /

CC

BY-

NC

-ND

Real-Life Architecture

@mobileLarson @_openKnowledge

Lars Röwekamp | CIO New Technologies

Real-Life Architecture Android 4+

MTC2014

Eigentlich ist ja alles ganz einfach ...

Real-Life Architecture Android 4+

MTC2014

Eigentlich ist ja alles ganz einfach ...

Real-Life Architecture Android 4+

MTC2014

... auch wenn es kompliziert(er) wird.

Real-Life Architecture Android 4+

MTC2014

... auch wenn es kompliziert(er) wird.

Real-Life ArchitectureMTC2014

Splash Overview Share

Preferences

Map

Real-Life Architecture

Android 4+

Real-Life Architecture Android 4+

MTC2014

Real-Life Architecture Android 4+

MTC2014

Wo liegt das ...

Problem?

Es war einmal eine App ...Real-Life Architecture

MTC2014

Splash Overview Share

Preferences

Map

Es war einmal eine App ...Real-Life Architecture

MTC2014

Es war einmal eine App ...Real-Life Architecture

MTC2014

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

MTC2014

Anforderungen eXtended Edition !

‣ Smartphone & Tablet Support ‣ Android 2.3 & Android 4.x Support ‣Multi-Language Support ‣Multi-User Support ‣ Localization Support

Es war einmal eine App ...Real-Life Architecture

MTC2014

Anforderungen Directors Cut !

‣ Daten immer aktuell ‣ Daten auch offline ‣ Daten auch für Dritte ‣ batterieschonend ‣ ... und natürlich „Top Security“ ‣ ... und natürlich „Top Usabillity“

... und die hatte eine ArchitekturReal-Life Architecture

MTC2014

... und die hatte eine ArchitekturReal-Life Architecture

MTC2014

Es war einmal eine App ...Real-Life Architecture

MTC2014

Let‘s go !

‣ klein starten ‣ schrittweise erweitern ‣ stets lauffähig und sinnvoll !

‣ Refactoring ist ein Zeichen von Stärke

MTC2014 Real-Life Architecture

Schritt 1: POI senden

Schritt 1: POI sendenReal-Life Architecture

MTC2014

Schritt 1: POI sendenReal-Life Architecture

MTC2014

automatisch vs. manuell

Position / Notiz

MTC2014

POI senden Best Practices !

‣ POI via GPS erfragen ‣ POI via Adresseingabe erfragen !

‣ UI und Logik trennen ‣ Kommunikation in eigene Lib auslagern ‣ „Application not Responding“ vermeiden

Real-Life ArchitectureSchritt 1: POI senden

MTC2014

Code Diving ...

Real-Life ArchitectureSchritt 1: POI senden

MTC2014

POI senden Pitfalls !

‣ Network on Main Thread ‣ Strict Mode

Real-Life ArchitectureSchritt 1: POI senden

MTC2014

POI senden Pitfalls !

‣ Network on Main Thread ‣ Strict Mode

Real-Life ArchitectureSchritt 1: POI senden

MTC2014

Netzwerkzugriff seit 4.x nur via ...

Async

Real-Life ArchitectureSchritt 1: POI senden

MTC2014

POI senden Async !

‣ Handler & Threads ‣ AsyncTask & „nix“ ‣ IntentService & BroadcastReceiver

Real-Life ArchitectureSchritt 1: POI senden

MTC2014class  PostToFriendFinder  extends  AsyncTask<Void,  Integer,  String>  {  !        //  Invoked  on  the  background  thread  immediately  after  onPreExecute()          //  finishes  executing.  Performs  background  computation  that  can  take            //  a  long  time.  The  parameters  of  the  async  task  are  passed  to  this            //  step.                  //  @Override          protected  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.";            }          }                @Override          protected  void  onProgressUpdate(Integer...  values)  {  ...  }  !        @Override          protected  void  onPostExecute(String  result)  {  ...  }  !}

Real-Life ArchitectureSchritt 1: POI senden

AsyncTasks

MTC2014class  PostToFriendFinder  extends  AsyncTask<Void,  Integer,  String>  {  !        //  Invoked  on  the  background  thread  immediately  after  onPreExecute()          //  finishes  executing.  Performs  background  computation  that  can  take            //  a  long  time.  The  parameters  of  the  async  task  are  passed  to  this            //  step.                  //  @Override          protected  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.";            }          }                @Override          protected  void  onProgressUpdate(Integer...  values)  {  ...  }  !        @Override          protected  void  onPostExecute(String  result)  {  ...  }  !}

Async

Real-Life ArchitectureSchritt 1: POI senden

POI & Note?

AsyncTasks

MTC2014class  PostToFriendFinder  extends  AsyncTask<Void,  Integer,  String>  {  !        private  PointOfInterest  poi;            private  String  note;                  public  PostToFriendFinder(PointOfInterest  poi,  String  note)  {  ...  }                //  @Override          protected  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.";            }          }                @Override          protected  void  onProgressUpdate(Integer...  values)  {  ...  }  !        @Override          protected  void  onPostExecute(String  result)  {  ...  }  !}

Real-Life ArchitectureSchritt 1: POI senden

AsyncTasks

MTC2014//  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  interest     PointOfInterest  poi  =  new  PointOfInterest(longitude,  latitude,  0.00F);    !   //  create  async  tasks  to  communicate  with  the  cloud  service     new  PostToFriendFinder(poi,  note).execute();      !}

Real-Life ArchitectureSchritt 1: POI senden

AsyncTasks

MTC2014//  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  interest     PointOfInterest  poi  =  new  PointOfInterest(longitude,  latitude,  0.00F);    !   //  create  async  tasks  to  communicate  with  the  cloud  service     new  PostToFriendFinder(poi,  note).execute();      !}

Real-Life ArchitectureSchritt 1: POI senden

AsyncTasks

MTC2014public  class  SendService  extends  IntentService  {        ...      @Override      protected  void  onHandleIntent(Intent  intent)  {  !        //  get  point  of  interest  and  note  form  intent  extras          PointOfInterest  poi  =                      (PointOfInterest)intent.getSerializableExtra(POINT_OF_INTEREST_EXTRA);          String  note  =  intent.getStringExtra(NOTE_EXTRA);                //  get  current  friend  finder  instance  (connection  to  friend  finder  WS)          FriendFinder  friendFinder  =  new  FriendFinder(getCurrentContact());                    String  result;          try  {              //  share  own  position  to  WS                friendFinder.sharePointOfInterstVisit(poi,  note);              result  =  "Sending  point  of  interest  visitation  was  successfull.";          }  catch  (FriendFinderException  e)  {              result  =  "Sending  point  of  interest  visitation  failed.";          }        //  inform  registered  receivers  via  notification            LocalBroadcastManager.getInstance(this).sendBroadcast(new  Intent(POI_SHARED));        }  }

Real-Life ArchitectureSchritt 1: POI senden

IntentService

MTC2014//  onClick  handler  to  collect  input  data,  create  an  Intent  //  and  share  it  async  via  related  intent  service    public  void  onClick(View  view)  {         float  latitude  =  ...;     float  longitude  =  ...;     String  note  =  ...;    !   //  create  new  point  of  interest     PointOfInterest  poi  =  new  PointOfInterest(longitude,  latitude,  0.00F);    !        Intent  serviceIntent  =  new  Intent(this,  SendService.class);          serviceIntent.putExtra(SendService.POINT_OF_INTEREST_EXTRA,  poi);          serviceIntent.putExtra(SendService.NOTE_EXTRA,  note);          startService(serviceIntent);  }

Real-Life ArchitectureSchritt 1: POI senden

IntentService

MTC2014//  onClick  handler  to  collect  input  data,  create  an  Intent  //  and  share  it  async  via  related  intent  service    public  void  onClick(View  view)  {         float  latitude  =  ...;     float  longitude  =  ...;     String  note  =  ...;    !   //  create  new  point  of  interest     PointOfInterest  poi  =  new  PointOfInterest(longitude,  latitude,  0.00F);    !        Intent  serviceIntent  =  new  Intent(this,  SendService.class);          serviceIntent.putExtra(SendService.POINT_OF_INTEREST_EXTRA,  poi);          serviceIntent.putExtra(SendService.NOTE_EXTRA,  note);          startService(serviceIntent);  }

Real-Life ArchitectureSchritt 1: POI senden

IntentService

MTC2014 Real-Life Architecture

Schritt 2: Einstellungen merken

Real-Life ArchitectureMTC2014

Schritt 2: Einstellungen merken

Schritt 2: Einstellungen merkenMTC2014

Einstellungen merken Best Practices !

‣ Einstellungen zentral verwalten ‣ Einstellungen gruppieren ‣ UI zur Bearbeitung bereit stellen ‣ Settings Design Guide beachten

Real-Life Architecture

Schritt 2: Einstellungen merkenMTC2014

Einstellungen merken Best Practices !

‣ Einstellungen zentral verwalten ‣ Einstellungen gruppieren ‣ UI zur Bearbeitung bereit stellen ‣ Settings Design Guide beachten

Real-Life Architecture

MTC2014

Einstellungen merken Pitfalls !

‣ Hierarchie von Einstellungen ‣ Einstellungen können sich ändern ‣ Zugriff auf Einstellungen an mehreren Stellen !

‣ Android 2.x vs. Android 4.x

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014

Einstellungen via ...

Preferences

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014<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>

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014<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>

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014public 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 Activity { ! @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(); } }

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014public class FriendFinderApplication extends Application implements OnSharedPreferenceChangeListener { ! SharedPreferences preferences; ... ! @Override public 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 ... } ... ! @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // force reload of preferences and reinitialization of global data ... } }

Real-Life ArchitectureSchritt 2: Einstellungen merken

Ok, aber wofür brauche ich dann

noch die PreferenceActivity?

MTC2014 Real-Life Architecture

Schritt 2: Einstellungen merken

MTC2014 Real-Life Architecture

Schritt 2: Einstellungen merken

MTC2014public class AllPreferenceActivity extends PreferenceActivity { ! // --- load all headers @Override public void onBuildHeaders(List<Header> target) { ! super.onBuildHeaders(target); loadHeadersFromResource(R.xml.pref_header, target); } ! // --- checks if chosen fragment is valid (added in API 19) @Override protected boolean isValidFragment(String fragmentName) { return MapsPreferenceFragment.class.getName().equals(fragmentName) || ServerPreferenceFragment.class.getName().equals(fragmentName) || UserPreferenceFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } }

Real-Life ArchitectureSchritt 2: Einstellungen merken

Wie reagiere ich auf Änderungen?

MTC2014public class FriendFinderApplication extends Application implements OnSharedPreferenceChangeListener { ...! @Override public void onSharedPreferenceChanged(SharedPreferences sharedPrefs, String key) {! // user name must not be empty if (Preferences.PREF_USER_NICK_NAME.equals(key)) { // check if changed user name forces restart of updater service // to load new friend list ... }! // update UI with new preference settings ... }

Real-Life ArchitectureSchritt 2: Einstellungen merken

Und wie verhindere ich falsche Eingaben?

MTC2014!@Overridepublic void onSharedPreferenceChanged(SharedPreferences sharedPrefs, String key) {! // user name must not be empty if (Preferences.PREF_USER_NICK_NAME.equals(key)) {! if (sharedPrefs.getString(Preferences.PREF_USER_NICK_NAME,"").equals("")) {! Editor editor = sharedPrefs.edit(); editor.putString(Preferences.PREF_USER_NICK_NAME, DEFAULT_USER_NAME); editor.commit(); return; }! }! // check if changed preferences forces restart of updater service ... }

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014

Code Diving ...

Real-Life ArchitectureSchritt 2: Einstellungen merken

MTC2014 Real-Life Architecture

Schritt 3: POIs abgleichen

Real-Life ArchitectureMTC2014

Schritt 3: POIs abgleichen

Schritt 3: POIs abgleichenReal-Life Architecture

MTC2014

MTC2014

POIs abgleichen Best Practices !

‣ POIs regelmäßig abgleichen ‣ POIs so aktuell wie möglich halten ‣ POIs im Hintergrund laden

Real-Life ArchitectureSchritt 3: POIs abgleichen

MTC2014

POIs abgleichen Pitfalls !

‣ regelmäßig ‣ aktuell ‣ im Hintergrund

Real-Life ArchitectureSchritt 3: POIs abgleichen

MTC2014

Hintergrundaufgaben via ...

Service

Real-Life ArchitectureSchritt 3: POIs abgleichen

MTC2014 Real-Life Architecture

Schritt 3: POIs abgleichen

MTC2014public 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; } } ! } } }

Real-Life ArchitectureSchritt 3: POIs abgleichen

MTC2014public 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; } } ! } } }

Real-Life Architecture Starten & Stoppen?

Online vs. Offline?

Schritt 3: POIs abgleichen

MTC2014public class FriendFinderApplication extends Application { ... /** check is updater service is running with the help of the * <code>RunningServiceInfo</code> of the <code>ActivityManager</code> */ public boolean isUpdaterServiceRunning() { ! String serviceName; ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { serviceName = service.service.getClassName(); ! // is the requested service running? if(UpdaterService.class.getName().equals(serviceName)) { return true; } } return false; } !}

Real-Life ArchitectureSchritt 3: POIs abgleichen

MTC2014

Code Diving ...

Real-Life ArchitectureSchritt 3: POIs abgleichen

MTC2014 Real-Life Architecture

Schritt 4: Offline-Modus

Real-Life ArchitectureMTC2014

Schritt 4: Offline-Modus

Schritt 4: Offline-ModusReal-Life Architecture

MTC2014

MTC2014

Offline Modus Best Practices !

‣ POIs via Online-Modus abgleichen ‣ POIs für Offline-Modus speichern ‣ POI UI aktuell halten !

‣ Datenzugriff kapseln ‣ Datenzugriff optimieren

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014

Offline-Modus Pitfalls !

‣ Online vs. Offline ‣ Read vs. Write ‣ UI aktuell halten !

‣ Testen

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014

Daten verfügbar machen via ...

SQL & Adapter

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014 Real-Life Architecture

?

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

PoiVisitations

username, name,

...

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

MTC2014 Real-Life Architecture

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 view listView = (ListView) findViewById(R.id.list_position_overview); ! // lookup data friendFinderData = ((FriendFinderApplication) getApplication()) .getFriendFinderData(); } ... }

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

public class PositionOverviewActivity extends Activity { ! Cursor cursor; ListView listView; PositionOverviewAdapter adapter; FriendFinderData friendFinderData; ! ... ! public void onResume() { super.onResume(); cursor = friendFinderData.getPoiVisitations(); // start managing the cursor startManagingCursor(cursor); // created special adapter adapter = new PositionOverviewAdapter(this, cursor); listView.setAdapter(adapter); } ... }

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

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 adapter adapter = new PositionOverviewAdapter(this, cursor); listView.setAdapter(adapter); } ... }

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

Schritt 4: Offline-Modus

MTC2014 Real-Life Architecture

Schritt 4: Offline-Modus

MTC2014

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

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014// Activity implementing Loader Callbacks public 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); } ... !}

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014// Activity implementing Loader Callbacks public 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); } ... }

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014// Activity implementing Loader Callbacks public 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); } }

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014

Loader und ... !

‣ Content Provider für Lau ‣ SQLite via eigenem AsyncTaskLoader<Cursor>

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014

Und wie kann ich die DB Daten sehen ... !

‣ DB liegt unter

/data/data/[mypackage]/databases/[myapp].db !

‣ DB Copy auf den Rechner ‣ SQLiteManager PlugIn für Eclipse

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014

Code Diving ...

Real-Life ArchitectureSchritt 4: Offline-Modus

MTC2014 Real-Life Architecture

Schritt 5: Mobile Intelligenz

Real-Life ArchitectureMTC2014

Schritt 5: Mobile Intelligenz

Schritt 5: Mobile IntelligenzReal-Life Architecture

MTC2014

MTC2014

Mobile Intelligenz Best Practices !

‣ POIs nur senden/abfragen, wenn Internet ‣ POIs nur senden/abfragen, wenn Strom ‣ UI aktualisieren, wenn neue Daten ‣ ...

Real-Life ArchitectureSchritt 5: Mobile Intelligenz

MTC2014

Mobile Intelligenz Pitfalls !

‣ POIs im günstigsten Moment abfragen ‣ Umgebungsänderungen feststellen ‣ Zugriffe ausreichend absichern

Real-Life ArchitectureSchritt 5: Mobile Intelligenz

MTC2014

Mobile Intelligenz via ...

Broadcast Receiver

Real-Life ArchitectureSchritt 5: Mobile Intelligenz

MTC2014

Was sind Broadcast Receiver? !

‣ Publisher-Subscriber-Pattern ‣ Observer-Pattern !

‣ App / System sendet Nachricht ‣ Broadcast Receiver hört auf Nachricht

Real-Life ArchitectureSchritt 5: Mobile Intelligenz

MTC2014 Real-Life Architecture

Schritt 5: Mobile Intelligenz

MTC2014

Was helfen uns die Broadcast Receiver? !

‣ Service starten sobald Device gebootet ist !

‣ auf Internet-Verfügbarkeit reagieren ‣ auf Batteriestatus reagieren !

‣ auf neue POIs reagieren

Real-Life ArchitectureSchritt 5: Mobile Intelligenz

MTC2014 Real-Life Architecture

public class BootReceiver extends BroadcastReceiver { private static final String TAG = BootReceiver.class.getSimpleName(); ! // start update service after system start automatically @Override public 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

MTC2014 Real-Life Architecture

<!-- 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

MTC2014 Real-Life Architecture

public class NetworkReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { // check if network is available boolean 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

MTC2014 Real-Life Architecture

<!-- 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

MTC2014 Real-Life Architecture

public class PositionOverviewActivity extends Activity { ... ! class NewLocationReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { LoaderManager lm = getLoaderManager(); lm.restartLoader(0, null, PositionOverviewActivity.this); } } }

‣ auf neue POIs reagieren

Schritt 5: Mobile Intelligenz

MTC2014 Real-Life Architecture

public class PositionOverviewActivity extends Activity { ... ! @Override public void onResume() { super.onResume(); getActivity().registerReceiver(newLocationReceiver, new IntentFilter(UpdaterService.NEW_LOCATION_INTENT)); } ! @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(newLocationReceiver); } }

‣ auf neue POIs reagieren

Schritt 5: Mobile Intelligenz

MTC2014

Code Diving ...

Real-Life ArchitectureSchritt 5: Mobile Intelligenz

MTC2014 Real-Life Architecture

Schritt 6: Externer Datenzugriff

Real-Life ArchitectureMTC2014

Schritt 6: Externer Datenzugriff

Schritt 6: Externer DatenzugriffReal-Life Architecture

MTC2014

MTC2014

Externer Datenzugriff Best Practices !

‣ Content Provider zur Datenbereitstellung ‣Widget / App zur Datennutzung

Real-Life ArchitectureSchritt 6: Externer Datenzugriff

MTC2014

Externer Datenzugriff Pitfalls !

‣ Lesen vs. Schreiben ‣ Security Defaults ‣ Android 2.x vs. Android 4.x

Real-Life ArchitectureSchritt 6: Externer Datenzugriff

MTC2014

Daten verfügbar machen via ...

Content Provider

Real-Life ArchitectureSchritt 6: Externer Datenzugriff

MTC2014 Real-Life Architecture

Schritt 6: Externer Datenzugriff

MTC2014 Real-Life Architecture

<!-- AndroidManifest.xml --> !... ! <provider android:name =".PositionProvider" android:authorities ="de.openknowledge.mtc.ff.poi" /> !...

Schritt 6: Externer Datenzugriff

MTC2014 Real-Life Architecture

<!-- AndroidManifest.xml --> !... ! <provider android:name =".PositionProvider" android:authorities ="de.openknowledge.mtc.ff.poi" /> !...

Öffentlich?

Lesen vs. Schreiben?

Schritt 6: Externer Datenzugriff

MTC2014 Real-Life Architecture

<!-- 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

MTC2014

Code Diving ...

Real-Life ArchitectureSchritt 6: Externer Datenzugriff

MTC2014 Real-Life Architecture

Schritt 7: Standorte anzeigen

Real-Life ArchitectureMTC2014

Schritt 7: Standorte anzeigen

MTC2014

Standorte anzeigen Best Practices !

‣ Interaktive Karte ‣ eigenen Standort hervorheben ‣ Detailinfos beim „Anklicken“ ‣ User Controlls

Real-Life ArchitectureSchritt 7: Standorte anzeigen

MTC2014

Standorte anzeigen Pitfalls !

‣ Google Maps v2 ‣ Google Play Service ‣ Emulator

Real-Life ArchitectureSchritt 7: Standorte anzeigen

MTC2014

Standorte anzeigen via ...

Google Maps API v2

Real-Life ArchitectureSchritt 7: Standorte anzeigen

Real-Life ArchitectureMTC2014

Schritt 7: Google Maps API v2

MTC2014

Google Maps v2 Pitfalls !

‣ API laden via Google Play Service ‣ Google Maps API Key generieren ‣Manifest.xml anpassen ‣Map Fragment „bauen“ ‣Map Activity implementieren

Schritt 7: Google Maps API v2Real-Life Architecture

MTC2014

Google Maps v2 Pitfalls !

‣ API laden via Google Play Service ‣ Google Maps API Key generieren ‣Manifest.xml anpassen ‣Map Fragment „bauen“ ‣Map Activity implementieren ‣ ... sie sehen, sie sehen nix (im Emulator)

Schritt 7: Google Maps API v2Real-Life Architecture

MTC2014

Google Maps v2 Pitfalls !

‣ API laden via Google Play Service ‣ Google Maps API Key generieren ‣Manifest.xml anpassen ‣Map Fragment „bauen“ ‣Map Activity implementieren ‣ ... sie sehen, sie sehen nix (im Emulator)

Schritt 7: Google Maps API v2Real-Life Architecture

adb -e install com.google.android.gms.apk

adb -e install com.android.vending.apk

MTC2014

Code Diving ...

Schritt 7: Google Maps API v2Real-Life Architecture

Pho

to c

redi

t: S

antiM

B .

/ Fot

er.c

om /

CC

BY-

NC

-ND

@mobileLarson @_openKnowledge

Lars Röwekamp | CIO New Technologies

Real-Life Architecture

Recommended