Android Development, the Right Way
-
Upload
jayson-basanes -
Category
Software
-
view
442 -
download
2
Transcript of Android Development, the Right Way
Download Lifebit for iOS and [email protected]
ANDROID STUDIO (0.8+)
• Gradle-based build system
• Improved Interface Designer
• Better code completion and refactoring
• Code analysis
• Faster in every aspect
Download Lifebit for iOS and [email protected]
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.12.+' } } apply plugin: 'com.android.application' dependencies { compile 'com.netflix.rxjava:rxjava-core:0.19.1' compile 'com.netflix.rxjava:rxjava-android:0.19.1' compile 'com.squareup.retrofit:retrofit:1.6.1' compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0' compile 'com.squareup.okhttp:okhttp:2.0.0' } android { compileSdkVersion 19 buildToolsVersion "20.0.0" defaultConfig { minSdkVersion 15 targetSdkVersion 19 versionCode 1 versionName "1.0" } buildTypes { debug { runProguard false } release { signingConfig signingConfigs.release runProguard true proguardFiles 'proguard-rules.pro' } } }
Download Lifebit for iOS and [email protected]
Build Variants productFlavors { demo { applicationId "com.buildsystemexample.app.demo" versionName "1.0-demo" } full { applicationId "com.buildsystemexample.app.full" versionName "1.0-full" } }
Download Lifebit for iOS and [email protected]
GENYMOTION• A lot faster than the Android
Emulator
• GPS, Battery, Accelerometer
• Simpler interface
Download Lifebit for iOS and [email protected]
VERSION CONTROL (GIT)
• SourceTree + Bitbucket
• History
• Collaboration
• Multiple build versions
• Blame games
Download Lifebit for iOS and [email protected]
CRASHLYTICS
• Crash reporting
• Logging
• Analytics
• Distribution
Download Lifebit for iOS and [email protected]
RETROFIT
• REST client for Android and Java
• Stupidly easy to use.
• Uses Gson for JSON parsing
square.github.io/retrofit
Download Lifebit for iOS and [email protected]
RETROFIT public interface LifebitService { @GET("/users/{user}/bits") List<Bit> getUserBits(@Path("user") String user); }
RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint("https://api.lifebit.com") .build(); ! LifebitService service = restAdapter.create(LifebitService.class);
List<Bit> bits = service.getUserBits(“shiki");
Profit!
Download Lifebit for iOS and [email protected]
OKHTTP
• Efficient HTTP Client. Alternative to Apache's HTTPClient and java.net.HttpUrlConnection.
• Can be used with Retrofit.
• From their site: Perseveres when the network is troublesome
square.github.io/okhttp
Download Lifebit for iOS and [email protected]
OKHTTP OkHttpClient client = new OkHttpClient(); ! String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); ! Response response = client.newCall(request).execute(); return response.body().string(); }
Download Lifebit for iOS and [email protected]
PICASSO
• Image downloading and caching library.
• Automatic memory and disk caching.
• Mostly one-liners to load a remote image into an ImageView.
• Supports: resizing, cropping, placeholders.
• Can use OkHttp
square.github.io/picasso
Download Lifebit for iOS and [email protected]
PICASSO
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Picasso.with(context) .load(url) .resize(50, 50) .centerCrop() .into(imageView)
Picasso.with(context) .load(url) .placeholder(R.drawable.user_placeholder) .error(R.drawable.user_placeholder_error) .into(imageView);
Download Lifebit for iOS and [email protected]
EVENTBUS OR OTTO
• publish-subscribe-style communication between components
github.com/greenrobot/EventBus or square.github.io/otto
Download Lifebit for iOS and [email protected]
EVENTBUS
public class MessageEvent { /* Additional fields if needed */ }
EventBus.getDefault().register(this); ! public void onEvent(MessageEvent event) { /* Do something */ };
MessageEvent event = new MessageEvent(); EventBus.getDefault().post(event);
Download Lifebit for iOS and [email protected]
BUTTER KNIFE
• View injection
• No more findViewById boilerplate
jakewharton.github.io/butterknife
Download Lifebit for iOS and [email protected]
BUTTER KNIFE
class ExampleActivity extends Activity { TextView title; TextView subtitle; TextView footer; ! @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); title = (TextView) findViewById(R.id.title); subtitle = (TextView) findViewById(R.id.subtitle); footer = (TextView) findViewById(R.id.footer); } }
The old way
Download Lifebit for iOS and [email protected]
BUTTER KNIFE
class ExampleActivity extends Activity { @InjectView(R.id.title) TextView title; @InjectView(R.id.subtitle) TextView subtitle; @InjectView(R.id.footer) TextView footer; ! @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.inject(this); } }
Using injection
Download Lifebit for iOS and [email protected]
BUTTER KNIFEEvents
@OnClick(R.id.submit) public void sayHi(Button button) { button.setText("Hello!"); }
Download Lifebit for iOS and [email protected]
ROBOLECTRIC
• Unit test framework
• Runs outside of the emulator
robolectric.org
Download Lifebit for iOS and [email protected]
ROBOLECTRIC
@RunWith(RobolectricTestRunner.class) public class MyActivityTest { ! @Test public void clickingButton_shouldChangeResultsViewText() throws Exception { Activity activity = Robolectric.buildActivity(MyActivity.class).create().get(); ! Button pressMeButton = (Button) activity.findViewById(R.id.press_me_button); TextView results = (TextView) activity.findViewById(R.id.results_text_view); ! pressMeButton.performClick(); String resultsText = results.getText().toString(); assertThat(resultsText, equalTo("Testing Android Rocks!")); } }
Download Lifebit for iOS and [email protected]
Beware of the 65K DEX methods limit
Unable to execute dex: method ID not in [0, 0xffff]: 65536 Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
• Choose third-party libraries wisely
• Use Proguard if you can’t avoid it
Download Lifebit for iOS and [email protected]
Use Proguard for release builds
buildTypes { debug { runProguard false } release { signingConfig signingConfigs.release runProguard true proguardFiles 'proguard-rules.pro' } }
• smaller, optimized, obfuscated builds
Download Lifebit for iOS and [email protected]
# Obfuscation parameters: #-dontobfuscate -useuniqueclassmembernames -keepattributes SourceFile,LineNumberTable -allowaccessmodification # Keep Jackson stuff -keep class org.codehaus.** { *; } -keep class com.fasterxml.jackson.annotation.** { *; } # Keep these for GSON and Jackson -keepattributes Signature -keepattributes *Annotation* -keepattributes EnclosingMethod # Keep Retrofit -keep class retrofit.** { *; } -keepclasseswithmembers class * { @retrofit.** *; } -keepclassmembers class * { @retrofit.** *; } # Keep Picasso -keep class com.squareup.picasso.** { *; } -keepclasseswithmembers class * { @com.squareup.picasso.** *; } -keepclassmembers class * { @com.squareup.picasso.** *; }
Download Lifebit for iOS and [email protected]
Prefer Maven dependencies instead of jar files
dependencies { compile 'com.squareup.okio:okio:1.0.+' compile 'com.squareup.okhttp:okhttp:2.0.+' ! compile 'com.squareup.retrofit:retrofit:1.7.0' }
• ez
Download Lifebit for iOS and [email protected]
Not Invented Here• yet-another-image-loading library
• Unless you can make something better in the little time that you have.
Download Lifebit for iOS and [email protected]
Load heavy objects on demandNot
class MyActivity extends Activity { private Service service; @Override public void onCreate(Bundle savedInstanceState) { ... service = restAdapter.create(LifebitService.class); } @OnClick(R.id.submit) public void onButtonClick(Button button) { List<Bit> bits = service.getBits(); // Do something with bits } }
Download Lifebit for iOS and [email protected]
Load heavy objects on demandNot Not
class MyActivity extends Activity { private Service service; ! Service getService() { if (service == null) { service = restAdapter.create(LifebitService.class); } return service; } ! @OnClick(R.id.submit) public void onButtonClick(Button button) { List<Bit> bits = getService().getBits(); // Do something with bits } }
Download Lifebit for iOS and [email protected]
Know when to use background threads• API calls
• Loading files
• Anything that’s gonna take more than a second
Download Lifebit for iOS and [email protected]
Know when to use background threads private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; } protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } } new DownloadFilesTask().execute(url1, url2, url3);
Download Lifebit for iOS and [email protected]
View Holder Pattern public class MyAdapter extends BaseAdapter { @Override public View getView(int position, View view, ViewGroup parent) { ViewHolder holder; if (view != null) { holder = (ViewHolder) view.getTag(); } else { view = inflater.inflate(R.layout.whatever, parent, false); holder = new ViewHolder(view); view.setTag(holder); } holder.name.setText("John Doe"); // etc... return view; } static class ViewHolder { @InjectView(R.id.title) TextView name; @InjectView(R.id.job_title) TextView jobTitle; public ViewHolder(View view) { ButterKnife.inject(this, view); } } }
Download Lifebit for iOS and [email protected]
Please do not ignore exceptions!
try { List<Bit> bits = service.getBits(); ... } catch (APIException e) { // Something terrible has happened // but I am too lazy to handle this exception. // So f*ck you users! ! // Logging is not considered handling an exception! Log.d(getClass().getName(), e.getMessage()); }
Download Lifebit for iOS and [email protected]
Download Lifebit for iOS and [email protected]
Please do not ignore exceptions!
try { List<Bit> bits = service.getBits(); ... } catch (APIException e) { // If you are sure that the message is safe for the end user: showDialog(e.getMessage()); }
Inform the user
Download Lifebit for iOS and [email protected]
Please do not ignore exceptions!Throw it up the call chain
private void loadBits() throws APIException { List<Bit> bits = service.getBits(); ... }
Download Lifebit for iOS and [email protected]
Please do not ignore exceptions!Crash that motherf*cker
try { List<Bit> bits = service.getBits(); ... } catch (APIException e) { // I am pretty sure that an exception will never happen, // but just in case: throw new RuntimeException(e); }
Download Lifebit for iOS and [email protected]
Please do not ignore exceptions!Log to Crashlytics
try { List<Bit> bits = service.getBits(); ... } catch (APIException e) { Crashlytics.logException(e); ! showDialog(e.getMessage()); }
Download Lifebit for iOS and [email protected]
Enforced Coding Style• Everyone can read everyone’s shit
• Unity through uniformity
• Automate!
Download Lifebit for iOS and [email protected]
Automated Tests• Robolectric, Robotium, whatever floats your boat
• Pays off in the long run
• Good luck convincing your boss ^_^x
Download Lifebit for iOS and [email protected]
Download Lifebit for iOS and [email protected]
Download Lifebit for iOS and [email protected]
Download Lifebit for iOS and [email protected]
REFERENCES
• androidweekly.net: Free weekly newsletter for the latest Android dev news, tutorials, articles, etc.
• android-arsenal.com: List of free and paid Android libraries
• github.com/futurice/android-best-practices: Android dev best practices
Download Lifebit for iOS and [email protected]
GLHF!Slides will be up at speakerdeck.com/shiki