More Related Content Similar to Android workshop Similar to Android workshop (20) More from Michael Galpin (11) Android workshop2. ABOUT: ME
‣ Android engineer, Bump Technologies
‣ ex-eBay: eBay Mobile for Android
‣ Android in Practice
‣ Social info
‣ @michaelg
‣ +Michael Galpin
3. Bump
Technologies
• Creators of Bump app
• Android + iOS
• 65M+ Downloads
• Creators of BumpCube
• Hiring!
4. You down with AIP?
• Chapter 11: “Appeal to
the senses using
multimedia.”
• Slideshow app
MediaMogul.apk
• http://
code.google.com/p/
android-in-practice/
7. “Of all of our inventions for mass
communication, pictures still speak the
most universally understood language.”
-- Walt Disney
14. AndroidManifest.xml
<uses-feature android:name="android.hardware.camera"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus"
android:required="true"/>
<uses-feature android:name="android.hardware.camera.flash"
android:required="false" />
<uses-feature android:name="android.hardware.camera.front"
android:required="false" />
<uses-feature android:name="android.hardware.microphone"
android:required="true"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
15. Check the front
private boolean hasFrontFacingCamera(){
PackageManager mgr = this.getPackageManager();
for (FeatureInfo fi : mgr.getSystemAvailableFeatures()){
if (fi.name.equals(PackageManager.FEATURE_CAMERA_FRONT)){
return true;
}
}
return false;
}
private Camera getFrontFacingCamera(){
for (int i=0;i<Camera.getNumberOfCameras();i++){
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
return Camera.open(i);
}
}
return null;
}
16. Check the front
private boolean hasFrontFacingCamera(){
PackageManager mgr = this.getPackageManager();
for (FeatureInfo fi : mgr.getSystemAvailableFeatures()){
if (fi.name.equals(PackageManager.FEATURE_CAMERA_FRONT)){
return true;
}
}
return false;
}
private Camera getFrontFacingCamera(){
for (int i=0;i<Camera.getNumberOfCameras();i++){
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
return Camera.open(i);
}
}
return null;
}
21. LOLZ
// Can't use Build.VERSION_CODES.GINGERBREAD
if (Build.VERSION.SDK_INT >= 9){
PostFroyoClass wontThisBlowUp = new PostFroyoClass();
}
24. Proprietary APIs FTW!
import com.sprint.hardware.twinCamDevice.FrontFacingCamera;
Camera evoCam = FrontFacingCamera.getFrontFacingCamera();
34. Just give it to me /res/raw
Resources res = this.getResources();
InputStream stream = res.openRawResource(R.raw.sunflower);
MediaPlayer.create(this, R.raw.constancy).start();
36. MediaPlayer player = new MediaPlayer();
AssetManager mgr = getResources().getAssets();
String audioDir = "audio";
final LinkedList<FileDescriptor> queue =
new LinkedList<FileDescriptor>();
for (String song : mgr.list("audio")){
queue.add(
mgr.openFd(audioDir + "/" + song).getFileDescriptor());
}
if (!queue.isEmpty()){
FileDescriptor song = queue.poll();
player.setDataSource(song);
player.prepare();
player.start();
}
player.setOnCompletionListener(new OnCompletionListener(){
@Override
public void onCompletion(MediaPlayer mp) {
if (!queue.isEmpty()){
FileDescriptor song = queue.poll();
player.setDataSource(song);
player.prepare();
player.start();
}
}
});
38. File picturesDir =
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
39. File picturesDir =
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
43. Loading local images
InputStream stream = new FileInputStream(imgFile);
Bitmap bm = BitmapFactory.decodeStream(stream);
// calculate desired width and height
Bitmap thumb = ThumbnailUtils.extractThumbnail(bm, width, height);
Bitmap thumb = Bitmap.createScaledBitmap(bm, width, height, false);
45. Loading without
exploding
public static Bitmap decodeDownsizedBitmapStream(File file, int target, Context context) throws
IOException {
FileInputStream stream = new FileInputStream(file);
Pair<Integer, Integer> source = getDimensionsForStream(stream);
stream.close();
FileInputStream in = new FileInputStream(file);
Options options = new Options();
options.inSampleSize = 1 + getDownSampleSize(max(source.first, source.second), target);
return BitmapFactory.decodeStream(in, null, options);
}
public static Pair<Integer, Integer> getDimensionsForStream(InputStream in){
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, options);
return new Pair<Integer, Integer>(options.outWidth, options.outHeight);
}
public static int getDownSampleSize(int source, int target){
int size = 1;
if (source <= 2*target){
int power = (int) ((log (source / target)) / log(2));
size = (int) pow(2, power);
}
return size;
}
46. Loading web images
URL url = new URL(urlString);
// NOTE, be careful about just doing "url.openStream()"
// it's a shortcut for openConnection().getInputStream() and doesn't set timeouts
// the defaults are "infinite" so it will wait forever if endpoint server is down
// do it properly with a few more lines of code . . .
URLConnection conn = url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);
Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream());
48. AsyncTask FTW!
private class RetrieveImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
public RetrieveImageTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... args) {
try {
URL url = new URL(args[0]);
URLConnection conn = url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(5000);
return BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
49. Loading a dozen images
isn’t cool.
Y’know what’s cool?
Loading a billion images.
50. Hot or Not?
private class DealsAdapter extends ArrayAdapter<Item> {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false);
}
TextView text = (TextView) convertView.findViewById(R.id.deal_title);
ImageView image = (ImageView) convertView.findViewById(R.id.deal_img);
Item item = getItem(position);
if (item != null) {
text.setText(item.getTitle());
new RetrieveImageTask(image).execute(item.getSmallPicUrl());
}
return convertView;
}
}
51. Watch out for leaks
public class LeakProofActivity extends Activity {
LeakProofAsyncTask task;
private class LeakProofAsyncTask extends AsyncTask<String, Void, Bitmap>{
Context potentialLeak = LeakProofActivity.this;
@Override
protected Bitmap doInBackground(String... arg0) {
// Do something that takes a long time
// Note if you need a Context here, use Application
return null;
}
@Override
protected void onPostExecute(Bitmap result){
// update UI, etc.
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
task = (LeakProofAsyncTask) getLastNonConfigurationInstance();
if (task != null){
task.potentialLeak = this;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (task != null){
task.potentialLeak = null;
}
}
@Override
public Object onRetainNonConfigurationInstance() {
return task;
}
}
53. How to build a cache?
• WeakReferences?
• SoftReferences?
• WeakHashMap?
• MapMaker?
54. LinkedHashMap!?
private static class ImageCache extends LinkedHashMap<String, Bitmap>{
private final int capacity;
public ImageCache(int capacity){
super(capacity/2, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
return this.size() > capacity;
}
}
56. Sidebar: Bitmaps & Heap
Reported by andreas....@googlemail.com, May 22, 2010
Note: This is NOT a requst for assistance!
In my applications, I keep getting the following exception:
E/AndroidRuntime( 1420): java.lang.OutOfMemoryError: bitmap size exceeds
VM budget
E/AndroidRuntime( 1420):
at
android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime( 1420):
at
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
E/AndroidRuntime( 1420):
at
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:515)
E/AndroidRuntime( 1420):
at
de.schildbach.bitmapbug.MyActivity.bitmap(MyActivity.java:38)
Comment 1 by romaingu...@gtempaccount.com, May 23, 2010
Your app needs to use less memory.
http://code.google.com/p/android/issues/detail?id=8488
58. Use Placeholders
ImageView image =
(ImageView) convertView.findViewById(R.id.deal_img);
image.setImageBitmap(
BitmapFactory.decodeResource(
getResources(), R.drawable.temp));
Item item = getItem(position);
new RetrieveImageTask(image).
execute(item.getSmallPicUrl());
59. @Override
View Holders
public View getView(final int position, View cell, ViewGroup parent) {
ViewHolder holder = (ViewHolder) cell.getTag();
if (holder == null){
holder = new ViewHolder(cell);
cell.setTag(holder);
}
Bitmap thumb = (Bitmap) getItem(position);
holder.img.setImageBitmap(thumb);
File file = getImageFile(position);
if (selectedFiles.contains(file)){
holder.cbox.setChecked(true);
} else {
holder.cbox.setChecked(false);
}
return cell;
}
static class ViewHolder {
final ImageView img;
final CheckBox cbox;
ViewHolder(View cell){
img = (ImageView) cell.findViewById(R.id.thumb);
cbox = (CheckBox) cell.findViewById(R.id.cbox);
}
}
62. SQL?
NoSQL?
SortOfSQL!
63. Queries
Cursor cursor = getContentResolver().query(table,
columns,
whereClause,
paramValues,
sortOrder);
while (cursor.moveToNext()){
// process results
}
cursor.close();
64. Music
import static android.provider.BaseColumns._ID;
import static android.provider.MediaStore.Audio.AudioColumns.ARTIST;
import static android.provider.MediaStore.Audio.AudioColumns.IS_MUSIC;
import static android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
import static android.provider.MediaStore.MediaColumns.DATA;
import static android.provider.MediaStore.MediaColumns.TITLE;
String[] columns = {TITLE,ARTIST,_ID, DATA};
// where clause so we don't get ringtones, podcasts, etc.
String whereClause = IS_MUSIC + " = ?";
String[] whereValues = {"1"};
cursor = managedQuery(EXTERNAL_CONTENT_URI,
columns,
whereClause,
whereValues,
null
);
65. Contacts
String[] projection = {Phone.CONTACT_ID, Phone.NUMBER};
String selection = Data.IN_VISIBLE_GROUP + "=1 AND " +
Phone.NUMBER + " LIKE ?";
String[] selectionArgs = {"%" + phoneSubStr + "%"};
Cursor phoneCursor = resolver.query(Phone.CONTENT_URI,
projection,
selection,
selectionArgs,
null);
String[] projection = new String[] {StructuredName.GIVEN_NAME,
StructuredName.FAMILY_NAME,
StructuredName.RAW_CONTACT_ID,
StructuredName.CONTACT_ID};
String selection = StructuredName.CONTACT_ID+ " = ? AND " +
Data.MIMETYPE + " = '" + StructuredName.CONTENT_ITEM_TYPE +"'";
String[] selectionArgs = new String[] {contact.id};
Cursor nameCursor = resolver.query(Data.CONTENT_URI,
projection,
selection,
selectionArgs,
null);
68. Scanning 101
MediaScannerConnection conn = new MediaScannerConnection(this,
new MediaScannerConnectionClient(){
public void onMediaScannerConnected(){
scanFile("/some/path/SomeSong.mp3", "audio/mpeg3");
scanFile("/some/other/path/IMG1999.jpg", "image/jpeg");
}
public void onScanCompleted(String path, Uri uri){
Log.d("LogTag", "Media scanned uir=" + uri.toString());
}
});
conn.connect();
72. Use Other Apps
private static final int SELECT_VIDEO = 1;
private Uri videoUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
Button vidBtn = (Button) findViewById(R.id.vidBtn);
vidBtn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View button) {
Intent videoChooser = new Intent(Intent.ACTION_GET_CONTENT);
videoChooser.setType("video/*");
startActivityForResult(videoChooser, SELECT_VIDEO);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == SELECT_VIDEO && resultCode == RESULT_OK){
videoUri = data.getData();
}
}
75. Dissolve Animation
private void nextSlide() {
AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);
if ((count % 2) == 0) {
animation = new AlphaAnimation(1.0f, 0.0f);
}
animation.setStartOffset(TIME_PER_SLIDE);
animation.setDuration(TIME_PER_SLIDE);
animation.setFillAfter(true);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
if (playingSlides){
nextImage = getNextImage();
ImageView backgroundImage =
(count % 2 == 0) ? rightSlide : leftSlide;
backgroundImage.setImageBitmap(nextImage);
count++;
nextSlide();
}
}
});
rightSlide.startAnimation(animation);
currentImage = nextImage;
}
77. Animators
ImageView backgroundImage = (count % 2 == 0) ? rightSlide : leftSlide;
ObjectAnimator anim =
ObjectAnimator.ofFloat(backgroundImage, "alpha", 0.0f, 1.0f);
anim.addListener(new AnimatorListenerAdapter(){
public void onAnimationEnd(Animator animator){
nextSlide();
}
});
81. playBtn.setOnClickListener(new OnClickListener(){
private Handler handler = new Handler();
MediaPlayer player = null;
long maxTime = 15L*1000; // 15 seconds
long timeLeft = maxTime;
Audio Preview
Runnable autoStop;
@Override
public void onClick(View view) {
if (player == null){
player = MediaPlayer.create(activity, song.uri);
}
if (!playingSongs.contains(song.id)){ Start/Resume
player.start();
playingSongs.add(song.id);
autoStop = new Runnable(){
Timer
@Override
public void run() {
player.pause();
player.seekTo(0);
playingSongs.remove(song.id);
playBtn.setText(R.string.play);
timeLeft = maxTime;
}
};
handler.postDelayed(autoStop, timeLeft);
playBtn.setText(R.string.pause);
} else {
player.pause();
Pause
playingSongs.remove(song.id);
timeLeft = maxTime - player.getCurrentPosition();
playBtn.setText(R.string.play);
Calc time left
handler.removeCallbacks(autoStop);
}
}
});
84. Handler?
Thread Pipeline Thread
Looper.prepare();
handler = new Handler();
// handle tasks in queue
Looper.loop();
// long running task
Message msg =
handler.obtainMessage();
handler.sendMessage(msg);
85. Handler?
Thread Pipeline Thread Thread
Looper.prepare();
handler = new Handler();
// handle tasks in queue
// long running task
handler.post(
Looper.loop(); new Runnable() {...
});
// long running task
Message msg =
handler.obtainMessage();
handler.sendMessage(msg);
86. Theme Muzak
private MediaPlayer player;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupThemeMusic();
}
@Override
public void onResume() {
super.onResume();
if (player != null){
player.start();
}
@Override
}
public void onPause(){
super.onPause();
if (player != null && player.isPlaying()){
player.pause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (player != null && player.isPlaying()){
player.stop();
}
player.release();
}
89. Video Playback
Uri videoUri = ...;
VideoView video = (VideoView) findViewById(R.id.video);
video.setVideoURI(videoUri);
MediaController controller = new MediaController(this);
controller.setMediaPlayer(video);
video.setMediaController(controller);
video.requestFocus();
video.start();
92. Use the Camera (app)
import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoUri = getContentResolver().insert(
EXTERNAL_CONTENT_URI, new ContentValues());
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intent,TAKE_PHOTO);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && requestCode == TAKE_PHOTO){
ImageView img = (ImageView) findViewById(R.id.photoThumb);
InputStream stream =
getContentResolver().openInputStream(photoUri);
Bitmap bmp = BitmapFactory.decodeStream(stream);
img.setImageBitmap(bmp);
}
}
96. Correctly Oriented
Options photoImageOptions = new BitmapFactory.Options();
Matrix matrix = new Matrix();
ExifInterface ei = new ExifInterface(somePath);
int orientation = ei.getAttributeInt(TAG_ORIENTATION, ORIENTATION_UNDEFINED);
int angle = 0;
switch (orientation) {
case ORIENTATION_ROTATE_90 : angle=90; break;
case ORIENTATION_ROTATE_180 : angle=180; break;
case ORIENTATION_ROTATE_270 : angle=270; break;
default: break;
}
Bitmap bm = BitmapFactory.decodeFile(path, photoImageOptions);
if (angle > 0){
matrix.setRotate(angle);
Bitmap bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(),
matrix, true);
}
97. Geo Tagging
ExifInterface exif = new ExifInterface(filename);
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
exif.saveAttributes();
“dd/1,mm/1,ss/1”
99. Getting a (geo) fix
LocationManager mgr = getSystemService(LOCATION_SERVICE);
for (String provider : mgr.getAllProviders()){
Location last = mgr.getLastKnownLocation(last);
mgr.requestLocationUpdates(provider, 60000, 500, new LocationListener(){
public void onLocationChanged(Location loc){
// do stuff
}
// other methods
});
}
101. Uploading a photo
String url = "http://my/server";
HttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpPost post = new HttpPost(url);
File imgFile = new File("/path/to/my/image");
MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
entity.addPart("image", new FileBody(imgFile));
post.setEntity(entity);
HttpResponse response = client.execute(httpPost, localContext);
103. Upload Service
UploadService extends IntentService {
protected void onHandleIntent(Intent i){
String imgPath = i.getStringExtra("file");
// upload code goes here
}
}
Intent i = new Intent(this, UploadService.class);
i.putExtra("file", "/path/to/my/image");
startService(i);
108. Video Preview
private SurfaceHolder holder;
private Camera camera;
private MediaRecorder mediaRecorder;
private File tempFile;
private SurfaceView preview;
private boolean isRecording = false;
private final int maxDurationInMs = 20000;
private final long maxFileSizeInBytes = 500000;
private final int videoFramesPerSecond = 20;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
preview = new SurfaceView(this);
holder = preview.getHolder();
holder.addCallback(cameraman);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
setContentView(preview);
tempFile = new File(getCacheDir(), "temp.mov");
if (tempFile.length() > 0){
tempFile.delete();
}
}
109. Video Preview
private Callback cameraman = new Callback(){
@Override
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
camera.setPreviewDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width,int height) {
Parameters params = camera.getParameters();
List<Size> sizes = params.getSupportedPreviewSizes();
Size optimalSize = getOptimalPreviewSize(sizes, width, height);
params.setPreviewSize(optimalSize.width, optimalSize.height);
camera.setParameters(params);
camera.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
camera.stopPreview();
camera.release();
}
};
110. Video Preview
private void startRecording(){
if (isRecording){
return;
}
isRecording = true;
camera.unlock();
mediaRecorder = new MediaRecorder();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mediaRecorder.setMaxDuration(maxDurationInMs);
mediaRecorder.setOutputFile(tempFile.getPath());
mediaRecorder.setVideoFrameRate(videoFramesPerSecond);
mediaRecorder.setVideoSize(preview.getWidth(), preview.getHeight());
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mediaRecorder.setPreviewDisplay(holder.getSurface());
mediaRecorder.setMaxFileSize(maxFileSizeInBytes);
mediaRecorder.prepare();
mediaRecorder.start();
}