-
+
+ Imparare l'architettura, le API, e i tool di sviluppo su Android. +
+ + Acquisire pattern di programmazione adatti a dispositivi resource constrained in java. +
+ + Realizzare un'applicazione completa. +
+
diff --git a/.nojekyll b/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
deleted file mode 100644
index 1cfbddb..0000000
--- a/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
+ Imparare l'architettura, le API, e i tool di sviluppo su Android. +
+ Acquisire pattern di programmazione adatti a dispositivi resource constrained in java. +
+ Realizzare un'applicazione completa. +
+
+ 
Un'app android é costituita da componenti attivi (lousely coupled) e da un insieme di risorse.
Questi pezzi disaccoppiati sono uniti insieme a runtime in un'unica Application (Context) da un file di configurazione manifest.
+
Una caratteristica fondamentale di Android é che un app puó attivare direttamente un componente di un'altra applicazione, inviando messaggi intent o richiamando specifiche uri. Il manifest specifica quali messaggi un componente é in grado di ricevere tramite specifici filtri.
+
+ + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="corso.sample" + android:versionCode="1" + android:versionName="1.0" > + <uses-sdk + android:minSdkVersion="9" + android:targetSdkVersion="17"/> + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name"> + </application> + </manifest>+
+ Un'activity rappresenta una schermata (di solito copre l'intera finestra) con cui l'utente puó interagire per + realizzare un'azione. +
+
+
+ package com.corso.sample.activity;
+ import com.corso.sample.R;
+ import android.app.Activity;
+
+ public class ExampleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // The activity is being created.
+ setContentView(R.layout.my_layout);
+ }
+ }
+ + <application> + <activity android:name=".activity.ExampleActivity" + android:label="@string/app_name" + android:icon="@drawable/ic_launcher"> + <!-- ..... --> + </activity> + </application>+
+ <?xml version="1.0" encoding="utf-8"?> + <!-- res/layout/mylayout.xml --> + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/hello"/> + </LinearLayout>+
+ <resources> + <string name="app_name">Sample</string> + <string name="hello">Hello world!/string> + </resources>+
Dobbiamo poter recuperare le viste nel codice
++ <TextView + android:id="@+id/tv_hello" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/hello"/> ++
+ TextView mHello;
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.my_layout);
+ mHelloOut = (TextView)findViewById(R.id.tv_hello);
+ }
+
+ Alcune viste hanno associato un comportamento
+
+ View mInteractive;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.my_layout);
+ mInteractive = findViewById(R.id.btn);
+ mInteractive.setOnClickListener(new OnClickListener{
+ @Override
+ public void onClick(View v){
+ //do something
+ }
+ });
+ }
+
+ Alcune viste contengono dati
+
+ //....
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ //....
+ mInteractive = (EditText)findViewById(R.id.btn);
+ mReplaceButton.setOnClickListener(this);
+ }
+ @Override
+ public void onClick(View v){
+ if(v.getId()==mReplaceButton.getId()){
+ mSavedContent = replaceContent(mSavedContent);
+ }
+ }
+ //...
+
+ private String replaceContent(String content){
+ Editable content =mInteractive.getText();
+ mInteractive.setText(content);
+ return content.toString();
+ }
+
+ Il sistema operativo puó interrompere il nostro processo
+
+ private final static String SAVED_KEY="SAVED_KEY";
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if(savedInstanceState!=null){
+ //activity restarted
+ mState = savedInstanceState.getBoolean(SAVED_KEY);
+ }else{
+ mState = initializeState();
+ }
+ }
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState){/*or here after onStart()*/}
+ @Override
+ protected void onSaveInstanceState(Bundle outState){
+ outState.putBoolean(SAVED_KEY,mState)
+ }
+ Creare link tra activity
+
+ private void launch(boolean implicit){
+ final Intent intent;
+ if(implicit){
+ intent = new Intent(this,AnotherActivityInMyPackage.class);
+ }else{
+ intent = new Intent("an.explicit.action");
+ }
+ intent.setData(Uri.parse("mydata://somedata/1000"));
+ intent.putExtra("A_KEY",1000);
+ this.startActivity(intent);
+ }
+ Delegare l'esecuzione di un task
++ private final static int MY_REQUEST_CODE = 1; + //... + this.startActivityForResult(intent,MY_REQUEST_CODE);+
Creare link tra activity
+
+ @Override
+ public void onCreate(Bundle savedInstanceState){
+ handle(getIntent());
+ }
+ @Override
+ protected void onNewIntent(Intent intent){
+ setIntent(intent); //Optionally
+ handle(intent);
+ }
+ private void handle(Intent intent){}
+ Delegare l'esecuzione di un task
+
+ setResult(RESULT_OK,new Intent().putExtra("content",response));
+ //data is optional
+ //setResult(int x); RESULT_OK RESULT_CANCELED RESULT_FIRS_USER
+ finish();
+ Ricevere il risultato
+
+ private final static int MY_REQUEST_CODE = 1;
+ //...
+ @Override
+ protected void onActivityResult (int requestCode, int resultCode, Intent data){
+ if(requestCode==MY_REQUEST_CODE){
+ if(resultCode==RESULT_OK){
+ //do something with data may be null
+ }else{
+ //do something when user refused to complete action
+ }
+ }else{
+ // not my business another request
+ }
+ }
+ Alcune viste sono collegate a modelli complessi:
collection di un numero variabile di item, che a loro volta sono rappresentati da specifici insiemi di viste.
+
Serve un ulteriore livello di indirezione:
+ introduciamo gli Adapter.
+
+ public class MyAdapter extends BaseAdapter{
+ private final LayoutInflater mInflaterService;
+ //...
+ @Override
+ public View getView(int position,View convertView,View theList){
+ View v = mInflaterService.inflate(R.layout.item_layout,theList,false);
+ presentTheItem(getItem(position));
+ return v;
+ }
+ }
+ + ListView lv = findViewById(R.id.list); + mAdapter = new MyAdapter((Context)this); + lv.setAdapter(adapter); + lv.setOnItemClickListener(/*the listener*/);+
+ private void updateData(){
+ doUpdtateData();
+ mAdapter.notifyDataSetChanged();
+ }
+
+
+ public class MyFragment extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){
+ View v = inflater.inflate(R.layout.my_fragment,container,false);
+ // ... setup
+ return v;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState){
+ // ...
+ }
+ // ...
+ }
+
+ + <!-- in the layout for the activity --> + <!-- .... --> + <fragment class="com.example.MyFragment" + android:id="@+id/MyFragmentId" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> ++
+ public class MyActivity extends FragmentActivity{
+ @Override
+ public void onCreate(Bundle savedInstanceState){
+ setContentView(R.layout.my_activity);
+ //...
+ FragmentManager manager = getSupportFragmentManager();
+ MyFragment f = (MyFragment)manager.findFragmentById(R.id.MyFragmentId);
+
+ }
+ }
+ + // in the activity + + private final static String TRANSACTION_TAG = "A TAG"; + + //... + + MyFragment f = new MyFragment(); + + FragmentManager m = getSupportFragmentManager(); + m.beginTransaction() + .replace(R.id.viewgroup,f,TRANSACTION_TAG) + .addToBackStack(null) + .commit(); + + //.. + + m.findFragmentByTag(TRANSACTION_TAG);
setRetainInstance(true);
Internal private storage
++ String FILENAME = "myprivatefile"; + FileInputStream in =context.openFileInput(FILENAME,Context.MODE_PRIVATE); + //... + FileOutputStream out = context.openFileOutput(FILENAME,Context.MODE_PRIVATE);+
Caching
++ File f =context.getCacheDir(); // i file possono essere rimossi dal sistema
External storage
++ // controllare se abbiamo un sd + Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + // aprire la nostra root sull'sd esterna + File f =Environment.getExternalFilesDir(); // /Android/data/mio.package/files/ + File f =Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC); //shared+
Specifiche per activity o globali (con nome)
+Salvate come file xml nello storage interno
+
+ SharedPreferences localPrefs = activity.getPreferences();
+
+ SharedPreferences prefs = context.getSharedPreferences("PREFS_FILE",MODE_PRIVATE);
+ boolean myOptionalPref = context.getBoolean("onOffPreference",/*default*/false);
+ String myOptionalText = context.getString("textPreference",/*default*/"fallback");
+ //...
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean("key",false)
+ .putInt("intKey",69)
+ .commit():
+
+ SQLite é un database embedded (nessuna connessione tramite jdbc) gira nel nostro + stesso processo
+Non ha tutte le funzionalitá di un database full fledged
+Il db é un singolo file
+Non é tipizzato: una colonna puó contenere qualsiasi tipo
+Manca di alcune features importanti come gli outer join e i foreign constraints + sono disabilitati di default
+Pessima concorrenza
+Nonostante tutto ottimo per applicazioni embedded
+Creiamo un db tramite SQliteOpenHelper
+
+ public class TodoOpenHelper extends SqliteOpenHelper{
+ TodoOpenHelper(Context context){
+ super(context,DATABASE_FILE,/*CursorFactory*/null,DATABASE_INT_VERSION);
+ }
+
+ public void onCreate(SQLiteDatabase db) {
+ db.execSql(CREATE_TABLE_SQL);
+ }
+
+ public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion) {
+ db.execSql(ALTER_TABLE_SQL);
+ }
+ //....
+ }
+
+
+ TodoHelper helper = ...;
+ SQLiteDatabase db =helper.getWritableDatabase();
+
+ ContentValues values = new ContentValues(); // riga da inserire
+ long newid=db.insert("table_name",null,values);
+ int numUpdates=db.update("table_name",null,values,"_id = ?",new String[]{"1"});
+ int numDeletes=db.delete("table_name","_id = ?",new String[]{"2"});
+ Cursor cursor =db.query("table_name",
+ new String[]{"_id","text"},"_id = ?",new String[]{"3"},
+ /*groupby*/null,/*having*/null,/*orderby*/null,/*limit*/null);
+
+ int count = cursor.getCount();
+ if(cursor.moveToFirst()){
+ String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
+ }
+
+ cursor.close();
+
+ developers.android.com dice "you don't need content providers if you don't want to + share content with other apps but..."
+I content provider implementano un'architettura client/server
+Client: ContentResolver <-> Server: ContentProvider
+Un CP risponde ad una o piú AUTHORITY
+Si comunica con un CP tramite uri nella forma content://authority/path
+I CP possono implementare schemi complessi di permessi
+API ottimizzata per dati tabulari (SQL) o file, ma nessun altro enforcement per il + backend
+Possono auto sincronizzarsi con la rete
+Consentono di creare ui reattive
++ // lato client stessa api di sqlite con in piú l'uri + ContentResolver resolver =context.getContentResolver(); + Cursor cursor =resolver.query(uri,projection,selection,selectionArgs,null,null,null); + // o per l'apertura di file + InputStream input =resolve.openInputStream(uri);+
+ // il content provider
+ public class TodoProvider extends ContentProvider{
+ public boolean onCreate(){}
+
+ public Cursor query(Uri uri,String[] projection,String selection,...);
+ public Uri insert(Uri uri,ContentValues values);
+ public int delete(Uri,...);
+ public int update(Uri uri,...);
+ public String getType(Uri uri);
+ public ParcelFileDescriptor openFile(Uri uri,String mode);
+ }
+ + <!--nel manifest--> + <provider android:authorities="com.jdk.todo.provider" + android:readPermission="com.jdk.permissions.READ_TODO" + android:writePermission="com.jdk.permissions.WRITE_TODO">> + </provider> ++
05-14 23:52:47.258: + E/AndroidRuntime(21329): FATAL EXCEPTION: main +05-14 23:52:47.258: E/AndroidRuntime(21329): android.os.NetworkOnMainThreadException +05-14 23:52:47.258: E/AndroidRuntime(21329): at + android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1126) +05-14 23:52:47.258: E/AndroidRuntime(21329): at + java.net.InetAddress.lookupHostByName(InetAddress.java:385) +05-14 23:52:47.258: E/AndroidRuntime(21329): at + java.net.InetAddress.getAllByNameImpl(InetAddress.java:236) +05-14 23:52:47.258: E/AndroidRuntime(21329): at + java.net.InetAddress.getAllByName(InetAddress.java:214) +05-14 23:52:47.258: E/AndroidRuntime(21329): at + libcore.net.http.HttpConnection.<init>(HttpConnection.java:70) + .... +
+ L'unitá di concorrenza in java é il Thread
+Ogni thread esegue un path di esecuzione indipendente. In modo concorrente, + (realmente concorrente se siamo su un multi core)
+Un Thread é anche una radice per quanto concerne il garbage collector, la concorrenza é + la causa dei memory leak
+I thread hanno bisogno di comunicare tra loro e sincronizzarsi
+Uso diretto dei thread
+Threads e lock, anche se a volte necessari, sono strumenti di basso + livello.
+In particolare, non possiamo comunque bloccare il thread della ui, in attesa di un mutex
+Ci servono altre primitive
+
+ private TextView mTv;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ //...
+ mTv = (TextView)findViewById(...);
+ new Thread(){
+ public void run() {
+ final String result = obtainStringFromBlockingSource();
+ // mTv.setText(result); -> Exception ui update from non Main Thread
+ runOnUiThread(new Runnable(){
+ public void run(){
+ mTv.setText(result);
+ }
+ })
+ }
+ }.start();
+ }
+
+ Leak! Il thread (non statico) contiene un + riferimento all'activity, il garbage collector non puó eliminarla. + Rischio di OutOfMemory
+mTv non é piú visibile dopo un cambio di configurazione
+Strategia: bloccare il thread in onDestroy o onStop e riprendere il lavoro in + onStart()
+Minimizzare il rischio di thread non interruptible, ad esempio timeout, + controllare InterruptedException
+Minimizzare il rischio di memory leak, disaccoppiando l'activity dal thread: retained + fragments
+Ancora meglio:
+Non usate i thread nelle activity a meno che non siate sicuri di + quello che state facendo
+Facilita l'interazione con la ui
+Sconsigliabile per operazioni di lunga durata.
+Non gestisce il cilco di vita delle activity.
+
+ public class Task extends AsyncTask<Params,Progress,Result>{
+ protected void onPreExceute(){/* OPT sul thread della ui prima di essere avviato*/}
+
+ protected Result doInBackground(Params... params){
+ //REQ esegue il lavoro in background
+ publishProgress(Progress...); // invia risultati parziali al thread della ui
+ }
+
+ protected void onProgressUpdate(Progress... progress){
+ //OPT callback di publishProgress()
+ }
+ protected void onPostExecute(Result res){
+ //OPT riceve il valore di ritorno da doInBackground sulla ui
+ }
+ }
+
+
+ public AsyncTask execute(Params ... params); //avvia il task
+
+ public final boolean cancel(boolean interrupt); //aborts
+
+ public final boolean isCancelled(); // is aborted
+
+ public void onCancelled(){ } // callback di cancel
+
+
+ Async Task sfrutta al suo interno tre componenti basilari di android
+
+ private final static int ARG = 1;
+ private final static int EVENT_1 = 1;
+
+ public MyHandler extends Handler{
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case EVENT_1:
+ handleEvent(msg.obj,msg.arg1,msg.arg2);
+ break;
+ //...
+ }
+ }
+ }
+
+ MyHandler handler = new MyHandler();
+ //...
+
+ Message msg =handler.obtainMessage(EVENT_1,ARG,ARG,"ciao");
+ handler.sendMessage(msg);
+ handler.sendMessageDelayed(handler.obtainEmptyMessage(EVENT_1),1000);
+ handler.post(new Runnable(){/*code to execute*/})
+
+ handler.removeMessages(EVENT_1);
+ AsyncQueryHandler
+Wrappa un contentResolver: le operazioni sono effettuate serialmente su un + looper in background
+
+ private final static int QUERY_ID = 1;
+
+ private static class ContentHandler extends AsyncQueryHandler{
+ protected void onQueryComplete(int token,Object cookie,Cursor result){
+ // callback
+ }
+ }
+ ContentHandler h = new ContentHandler(getContentResolver());
+
+ h.startQuery(QUERY_ID,/*cookie*/null,uri,....);
+
+ HandlerThread un thread su cui gira un looper
+
+ public class MyLoopingThread extends HandlerThread{
+
+ public MyLoopingThread(){
+ super("name for debugging purposes");
+ }
+
+ @Override
+ protected void onLooperPrepared() {
+ // do some setup before start looping
+ }
+
+ public Handler createHandler(Handler.Callback callback){
+ return new Handler(getLooper(),callback);
+ }
+ }
+
+ I loader permettono di caricare dati in background in modo sicuro
+Gestire e creare i loader
++ private final static int ID = 1; + //il loader manager avvia un loader identificato da un id se e solo se non + //ne esiste giá uno precedente (anche in una configurazione precedente) + getSupportLoaderManager().initLoader(ID,null,fCallbacks); + // fCallbacks viene notificato quando il risultato é realizzato + + //kill currently running loader notify if already completed + getSupportLoaderManager().destroyLoader(ID); + + // praticamente la somma dei due precedenti + getSupportLoaderManager().restartLoader(ID,null fCallbacks ++
Implementare le callback necessarie nell'activity
+
+
+ LoaderManager.LoaderCallbacks<T> fCallbacks =
+ new LoaderManager.LoaderCallbacks<T>() {
+ public Loader<T> onCreateLoader(int id, Bundle args) {
+ /* create and return a loader for the id and arguments*/
+ /* cursor loader is the most used tipically we won't need to implement
+ a new one */
+ return new MyLoader(context);
+ }
+
+ public void onLoadFinished(Loader<T> loader, T data) {
+ /* use loaded data */
+ }
+
+ public void onLoaderReset(Loader<T> loader) {
+ /* loader has been reset clear data */
+ }
+ }
+
+ Implementare un loader custom
+
+ public class MyLoader extends AsyncTaskLoader<T>{
+ private T mData;
+
+ public MyLoader(Context context) {
+ super(context);
+ }
+ // background work
+ @Override public T loadInBackground() {
+ T data = loadYourData();
+ return data;
+ }
+ }
+
+ Implementare un loader custom
+
+ // delivers data to onLoadFinished
+ @Override public void deliverResult(T data) {
+ if(isReset()){
+ // another request overrides the current one
+ releaseYourData(data);
+ }
+ T old = mData; mData = data;
+ if(isStarted()) {
+ // currently active
+ super.deliverResult(mData);
+ }
+ releaseYourData(old);
+ }
+ @Override protected void onStartLoading() {
+ if(mData!=null) deliverResult(mData); // deliver current results;
+ // register watching T changes if possible (calling onContentChanged())
+ if(takeContentChanged()||mData==null) {
+ forceLoad();
+ }
+ }
+
+ Implementare un loader custom
+
+ @Override protected void onStopLoading(){
+ cancelLoad();
+ }
+ @Override public void onCanceled(T data){
+ super.onCanceled(data);
+ releaseYourData(data);
+ }
+ @Override public void onReset(){
+ super.onReset();
+ onStopLoading();
+ releaseYourData(mData);
+ mData = null;
+ }
+
+
+ + <service android:name=".MyService"> + <intent-filter> + <-- .... --> + </intent-filter> + </service>+ +
+ public class MyService extends Service {
+ @Override public IBinder onBind(Intent intent){
+ return null; // metodo astratto che va sempre implementato
+ }
+ @Override public int onStartCommand(Intent intent,
+ int flags,int startId){
+ return START_NOT_STICKY; //start mode
+ }
+ }
+ + // avviare un service + context.startService(new Intent(context,MyService.class)); + + // stoppare un service started + context.stopService(intent); + + // o dall'interno del service stesso + stopSelf(int startId); + stopSelf(); + stopSelfResult(int startId); + ++
+ private final ServiceConnection connection =
+ new ServiceConnection(){
+ public void onServiceConnected(ComponentName name,IBinder binder){
+ }
+ public void onServiceDisconnected(ComponentName name){}
+ };
+ //binding
+ bindService(intent,connection,/*flags*/Context.BIND_AUTO_CREATE);
+
+
+ Important contact information goes here.
++ +
+