Android 開發工具進化史

          Rick(台灣大哥大-MyMusic)   

                                                             開開心心的導入,安安穩穩的加班

Outline

• Android Architecture

• AndroidX

• Jetpack

• Database

• Image Process

• Http Connection

• Dependency Injection

• ReactiveX

• Language

Android Architecture

• No Architecture
• MVC(Model-View-Controller)
• MVP(Model-View-Presenter)
MVVM(Model-View-ViewModel)

No Architecture

override fun onCreate(savedInstanceState: Bundle?){
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    //100000 lines
}

No Architecture

override fun onCreate(savedInstanceState: Bundle?){
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    try{
        //100000 lines
    } catch (exception: Exception){
        //handle
    }
}

No Architecture

架構軟體如同蓋教堂,完成之後就可以開始祈禱惹。

                                                                  - Sam Redwine

MVC

MVP

MVVM

​MVC ​MVP ​MVVM
​Google  daddy support Yes No No
​Learning curve Easy Normal Normal
​Testability Hard Easy Easy
​Lifecycle-aware Hard Hard Easy

MVC vs MVP vs MVVM

What is AndroidX

AndroidX is a new library refactored from the Support Library.

AndroidX means Android extension

docx

Android KTX

Rxjava

...

android.useAndroidX:

When set to true, the Android plugin uses the appropriate AndroidX library instead of a Support Library. The flag is false by default if it is not specified.

android.enableJetifier:

When set to true, the Android plugin automatically migrates existing third-party libraries to use AndroidX by rewriting their binaries. The flag is false by default if it is not specified.

Android Jetpack

Database

  • SQLite

  • GreenDao

  • ORMLite

  • Realm

  • Room[Google support]

SQLite

public static final String TABLE_NAME = "item";
public static final String KEY_ID = "_id";
public static final String DATETIME_COLUMN = "datetime";
public static final String COLOR_COLUMN = "color";
public static final String CREATE_TABLE = 
    "CREATE TABLE " + TABLE_NAME + " (" + 
    KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
    DATETIME_COLUMN + " INTEGER NOT NULL)";

declare

public List<Item> getAll() {
    List<Item> result = new ArrayList<>();
    Cursor cursor = db.query(
            TABLE_NAME, null, null, null, null, null, null, null);
    while (cursor.moveToNext()) {
        result.add(getRecord(cursor));
    }
    cursor.close();
    return result;
}
public boolean delete(long id){
    String where = KEY_ID + "=" + id;
    return db.delete(TABLE_NAME, where , null) > 0;
}
public Item insert(Item item) {
    ContentValues cv = new ContentValues();     
    cv.put(DATETIME_COLUMN, item.getDatetime());
    cv.put(COLOR_COLUMN, item.getColor().parseColor());
    long id = db.insert(TABLE_NAME, null, cv);
    item.setId(id);
    return item;
}

query

delete

insert

public boolean update(Item item) {
    ContentValues cv = new ContentValues();   
    cv.put(DATETIME_COLUMN, item.getDatetime());
    cv.put(COLOR_COLUMN, item.getColor().parseColor());
    String where = KEY_ID + "=" + item.getId();
    return db.update(TABLE_NAME, cv, where, null) > 0;         
}

update

GreenDao

@Entity
public class Student {
   @Id(autoincrement = true)
   private Long id;
   @NonNull
   private String name;
}

declare

public List<Student> getAllStudent() {
    return studentDao.loadAll();
}
public boolean delete(Student student){
    return studentDao.delete(student);
}
public long insert(Student student) {
    long id = studentDao.insert(student);
}

query

delete

insert

public boolean update(Student student) {
    studentDao.update(student);       
}

update

ORMLite

@DatabaseTable(tableName = "User")
public class User {
    @DatabaseField(generatedId = true) private int id;
    @DatabaseField private String name;
    @DatabaseField private int sort;
    @ForeignCollectionField private ForeignCollection<Group> groups;
}

declare

public List<T> getAll() {
    try {
        return dao.queryBuilder().orderBy("id", false).query();
    } catch (SQLException e) {
        e.printStackTrace();
        return Collections.emptyList();
    }
}
public int deleteById(ID primaryKey) {
    int id;
    try {
        id = dao.deleteById(primaryKey);
    } catch (SQLException e) {
        e.printStackTrace();
        id = -1;
    }
    return id;
}
public int insert(T item) {
    int id;
    try {
        id = dao.create(item);
    } catch (SQLException e) {
        e.printStackTrace();
        id = -1;
    }
    return id;
}

query

delete

insert

public int update(T item) {
    int id;
    try {
        id = dao.update(item);
    } catch (SQLException e) {
        e.printStackTrace();
        id = -1;
    }
    return id;
}

update

Realm

public class User extends RealmObject {
    @PrimaryKey
    private String name;
    private int age;
    private RealmList<User> friends;

    @Ignore
    private int sessionId;
}

declare

public RealmResults<User> getAll() {
    RealmResults<User> result = mRealm.where(User.class).findAll();
}
public int delete(int userId) {
    realm.executeTransaction(new Realm.Transaction(){
        @Override
        public void execute(Realm realm){
            RealmResults<User> users = realm.where(User.class).equalTo(Message.USER_ID, userId).findAll();
            users.clear();
        }
    }
}
public int insert(final String name, final int age) {
    realm.executeTransaction(new Realm.Transaction(){
        @Override
        public void execute(Realm realm){
            User user = realm.createObject(User.class);
            user.setName(name);
            user.setAge(age);
        }
    }
}

query

delete

insert

public int update(String name, int age) {
    realm.executeTransaction(new Realm.Transaction(){
        @Override
        public void execute(Realm realm){
            User user = realm.where(User.class).findFirst();
            user.setName(name);
            user.setAge(age);
        }
    }
}

update

Room

@Entity
public class User{
    @PrimaryKey
    public int uid;
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
}
Context context = ApplicationProvider.getApplicationContext();
db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
userDao = db.getUserDao();

User user = TestUtil.createUser(3);
user.setName("george");
userDao.insert(user);
@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

我們為什麼選擇 Room?

它有Google把拔

Querying multiple tables

@Dao
interface MyDao {
    @Query(
        "SELECT user.name AS userName, pet.name AS petName " +
        "FROM user, pet " +
        "WHERE user.id = pet.user_id"
    )
    fun loadUserAndPetNames(): LiveData<List<UserPet>>

    // You can also define this class in a separate file.
    data class UserPet(val userName: String?, val petName: String?)
}

Image Process

  • Universal Image Loader

  • Picasso[Square]

  • Fresco[Facebook]

  • Glide[This is not an official Google product.]

Universal Image Loader

Really have no time for development... so I stop project maintaining since Nov 27 :(

UIL [27.11.2011 - 27.11.2015]

Thanks to all developers for your support :)

Picasso vs Fresco vs Glide

Picasso Fresco Glide
Support gif No Yes Yes
Circle image No Yes Yes(4.0+)
Method size 850 5,800 2,800
Support webP Yes Yes Yes
Android lifecycle No No Yes
Cache Large Normal Small
Container ImageView DraweeView ImageView

Http Connection

  • HttpClient

  • HttpURLConnection

  • AsyncTask

  • OkHttp

  • Volley

  • Retrofit

HttpClient vs HttpURLConnection

For Gingerbread and better, HttpURLConnection is the best choice. New applications should use HttpURLConnection.

  - Google (29 September 2011)

AsyncTask + HttpURLConnection

 class URLTask extends AsyncTask<String, Integer, String> {  
    @Override  
    protected void onPreExecute(){  
            
    }  
        
    @Override  
    protected String doInBackground(String... params) { 
         try {
            URL url = new URL(params[0]);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setDoInput(true);
            urlConnection.setDoOutput(true);
            urlConnection.setRequestProperty("Content-Type", "application/json");
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("Authorization", "someAuthString");
            if (this.postData != null) {
                OutputStreamWriter writer = new OutputStreamWriter(urlConnection.getOutputStream());
                writer.write(postData.toString());
                writer.flush();
            }
            int statusCode = urlConnection.getResponseCode();
            if (statusCode ==  200) {
                InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
                String response = convertInputStreamToString(inputStream);
            } else {
                // Status code is not 200
            }

        } catch (Exception e) {
            Log.d(TAG, e.getLocalizedMessage());
        }
    }

    @Override  
    protected void onProgressUpdate(Integer... values) {
            
    }  
        
    @Override  
    protected void onPostExecute(String result) {
            
    }
 }

Volley

val textView = findViewById<TextView>(R.id.text)
// ...

// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(this)
val url = "http://www.google.com"

// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
        Response.Listener<String> { response ->
            // Display the first 500 characters of the response string.
            textView.text = "Response is: ${response.substring(0, 500)}"
        },
        Response.ErrorListener { textView.text = "That didn't work!" })

// Add the request to the RequestQueue.
queue.add(stringRequest)

OkHttp

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
AsyncTask Volley OkHttp
readability No Yes Yes

AsyncTask vs Volley vs OkHttp  

HttpURLConnection

try{
    HttpURLConnection.setFollowRedirects(false);
    HttpURLConnection con = (HttpURLConnection) new URL(url).openConection();
    con.setRequestMethod("GET");
    con.setConnectTimeout(5000);
} catch(SocketTimeoutException e){
    e.printStackTrace();
} catch(IOException e){
    e.printStackTrace();
}

Volley

volleyRequest.setRetryPolicy(new DefaultRetryPolicy(
    MY_SOCKET_TIMEOUT_MS,
    DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
    DefaultRetryPolicy.DEFAULT_BACKOFF_MULTI));

OkHttp

OkHttpClient.Builder()
    .retryOnConnectionFailure(true)
    .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
    .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
    .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
    .followRedirects(false)
    .addIntercepteor(new RetryInterceptor())
    .addIntercepteor(new LoggerInterceptor())
    .build();

OkHttp Interceptor

HttpURLConnection vs Volley vs OkHttp 

HttpURLConnection  Volley OkHttp
Settings simple simple various choices

Retrofit

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

@POST("users/new")
Call<User> createUser(@Body User user);

class User{
    @SerializedName("user_name")
    private String userName;
    @SerializedName("user_age")
    private int userAge;
}

HttpURLConnection vs Volley vs OkHttp 

HttpURLConnection  Volley Retrofit
annotations to describe No No Yes

AsyncTask vs Volley vs Retrofit  

One Discussion Dashboard 25 Discussions
AsyncTask 941ms 4, 539ms 13, 957ms
Volley 560ms 2, 202ms 4, 275ms
Retrofit 312ms 889ms 1, 059ms

AsyncTask vs Volley vs Retrofit  

AsyncTask +
HttpURLConnection
Volley + HttpClient Retrofit + OkHttp
readability Hard Easy Easy
Interceptor No No Yes
Annotation describe No No Yes
Speed Slow Normal Fast
Class Singer{
    public Singer(){
        Album album = new Album();
        //...
    }
}

What is dependency injection?

  • new is evil
  • high coupling
  • hard to replace
  • hard to test
Class Singer{
    private Album album;
    public Singer(Album album){
        this.album = album;
        //...
    }
}

地方的物件需要注射

  • Reusability
  • Testing
  • Maintainability

Dependency Injection

  • Dagger2

  • Koin2

Dagger2

Dagger2

•Hard to learn

Stable

Flexible

Powerful

Fast in rumtime

•For Java and Kotlin

Koin

Koin

選擇哪一個?

小朋友才選擇

What is callback hell?

new Thread(new Runnable(){
    @Override
    public void run(){
        super.run();
        List<Album> albums = getAlbumFromApi();
        for(Album album : albums){
            List<Song> songs = album.getSongList();
            for(Song song : songs){
                if(song.playTime >= 300){
                    tmpList.add(song);
                    runOnUiThread(new Runnable(){
                        list.songName.setText(song.getName());
                    })
                }
            }
        }
    }
})

RxJava

Observable.fromIterable(getAlbumFormApi())
    .flatMap(new Function<Album, ObservalbeSource<Song>>(){
        @Override
        public ObservaleSource<Song> apply(Album album) throws Exception{
            return Observable.fromIterable(album.getSongs());
        }
    })
    .filter(new Preficate<Song>(){
        @Oberride
        public boolean test(@NonNull Song song) throws Exception{
            return song.Time > 300;
        }
    })
    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidScheduler.mainThread())
    .subscribe(new Consumer<Song>(){
        @Override
        public void accept(Song song) throws Exception{
            list.add(song);
        }
    })

RxJava(use Java Lambda)

Observable.fromIterable(getAlbumFormApi())
    .flatMap((Function<Album, ObservalbeSource<Song>>) album->Observable.fromIterable(album.getSongs()))
    .filter(s-> s.Time > 300)
    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidScheduler.mainThread())
    .subscribe(s->list.add(song));

RxJava(use Kotlin)

Observable.fromIterable(getAlbumFormApi())
    .flatMap<Song>{Observable.fromIterable<Song>{album.getSongs()}}
    .filter{it.Time > 300}
    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidScheduler.mainThread())
    .subscribe{list.add(it)}

Why we choice Rxjava

  • Fix callback hell
  • Asynchronous handle
  • Clean code

Language

  • Java

  • Kotlin

Java

Observable.fromIterable(getAlbumFormApi())
    .flatMap(new Function<Album, ObservalbeSource<Song>>(){
        @Override
        public ObservaleSource<Song> apply(Album album) throws Exception{
            return Observable.fromIterable(album.getSongs());
        }
    })
    .filter(new Preficate<Song>(){
        @Oberride
        public boolean test(@NonNull Song song) throws Exception{
            return song.Time > 300;
        }
    })
    .subscribeOn(Schedulers.newThread())
    .observeOn(AndroidScheduler.mainThread())
    .subscribe(new Consumer<Song>(){
        @Override
        public void accept(Song song) throws Exception{
            list.add(song);
        }
    })

Kotlin

Observable.fromIterable(getAlbumFormApi())
.flatMap<Song>{Observable.fromIterable<Song>{album.getSongs()}}
.filter{it.Time > 300}
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidScheduler.mainThread())
.subscribe{list.add(it)}

Kotlin is best choice

Conclusion

Item name Better choice
Architecture MVVM(Google)
Http Connection Retrofit
Database Room(Google)
Image Process Glide
Dependency Injection Koin
ReactiveX RxJava
Language Kotlin(Google)

Q & A

Thank you.

Android 開發工具進化史

By givemepass

Android 開發工具進化史

描述 Android 開發中所需的工具以及一些歷史

  • 1,331