1/82
ANDROID DEVELOPMENT
2/82
ToC
4
INTRO1
USER INTERFACE
2
ADDITIONAL API FEATURES
3
DEBUGGING5
OPTIMISATIONS6
ANATOMY OF AN APPLICATION
3/82
Intro | quick start
• Android SDK (Software Development Kit)
• JDK
• ADT (Android Development Tools, Eclipse IDE plug-in)
4/82
Intro | platform overview
5/82
Intro | platform overview
6/82
Intro | platform overview
• Dalvik VM
– optimised to run on slow-cpu, low-ram, low-power devices
– runs .dex files (not .class/.jar)
– Multiple instances of DVM can run in parallel
7/82
Intro | dvm vs. jvm
• register-based vs. stack-based
– register-based VMs allow for faster execution times, but
– programs are larger when compiled.
• execution environment - multiple vs. single instance
8/82
Intro | java vs. android api
• Since it uses Java compiler, it implicitly supports a set of
Java commands
• Compatible with Java SE5 code
• A subset of Apache Harmony (open source, free Java
implementation)
• Multithreading as time-slicng.
• Dalvik implements the keyword synchronized and
java.util.concurrent.* package
• Supports reflexion and finalizers but these are not
recomended
• Does not support
– awt, swing, rmi, applet, ...
9/82
4
INTRO1
USER INTERFACE
2
ADDITIONAL API FEATURES
3
DEBUGGING5
OPTIMISATIONS6
ANATOMY OF AN APPLICATION
10/82
Apps | activity
• Base class mostly for visual components
– extends Activity
– override onCreate
11/82
Apps | activity
/* Example.java */
package uk.ac.ic.doc;
import android.app.Activity;
import android.os.Bundle;
public class Example extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.interface);
}
}
12/82
Apps | activity
/* interface.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”>
<TextView
android:id=“@+id/componentName”
android:layout_width=“fill_parent”
android:layout_height=“wrap_content”
android:text=“Text that will be displayed.”
/>
</LinearLayout>
13/82
Apps | activity
/* Example.java */
package uk.ac.ic.doc;
import android.app.Activity;
import android.os.Bundle;
public class Example extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.interface);
TextView text_view = (TextView)findViewById(R.id.componentName);
}
}
14/82
Apps | activity
/* strings.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<resources xmlns:android=“http://schemas.android.com/apk/res/android”>
<string name=“textRefName”>Text that will be displayed</strings>
</resources>
/* interface.xml */
[...]
<TextView
android:id=“@+id/componentName”
android:layout_width=“fill_parent”
android:layout_height=“wrap_content”
android:text=“@string/textRefName”
/>
15/82
Apps | activity
16/82
Apps | activity
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(“key”, value);
outState.putFloatArray(“key2”, value2);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
value = savedInstanceState.getString(“key”);
value2 = savedInstanceState.getFloatArray(“key2”);
}
17/82
Apps | intent
• Allows communication between components
– Message passing
– Bundle
Intent intent = new Intent(CurrentActivity.this, OtherActivity.class);
startActivity(intent);
18/82
Apps | intent
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Button listener
Button btnStart = (Button) findViewById(R.id.btn_start);
btnStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent intent =
new Intent(CurrentActivity.this, OtherActivity.class);
startActivity(intent);
}
});
}
19/82
Apps | thread
Button btnPlay = (Button) findViewById(R.id.btnPlay);
btnPlay.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
// Main Thread blocks
Thread backgroundMusicThread = new Thread(
new Runnable() {
public void run() {
playMusic();
}
}
);
backgroundMusicThread.start();
}
});
20/82
Apps | handler
• Communication between tasks running in parallel
21/82
Apps | handler
private Handler mHandler = new Handler();
private Color mColor = Color.BLACK;
private Runnable mRefresh = new Runnable() {
public void run() {
mTextViewOnUI.setBackgroundColor(mColor)
}};
private Thread mCompute = new Thread(Runnable() {
public void run() {
while(1){
mColor = cpuIntensiveColorComputation(...);
mHandler.post(mRefresh);
}
}});
public void onCreate(Bundle savedInstanceState) {
mCompute.start();
}
22/82
Apps | service
• Base class for background tasks
– extends Service
– override onCreate
• It’s not
– a separate process
– a separate thread
• It is
– part of the main thread
– a way to update an application when it’s not active
23/82
Apps | service
24/82
Apps | broadcast receiver
• extends BroadcastReceiver
• implements onReceive()
• Waits for a system broadcast to happen to trigger an
event
• OS-generated
– Battery empty
– Camera button pressed
– New app installed
– Wifi connection established
• User-generated
– Start of some calculation
– End of an operation
25/82
Apps | broadcast receiver
public class BRExample extends BroadcastReceiver {
@Override
public void onReceive(Context rcvCtx, Intent rcvIntent) {
if (rcvIntent.getAction().equals(Intent.ACTION_CAMERA_BUTTON)) {
rcvCtx.startService(new Intent(rcvCtx, SomeService.class));
}}}
public class SomeService extends Service {
@Override
public IBinder onBind(Intent arg0) { return null; }
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this,“Camera...”, Toast.LENGTH_LONG).show();}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, “Service done”, Toast.LENGTH_LONG).show();}
}
26/82
Apps | notifications
• Toast
• AlertDialog
• Notification
Toast.makeText(this, “Notification text”, Toast.LENGTH_SHORT).show();
27/82
Apps | manifest
<?xml version=“1.0” encoding=“utf-8”?>
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“uk.ac.ic.doc” android:versionCode=“1”
android:versionName=“1.0”>
<application android:icon=“@drawable/icon”
android:label=“@string/app_name”>
<activity android:name=“.SampleActivity”
android:label=“@string/activity_title_text_ref”>
<intent-filter>
/* ... */
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion=“3” />
</manifest>
28/82
Apps | resources
• /res
– anim
– drawable
• hdpi
• mdpi
• ldpi
– layout
– values
• arrays.xml
• colors.xml
• strings.xml
– xml
– raw
29/82
Apps | R.java
• Autogenerated, best if not manually edited
• gen/
30/82
4
INTRO1
USER INTERFACE
2
ADDITIONAL API FEATURES
3
DEBUGGING5
OPTIMISATIONS6
ANATOMY OF AN APPLICATION
31/82
Elements and layouts
• dip vs. px
• Component dimesions
– wrap_content
– fill_parent
32/82
Elements and layouts
• Linear Layout
– Shows nested View elements
/* linear.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout android:orientation=“horizontal”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:layout_weight=“1”>
<TextView android:text=“red” />
<TextView android:text=“green” />
</LinearLayout>
<LinearLayout android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:layout_weight=“1”>
<TextView android:text=“row one” />
</LinearLayout>
33/82
Elements and layouts
• Relative Layout
34/82
Elements and layouts
• Table Layout
– Like the HTML div tag
/* table.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<TableLayout android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:stretchColumns=“1”>
<TableRow>
<TextView android:layout_column=“1”
android:text=“Open...”
android:padding=“3dip” />
<TextView android:text=“Ctrl-O”
android:gravity=“right”
android:padding=“3dip” />
</TableRow>
</TableLayout>
35/82
Elements and layouts
• Grid View
/* grid.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<GridView
android:id=“@+id/gridview”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:columnWidth=“90dp”
android:numColumns=“auto_fit”
android:verticalSpacing=“10dp”
android:horizontalSpacing=“10dp”
android:stretchMode=“columnWidth”
android:gravity=“center”
/>
36/82
Elements and layouts
• Grid View
/* GridExample.java */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.grid);
GridView gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new AdapterForGridView(this));
gridview.setOnItemClickListener(
new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v,
int pos, long id) {
Toast.makeText(
GridPrimer.this, "" + pos, Toast.LENGTH_SHORT).show();
}});
}
37/82
Elements and layouts
• Grid View
/* AdapterForGridView.java */
public class AdapterForGridView extends BaseAdapter {
private Context mContext;
public AdapterForGridView(Context c) { mContext = c; }
public int getCount() { return mThumbIDs.length; }
public Object getItem(int position) { return null;}
public long getItemId(int position) { return 0; }
// bad getView implementation
public View getView(int pos, View convertView, ViewGroup parent) {
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(mThumbIDs[pos]);
return imageView;
}
private Integer[] mThumbIDs =
{ R.drawable.img1, R.drawable.img2 /*...*/ };
}
38/82
Elements and layouts
• Tab Layout
/* tab.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<TabHost android:id=“@android:id/tabhost”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”>
<LinearLayout android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”>
<TabWidget android:id=“@android:id/tabs”
android:layout_width=“fill_parent”
android:layout_height=“wrap_content”/>
<FrameLayout
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”/>
</LinearLayout>
</TabHost>
39/82
Elements and layouts
• Tab Layout
/* selector1.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<selector xmlns:android=“http://schemas.android.com/apk/res/android”>
<!– Tab is selected -->
<item android:drawable=“@drawable/ic_tab_1_selected”
android:state_selected=“true” />
<!– Tab not selected -->
<item android:drawable=“@drawable/ic_tab_1_not_selected” />
</selector>
/* selector2.xml */
/* selector3.xml */
40/82
Elements and layouts
• Tab Layout
/* Tab1.java */
public class Tab1 extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textview = new TextView(this);
textview.setText(“This is the Artists tab”);
setContentView(textview);
}
}
/* Tab2.java */
/* Tab3.java */
41/82
Elements and layouts
• Tab Layout
/* TabExample.java */
public class TabExample extends TabActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tab);
TabHost tabHost = getTabHost();
//--- tab 1 ---
Intent intent = new Intent().setClass(this, Tab1.class);
TabHost.TabSpec spec = tabHost.newTabSpec(“tab1”).setIndicator(
“Artists”, getResources().getDrawable(R.drawable.selector1))
.setContent(intent);
tabHost.addTab(spec);
//--- tab 1 ---
tabHost.setCurrentTab(2);
}
42/82
Elements and layouts
• List View
/* list_item.xml */
<?xml version=“1.0” encoding=“utf-8”?>
<TextView
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:padding=“10dp”
android:textSize=“16sp” />
43/82
Elements and layouts
• List View
/* ListViewExample.java */
public class ListViewExample extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(this,
R.layout.list_item, COUNTRIES));
ListView lv = getListView();
lv.setTextFilterEnabled(true);
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getApplicationContext(),
((TextView) view).getText(), Toast.LENGTH_SHORT).show();
}});
}
44/82
Elements and layouts
• Button
• ImageButton
• EditText
• CheckBox
• RadioButton
• ToggleButton
• RatingBar
45/82
Elements and layouts
• DatePicker
• TimePicker
• Spinner
• AutoComplete
• Gallery
• MapView
• WebView
46/82
Events
• Event Handler
– Hardware buttons
• Event Listener
– Touch screen
47/82
Events
• KeyEvent is sent to callback methods
– onKeyUp(), onKeyDown(), onKeyLongpress()
– onTrackballEvent(), onTouchEvent()
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_CAMERA) {
return true; // consumes the event
}
return super.onKeyDown(keyCode, event);
}
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) { /* ... */ }
});
48/82
Events
public class TouchExample extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) { /*...*/ }
});
button.setOnLongClickListener(new OnLongClickListener() {
public boolean onLongClick(View v) {
// ...
return true;
}
});
}
}
49/82
Menus
• Options Menu: MENU button, tied to an Activity
• Context Menu: View LongPress
• Submenu
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENU_ADD, 0, “Add”)
.setIcon(R.drawable.icon);
menu.add(0, MENU_WALLPAPER, 0, “Wallpaper”);
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case MENU_ADD: //... ; return true;
case MENU_WALLPAPER: //... ; return true;
default: return false;
}
}
public void onCreate(Bundle savedInstanceState) {
registerForContextMenu((View)findViewById(/*...*/));
}
public void onCreateContextMenu(ContextMenu menu, View
v, ContextMenuInfo menuInfo){
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, MENU_SMS, 0, “SMS”);
menu.add(0, MENU_EMAIL, 0, “Email”);
}
public boolean onContextItemSelected(MenuItem item) {
switch(item.getItemId()) { case MENU_SMS: /*...*/ }
}
50/82
Widget
• XML Layout
• AppWidgetProvider gets notified
• Dimensions and refresh frequency
51/82
4
INTRO1
USER INTERFACE
2
ADDITIONAL API FEATURES
3
DEBUGGING5
OPTIMISATIONS6
ANATOMY OF AN APPLICATION
52/82
More on API | 2D
Bitmap image;
image = BitmapFactory.decodeResource(getResources(),R.drawable.image1);
// getPixel(), setPixel()
image = BitmapFactory.decodeFile(“path/to/image/on/SDcard”);
// Environment.getExternalStorageDirectory().getAbsolutePath()
53/82
More on API | 2D
public class MyGUIcomponent extends View {
private Paint paint;
public MyGUIcomponent(Context c){
paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(25);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(“some text”, 5, 30, paint);
}
@Override
protected void onMeasure(int w, int h){
// w = ...; h = ...;
setMeasuredDimension(w, h);
}
}
54/82
More on API | 3D
• OpenGL library
• Camera, matrices, transformations, ...
• View animation
55/82
More on API | audio/video
<uses-permission android:name=“android.permission.RECORD_VIDEO” />
56/82
More on API | hardware
• Camera
• Phone
• Sensors
• WiFi
• Bluetooth
• GPS (Location services/Maps)
57/82
More on API | sensors
• Accelerometer
• Thermometer
• Compass
• Light sensor
• Barometer
• Proximity sensor
58/82
More on API | sensors
• A list of all sensors
– getSensorList()
• Control class
– SensorManager
• Callback methods
– onSensorChanged()
– onAccuracyChanged()
59/82
More on API | sensors
private void initAccel(){
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSens = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mSens,
SensorManager.SENSOR_DELAY_GAME);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == SensorManager.SENSOR_ACCELEROMETER) {
float x = event.values[SensorManager.DATA_X];
float y = event.values[SensorManager.DATA_Y];
float z = event.values[SensorManager.DATA_Z];
}
}
60/82
More on API | wifi
<uses-permission android:name=“android.permission.ACCESS_NETWORK_STATE”
/>
private BroadcastReceiver mNetworkReceiver = new BroadcastReceiver(){
public void onReceive(Context c, Intent i){
Bundle b = i.getExtras();
NetworkInfo info =
(NetworkInfo) b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
if(info.isConnected()){
//...
}else{
// no connection
}
}
};
this.registerReceiver(mNetworkReceiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
61/82
More on API | internet
• Social network apps
• Cloud apps
• Sockets, Datagrams, Http, ...
62/82
More on API | sms
SmsManager mySMS = SmsManager.getDefault();
String to_whom = “+4475...”;
String message_text = “...”;
mojSMS.sendTextMessage(to_whom, null, message_text, null, null);
<uses-permission android:name=“android.permission.SEND_SMS” />
<uses-permission android:name=“android.permission.RECEIVE_SMS” />
ArrayList<String> multiSMS = mySMS.divideMessage(poruka);
mySMS.sendMultipartTextMessage(to_whom, null, multiSMS, null, null);
63/82
More on API | sms
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context c, Intent in) {
if(in.getAction().equals(RECEIVED_ACTION)) {
Bundle bundle = in.getExtras();
if(bundle!=null) {
Object[] pdus = (Object[])bundle.get(“pdus”);
SmsMessage[] msgs = new SmsMessage[pdus.length];
for(int i = 0; i<pdus.length; i++) {
msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
}
// reply();
}
}
}};
64/82
More on API | sms
public class ResponderService extends Service {
private static final String RECEIVED_ACTION =
“android.provider.Telephony.SMS_RECEIVED”;
@Override
public void onCreate() {
super.onCreate();
registerReceiver(receiver, new IntentFilter(RECEIVED_ACTION));
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId); }
@Override
public void onDestroy() {
super.onDestroy(); unregisterReceiver(receiver); }
@Override
public IBinder onBind(Intent arg0) { return null; }
}
65/82
More on API | sharedPreferences
• Interface for easy storage of key-value pairs
• Mostly used for saving user settings (language, etc.)
– e.g. username/pass combination for auto-login
• Access to file
– MODE_PRIVATE
– MODE_WORLD_READABLE
– MODE_WORLD_WRITEABLE
66/82
More on API | sharedPreferences
SharedPreferences prefs = getSharedPreferences(“Name”, MODE_PRIVATE);
Editor mEditor = prefs.edit();
mEditor.putString(“username”, username);
mEditor.putString(“password”, password);
mEditor.commit();
SharedPreferences prefs = getSharedPreferences(“Name”, MODE_PRIVATE);
String username = prefs.getString(“username”, “”);
String password = prefs.getString(“password”, “”);
67/82
More on API | sqlite
• Each application has its own DB (can be shared)
• /data/data/<you_package>/databases
• Can
– Create a db
– Open a db
– Create tables
– Insert data into tables
– Fetch data from tables
– Close a db
• Basically, SQL syntax
68/82
More on API | contentProvider
• Since every application is sandboxed, this is Androids
mechanism which relates data across apps
• Required access privileges must be declared in Manifest
and approved by user during installation
69/82
More on API | java.io.File
FileInputStream fis = openFileInput(“some_file.txt”);
FileOutputStream fos = openFileOutput(“some_file.txt”,
Context.MODE_WORLD_WRITEABLE);
Bitmap slika;
FileOutputStream new_profile_image = openFileOutput(“new_image.png”,
Context.MODE_WORLD_WRITEABLE);
slika.compress(CompressFormat.PNG, 100, new_profile_image);
out.flush();
out.close();
InputStream is =
this.getResource().openRawResource(R.raw.some_raw_file);
70/82
4
INTRO1
USER INTERFACE
2
ADDITIONAL API FEATURES
3
DEBUGGING5
OPTIMISATIONS6
ANATOMY OF AN APPLICATION
71/82
Debugging
• gdb
– Since it’s based on Linux, similar command-set
• DDMS through ADT
– Dalvik Debug Monitoring Service
– Android Developer Tools plugin for Eclipse
– Using breakpoints
• Android SDK Debug tools
– ADB (Android Debug Bridge)
– LogCat
– HierarchyViewer
72/82
Debugging | gdb
> adb shell top
> adb shell ps
> gdb mojprogram
> adb logcat
73/82
Debugging | ddms
74/82
Debugging | android debug bridge
• Controlling an emulator instance
> adb start-server
> adb stop-server
75/82
Debugging | LogCat
• Logging app execution
• Real-time logging tool
• Works with tags, priorities and filters
76/82
Debugging | hierarchy viewer
• For “interface debugging”
77/82
4
INTRO1
USER INTERFACE
2
ADDITIONAL API FEATURES
3
DEBUGGING5
OPTIMISATIONS6
ANATOMY OF AN APPLICATION
78/82
Optimisations | in general
• Instancing objects is expensive, avoid if possible
– Overhead from creating, allocation and GC-ing
• Use native methods
– written in C/C++
– around 10-100x faster than user-written in Java by user
• Calls through interfaces are up to 2x slower than virtual
• Declare methods as static if they don’t need access to
object’s fields
• Caching field access results
– counters, etc.
Map myMapa = new HashMap();
HashMap myMapa = new HashMap();
79/82
Optimisations | getView()
• Implemented in Adapter
• Used for:
– Fetching elements from XML
– Their creation in memory (inflate)
– Filling them with valid data
– Returning a ready View element
private static class SomeAdapter extends BaseAdapter {
public SomeAdapter(Context context) {}
public int getCount() { /*...*/ }
public Object getItem(int position) { /*...*/ }
public long getItemId(int position) { /*...*/ }
public View getView(int p, View cv, ViewGroup p) {
// this implementation directly impacts performace
}
}
80/82
Optimisations | getView()
public View getView(int p, View cv, ViewGroup p) {
View element = //... make a new View
element.text = //... get element from XML
element.icon = //... get element from XML
return element;
}
• Creation of a new element gets called each time, and
that is the single most expensive operation when dealing
with UI
81/82
Optimisations | getView()
public View getView(int p, View cv, ViewGroup p) {
if (cv == null) {
cv = //... make a new View
}
cv.text = //... get element from XML
cv.icon = //... get element from XML
return cv;
}
• New element get created only a couple of times
• Performance is acceptable
82/82
Optimisations | getView()
static class ViewHolder {
TextView text;
ImageView icon;
}
public View getView(int p, View cv, ViewGroup p) {
ViewHolder holder;
if (cv == null) {
cv = //... make a new View
holder = new ViewHolder();
holder.text = //... get element from XML
holder.icon = //... get element from XML
cv.setTag(holder);
} else {
holder = (ViewHolder) cv.getTag();
}
return cv;
}

Android tutorial (2)

  • 1.
  • 2.
    2/82 ToC 4 INTRO1 USER INTERFACE 2 ADDITIONAL APIFEATURES 3 DEBUGGING5 OPTIMISATIONS6 ANATOMY OF AN APPLICATION
  • 3.
    3/82 Intro | quickstart • Android SDK (Software Development Kit) • JDK • ADT (Android Development Tools, Eclipse IDE plug-in)
  • 4.
  • 5.
  • 6.
    6/82 Intro | platformoverview • Dalvik VM – optimised to run on slow-cpu, low-ram, low-power devices – runs .dex files (not .class/.jar) – Multiple instances of DVM can run in parallel
  • 7.
    7/82 Intro | dvmvs. jvm • register-based vs. stack-based – register-based VMs allow for faster execution times, but – programs are larger when compiled. • execution environment - multiple vs. single instance
  • 8.
    8/82 Intro | javavs. android api • Since it uses Java compiler, it implicitly supports a set of Java commands • Compatible with Java SE5 code • A subset of Apache Harmony (open source, free Java implementation) • Multithreading as time-slicng. • Dalvik implements the keyword synchronized and java.util.concurrent.* package • Supports reflexion and finalizers but these are not recomended • Does not support – awt, swing, rmi, applet, ...
  • 9.
    9/82 4 INTRO1 USER INTERFACE 2 ADDITIONAL APIFEATURES 3 DEBUGGING5 OPTIMISATIONS6 ANATOMY OF AN APPLICATION
  • 10.
    10/82 Apps | activity •Base class mostly for visual components – extends Activity – override onCreate
  • 11.
    11/82 Apps | activity /*Example.java */ package uk.ac.ic.doc; import android.app.Activity; import android.os.Bundle; public class Example extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.interface); } }
  • 12.
    12/82 Apps | activity /*interface.xml */ <?xml version=“1.0” encoding=“utf-8”?> <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android” android:orientation=“vertical” android:layout_width=“fill_parent” android:layout_height=“fill_parent”> <TextView android:id=“@+id/componentName” android:layout_width=“fill_parent” android:layout_height=“wrap_content” android:text=“Text that will be displayed.” /> </LinearLayout>
  • 13.
    13/82 Apps | activity /*Example.java */ package uk.ac.ic.doc; import android.app.Activity; import android.os.Bundle; public class Example extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.interface); TextView text_view = (TextView)findViewById(R.id.componentName); } }
  • 14.
    14/82 Apps | activity /*strings.xml */ <?xml version=“1.0” encoding=“utf-8”?> <resources xmlns:android=“http://schemas.android.com/apk/res/android”> <string name=“textRefName”>Text that will be displayed</strings> </resources> /* interface.xml */ [...] <TextView android:id=“@+id/componentName” android:layout_width=“fill_parent” android:layout_height=“wrap_content” android:text=“@string/textRefName” />
  • 15.
  • 16.
    16/82 Apps | activity @Override protectedvoid onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(“key”, value); outState.putFloatArray(“key2”, value2); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); value = savedInstanceState.getString(“key”); value2 = savedInstanceState.getFloatArray(“key2”); }
  • 17.
    17/82 Apps | intent •Allows communication between components – Message passing – Bundle Intent intent = new Intent(CurrentActivity.this, OtherActivity.class); startActivity(intent);
  • 18.
    18/82 Apps | intent @Override publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Button listener Button btnStart = (Button) findViewById(R.id.btn_start); btnStart.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Intent intent = new Intent(CurrentActivity.this, OtherActivity.class); startActivity(intent); } }); }
  • 19.
    19/82 Apps | thread ButtonbtnPlay = (Button) findViewById(R.id.btnPlay); btnPlay.setOnClickListener(new View.OnClickListener() { public void onClick(View view){ // Main Thread blocks Thread backgroundMusicThread = new Thread( new Runnable() { public void run() { playMusic(); } } ); backgroundMusicThread.start(); } });
  • 20.
    20/82 Apps | handler •Communication between tasks running in parallel
  • 21.
    21/82 Apps | handler privateHandler mHandler = new Handler(); private Color mColor = Color.BLACK; private Runnable mRefresh = new Runnable() { public void run() { mTextViewOnUI.setBackgroundColor(mColor) }}; private Thread mCompute = new Thread(Runnable() { public void run() { while(1){ mColor = cpuIntensiveColorComputation(...); mHandler.post(mRefresh); } }}); public void onCreate(Bundle savedInstanceState) { mCompute.start(); }
  • 22.
    22/82 Apps | service •Base class for background tasks – extends Service – override onCreate • It’s not – a separate process – a separate thread • It is – part of the main thread – a way to update an application when it’s not active
  • 23.
  • 24.
    24/82 Apps | broadcastreceiver • extends BroadcastReceiver • implements onReceive() • Waits for a system broadcast to happen to trigger an event • OS-generated – Battery empty – Camera button pressed – New app installed – Wifi connection established • User-generated – Start of some calculation – End of an operation
  • 25.
    25/82 Apps | broadcastreceiver public class BRExample extends BroadcastReceiver { @Override public void onReceive(Context rcvCtx, Intent rcvIntent) { if (rcvIntent.getAction().equals(Intent.ACTION_CAMERA_BUTTON)) { rcvCtx.startService(new Intent(rcvCtx, SomeService.class)); }}} public class SomeService extends Service { @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onCreate() { super.onCreate(); Toast.makeText(this,“Camera...”, Toast.LENGTH_LONG).show();} @Override public void onDestroy() { super.onDestroy(); Toast.makeText(this, “Service done”, Toast.LENGTH_LONG).show();} }
  • 26.
    26/82 Apps | notifications •Toast • AlertDialog • Notification Toast.makeText(this, “Notification text”, Toast.LENGTH_SHORT).show();
  • 27.
    27/82 Apps | manifest <?xmlversion=“1.0” encoding=“utf-8”?> <manifest xmlns:android=“http://schemas.android.com/apk/res/android” package=“uk.ac.ic.doc” android:versionCode=“1” android:versionName=“1.0”> <application android:icon=“@drawable/icon” android:label=“@string/app_name”> <activity android:name=“.SampleActivity” android:label=“@string/activity_title_text_ref”> <intent-filter> /* ... */ </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion=“3” /> </manifest>
  • 28.
    28/82 Apps | resources •/res – anim – drawable • hdpi • mdpi • ldpi – layout – values • arrays.xml • colors.xml • strings.xml – xml – raw
  • 29.
    29/82 Apps | R.java •Autogenerated, best if not manually edited • gen/
  • 30.
    30/82 4 INTRO1 USER INTERFACE 2 ADDITIONAL APIFEATURES 3 DEBUGGING5 OPTIMISATIONS6 ANATOMY OF AN APPLICATION
  • 31.
    31/82 Elements and layouts •dip vs. px • Component dimesions – wrap_content – fill_parent
  • 32.
    32/82 Elements and layouts •Linear Layout – Shows nested View elements /* linear.xml */ <?xml version=“1.0” encoding=“utf-8”?> <LinearLayout android:orientation=“horizontal” android:layout_width=“fill_parent” android:layout_height=“fill_parent” android:layout_weight=“1”> <TextView android:text=“red” /> <TextView android:text=“green” /> </LinearLayout> <LinearLayout android:orientation=“vertical” android:layout_width=“fill_parent” android:layout_height=“fill_parent” android:layout_weight=“1”> <TextView android:text=“row one” /> </LinearLayout>
  • 33.
  • 34.
    34/82 Elements and layouts •Table Layout – Like the HTML div tag /* table.xml */ <?xml version=“1.0” encoding=“utf-8”?> <TableLayout android:layout_width=“fill_parent” android:layout_height=“fill_parent” android:stretchColumns=“1”> <TableRow> <TextView android:layout_column=“1” android:text=“Open...” android:padding=“3dip” /> <TextView android:text=“Ctrl-O” android:gravity=“right” android:padding=“3dip” /> </TableRow> </TableLayout>
  • 35.
    35/82 Elements and layouts •Grid View /* grid.xml */ <?xml version=“1.0” encoding=“utf-8”?> <GridView android:id=“@+id/gridview” android:layout_width=“fill_parent” android:layout_height=“fill_parent” android:columnWidth=“90dp” android:numColumns=“auto_fit” android:verticalSpacing=“10dp” android:horizontalSpacing=“10dp” android:stretchMode=“columnWidth” android:gravity=“center” />
  • 36.
    36/82 Elements and layouts •Grid View /* GridExample.java */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.grid); GridView gridview = (GridView) findViewById(R.id.gridview); gridview.setAdapter(new AdapterForGridView(this)); gridview.setOnItemClickListener( new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int pos, long id) { Toast.makeText( GridPrimer.this, "" + pos, Toast.LENGTH_SHORT).show(); }}); }
  • 37.
    37/82 Elements and layouts •Grid View /* AdapterForGridView.java */ public class AdapterForGridView extends BaseAdapter { private Context mContext; public AdapterForGridView(Context c) { mContext = c; } public int getCount() { return mThumbIDs.length; } public Object getItem(int position) { return null;} public long getItemId(int position) { return 0; } // bad getView implementation public View getView(int pos, View convertView, ViewGroup parent) { ImageView imageView = new ImageView(mContext); imageView.setImageResource(mThumbIDs[pos]); return imageView; } private Integer[] mThumbIDs = { R.drawable.img1, R.drawable.img2 /*...*/ }; }
  • 38.
    38/82 Elements and layouts •Tab Layout /* tab.xml */ <?xml version=“1.0” encoding=“utf-8”?> <TabHost android:id=“@android:id/tabhost” android:layout_width=“fill_parent” android:layout_height=“fill_parent”> <LinearLayout android:orientation=“vertical” android:layout_width=“fill_parent” android:layout_height=“fill_parent”> <TabWidget android:id=“@android:id/tabs” android:layout_width=“fill_parent” android:layout_height=“wrap_content”/> <FrameLayout android:layout_width=“fill_parent” android:layout_height=“fill_parent”/> </LinearLayout> </TabHost>
  • 39.
    39/82 Elements and layouts •Tab Layout /* selector1.xml */ <?xml version=“1.0” encoding=“utf-8”?> <selector xmlns:android=“http://schemas.android.com/apk/res/android”> <!– Tab is selected --> <item android:drawable=“@drawable/ic_tab_1_selected” android:state_selected=“true” /> <!– Tab not selected --> <item android:drawable=“@drawable/ic_tab_1_not_selected” /> </selector> /* selector2.xml */ /* selector3.xml */
  • 40.
    40/82 Elements and layouts •Tab Layout /* Tab1.java */ public class Tab1 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView textview = new TextView(this); textview.setText(“This is the Artists tab”); setContentView(textview); } } /* Tab2.java */ /* Tab3.java */
  • 41.
    41/82 Elements and layouts •Tab Layout /* TabExample.java */ public class TabExample extends TabActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tab); TabHost tabHost = getTabHost(); //--- tab 1 --- Intent intent = new Intent().setClass(this, Tab1.class); TabHost.TabSpec spec = tabHost.newTabSpec(“tab1”).setIndicator( “Artists”, getResources().getDrawable(R.drawable.selector1)) .setContent(intent); tabHost.addTab(spec); //--- tab 1 --- tabHost.setCurrentTab(2); }
  • 42.
    42/82 Elements and layouts •List View /* list_item.xml */ <?xml version=“1.0” encoding=“utf-8”?> <TextView android:layout_width=“fill_parent” android:layout_height=“fill_parent” android:padding=“10dp” android:textSize=“16sp” />
  • 43.
    43/82 Elements and layouts •List View /* ListViewExample.java */ public class ListViewExample extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, COUNTRIES)); ListView lv = getListView(); lv.setTextFilterEnabled(true); lv.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(getApplicationContext(), ((TextView) view).getText(), Toast.LENGTH_SHORT).show(); }}); }
  • 44.
    44/82 Elements and layouts •Button • ImageButton • EditText • CheckBox • RadioButton • ToggleButton • RatingBar
  • 45.
    45/82 Elements and layouts •DatePicker • TimePicker • Spinner • AutoComplete • Gallery • MapView • WebView
  • 46.
    46/82 Events • Event Handler –Hardware buttons • Event Listener – Touch screen
  • 47.
    47/82 Events • KeyEvent issent to callback methods – onKeyUp(), onKeyDown(), onKeyLongpress() – onTrackballEvent(), onTouchEvent() public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_CAMERA) { return true; // consumes the event } return super.onKeyDown(keyCode, event); } Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { /* ... */ } });
  • 48.
    48/82 Events public class TouchExampleextends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { public void onClick(View v) { /*...*/ } }); button.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { // ... return true; } }); } }
  • 49.
    49/82 Menus • Options Menu:MENU button, tied to an Activity • Context Menu: View LongPress • Submenu public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, MENU_ADD, 0, “Add”) .setIcon(R.drawable.icon); menu.add(0, MENU_WALLPAPER, 0, “Wallpaper”); return super.onCreateOptionsMenu(menu); } public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case MENU_ADD: //... ; return true; case MENU_WALLPAPER: //... ; return true; default: return false; } } public void onCreate(Bundle savedInstanceState) { registerForContextMenu((View)findViewById(/*...*/)); } public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){ super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, MENU_SMS, 0, “SMS”); menu.add(0, MENU_EMAIL, 0, “Email”); } public boolean onContextItemSelected(MenuItem item) { switch(item.getItemId()) { case MENU_SMS: /*...*/ } }
  • 50.
    50/82 Widget • XML Layout •AppWidgetProvider gets notified • Dimensions and refresh frequency
  • 51.
    51/82 4 INTRO1 USER INTERFACE 2 ADDITIONAL APIFEATURES 3 DEBUGGING5 OPTIMISATIONS6 ANATOMY OF AN APPLICATION
  • 52.
    52/82 More on API| 2D Bitmap image; image = BitmapFactory.decodeResource(getResources(),R.drawable.image1); // getPixel(), setPixel() image = BitmapFactory.decodeFile(“path/to/image/on/SDcard”); // Environment.getExternalStorageDirectory().getAbsolutePath()
  • 53.
    53/82 More on API| 2D public class MyGUIcomponent extends View { private Paint paint; public MyGUIcomponent(Context c){ paint = new Paint(); paint.setColor(Color.WHITE); paint.setTextSize(25); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(“some text”, 5, 30, paint); } @Override protected void onMeasure(int w, int h){ // w = ...; h = ...; setMeasuredDimension(w, h); } }
  • 54.
    54/82 More on API| 3D • OpenGL library • Camera, matrices, transformations, ... • View animation
  • 55.
    55/82 More on API| audio/video <uses-permission android:name=“android.permission.RECORD_VIDEO” />
  • 56.
    56/82 More on API| hardware • Camera • Phone • Sensors • WiFi • Bluetooth • GPS (Location services/Maps)
  • 57.
    57/82 More on API| sensors • Accelerometer • Thermometer • Compass • Light sensor • Barometer • Proximity sensor
  • 58.
    58/82 More on API| sensors • A list of all sensors – getSensorList() • Control class – SensorManager • Callback methods – onSensorChanged() – onAccuracyChanged()
  • 59.
    59/82 More on API| sensors private void initAccel(){ mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); mSens = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorManager.registerListener(this, mSens, SensorManager.SENSOR_DELAY_GAME); } @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() == SensorManager.SENSOR_ACCELEROMETER) { float x = event.values[SensorManager.DATA_X]; float y = event.values[SensorManager.DATA_Y]; float z = event.values[SensorManager.DATA_Z]; } }
  • 60.
    60/82 More on API| wifi <uses-permission android:name=“android.permission.ACCESS_NETWORK_STATE” /> private BroadcastReceiver mNetworkReceiver = new BroadcastReceiver(){ public void onReceive(Context c, Intent i){ Bundle b = i.getExtras(); NetworkInfo info = (NetworkInfo) b.get(ConnectivityManager.EXTRA_NETWORK_INFO); if(info.isConnected()){ //... }else{ // no connection } } }; this.registerReceiver(mNetworkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
  • 61.
    61/82 More on API| internet • Social network apps • Cloud apps • Sockets, Datagrams, Http, ...
  • 62.
    62/82 More on API| sms SmsManager mySMS = SmsManager.getDefault(); String to_whom = “+4475...”; String message_text = “...”; mojSMS.sendTextMessage(to_whom, null, message_text, null, null); <uses-permission android:name=“android.permission.SEND_SMS” /> <uses-permission android:name=“android.permission.RECEIVE_SMS” /> ArrayList<String> multiSMS = mySMS.divideMessage(poruka); mySMS.sendMultipartTextMessage(to_whom, null, multiSMS, null, null);
  • 63.
    63/82 More on API| sms BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context c, Intent in) { if(in.getAction().equals(RECEIVED_ACTION)) { Bundle bundle = in.getExtras(); if(bundle!=null) { Object[] pdus = (Object[])bundle.get(“pdus”); SmsMessage[] msgs = new SmsMessage[pdus.length]; for(int i = 0; i<pdus.length; i++) { msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]); } // reply(); } } }};
  • 64.
    64/82 More on API| sms public class ResponderService extends Service { private static final String RECEIVED_ACTION = “android.provider.Telephony.SMS_RECEIVED”; @Override public void onCreate() { super.onCreate(); registerReceiver(receiver, new IntentFilter(RECEIVED_ACTION)); } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(receiver); } @Override public IBinder onBind(Intent arg0) { return null; } }
  • 65.
    65/82 More on API| sharedPreferences • Interface for easy storage of key-value pairs • Mostly used for saving user settings (language, etc.) – e.g. username/pass combination for auto-login • Access to file – MODE_PRIVATE – MODE_WORLD_READABLE – MODE_WORLD_WRITEABLE
  • 66.
    66/82 More on API| sharedPreferences SharedPreferences prefs = getSharedPreferences(“Name”, MODE_PRIVATE); Editor mEditor = prefs.edit(); mEditor.putString(“username”, username); mEditor.putString(“password”, password); mEditor.commit(); SharedPreferences prefs = getSharedPreferences(“Name”, MODE_PRIVATE); String username = prefs.getString(“username”, “”); String password = prefs.getString(“password”, “”);
  • 67.
    67/82 More on API| sqlite • Each application has its own DB (can be shared) • /data/data/<you_package>/databases • Can – Create a db – Open a db – Create tables – Insert data into tables – Fetch data from tables – Close a db • Basically, SQL syntax
  • 68.
    68/82 More on API| contentProvider • Since every application is sandboxed, this is Androids mechanism which relates data across apps • Required access privileges must be declared in Manifest and approved by user during installation
  • 69.
    69/82 More on API| java.io.File FileInputStream fis = openFileInput(“some_file.txt”); FileOutputStream fos = openFileOutput(“some_file.txt”, Context.MODE_WORLD_WRITEABLE); Bitmap slika; FileOutputStream new_profile_image = openFileOutput(“new_image.png”, Context.MODE_WORLD_WRITEABLE); slika.compress(CompressFormat.PNG, 100, new_profile_image); out.flush(); out.close(); InputStream is = this.getResource().openRawResource(R.raw.some_raw_file);
  • 70.
    70/82 4 INTRO1 USER INTERFACE 2 ADDITIONAL APIFEATURES 3 DEBUGGING5 OPTIMISATIONS6 ANATOMY OF AN APPLICATION
  • 71.
    71/82 Debugging • gdb – Sinceit’s based on Linux, similar command-set • DDMS through ADT – Dalvik Debug Monitoring Service – Android Developer Tools plugin for Eclipse – Using breakpoints • Android SDK Debug tools – ADB (Android Debug Bridge) – LogCat – HierarchyViewer
  • 72.
    72/82 Debugging | gdb >adb shell top > adb shell ps > gdb mojprogram > adb logcat
  • 73.
  • 74.
    74/82 Debugging | androiddebug bridge • Controlling an emulator instance > adb start-server > adb stop-server
  • 75.
    75/82 Debugging | LogCat •Logging app execution • Real-time logging tool • Works with tags, priorities and filters
  • 76.
    76/82 Debugging | hierarchyviewer • For “interface debugging”
  • 77.
    77/82 4 INTRO1 USER INTERFACE 2 ADDITIONAL APIFEATURES 3 DEBUGGING5 OPTIMISATIONS6 ANATOMY OF AN APPLICATION
  • 78.
    78/82 Optimisations | ingeneral • Instancing objects is expensive, avoid if possible – Overhead from creating, allocation and GC-ing • Use native methods – written in C/C++ – around 10-100x faster than user-written in Java by user • Calls through interfaces are up to 2x slower than virtual • Declare methods as static if they don’t need access to object’s fields • Caching field access results – counters, etc. Map myMapa = new HashMap(); HashMap myMapa = new HashMap();
  • 79.
    79/82 Optimisations | getView() •Implemented in Adapter • Used for: – Fetching elements from XML – Their creation in memory (inflate) – Filling them with valid data – Returning a ready View element private static class SomeAdapter extends BaseAdapter { public SomeAdapter(Context context) {} public int getCount() { /*...*/ } public Object getItem(int position) { /*...*/ } public long getItemId(int position) { /*...*/ } public View getView(int p, View cv, ViewGroup p) { // this implementation directly impacts performace } }
  • 80.
    80/82 Optimisations | getView() publicView getView(int p, View cv, ViewGroup p) { View element = //... make a new View element.text = //... get element from XML element.icon = //... get element from XML return element; } • Creation of a new element gets called each time, and that is the single most expensive operation when dealing with UI
  • 81.
    81/82 Optimisations | getView() publicView getView(int p, View cv, ViewGroup p) { if (cv == null) { cv = //... make a new View } cv.text = //... get element from XML cv.icon = //... get element from XML return cv; } • New element get created only a couple of times • Performance is acceptable
  • 82.
    82/82 Optimisations | getView() staticclass ViewHolder { TextView text; ImageView icon; } public View getView(int p, View cv, ViewGroup p) { ViewHolder holder; if (cv == null) { cv = //... make a new View holder = new ViewHolder(); holder.text = //... get element from XML holder.icon = //... get element from XML cv.setTag(holder); } else { holder = (ViewHolder) cv.getTag(); } return cv; }