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