OKHttp 使用注意

1.若想在 Callback 中修改畫面(包含產生 Dialog, Toast 等等),必須使用 runonUiThread 包含要更改的畫面的行為,如下為使用 OKHttp 包裝器的方法宣告,
  public static void sendPostWithJSONFormat(String URL, Map<String, String> requestParameter, Callback callback) {  
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody requestBody = RequestBody.create(JSON, new JSONObject(requestParameter).toString());
Request request = new Request.Builder().url(URL).post(requestBody).build();
Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

以下是使用該包裝器方法的範例
    OKHttpWrapper.sendPostWithJSONFormat(URL, httpParameter, new Callback() {  
@Override
public void onResponse(Call call, Response response) throws IOException {
//更改畫面行為
}

@Override
public void onFailure(Call call, IOException e) {
//更改畫面行為
}
});

重點在於第 4 行和第 9 行若有更改畫面的行為必須使用 runOnUiThread 去包含。否則會出現以下 Exception:
E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher  
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.app.Dialog.<init>(Dialog.java:119)
at android.app.AlertDialog.<init>(AlertDialog.java:200)
at android.app.AlertDialog$Builder.create(AlertDialog.java:1086)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:153)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)

 

Orignal From: OKHttp 使用注意

在裝置上產生 2 個相同的啟動圖示

情境:
安裝 App 後在裝置上產生 2 個該 App 的啟動圖示

原因:
通常是在 AndroidManifest.xml 中的 Activity,同時存在 2 個如下的 intent-filter 內容
      <intent-filter>  
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

如下面的 SplashActivity 和 MainActivity 都有相同的 intent-filter
...    
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="2 launcher icon"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity android:name=".splash.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>

 

解法:
移除其中一個相同的 intent-filter

Orignal From: 在裝置上產生 2 個相同的啟動圖示

Android Studio 在專案中引用 AAR

使用 Android Studio 並在專案中引用 AAR,需要 2 個步驟

1.把要使用的 AAR 檔拷貝到 app/libs 目錄,完成後如下圖


2.右鍵點擊專案 -> Open Module Settings

2-1.選擇左方的 Modules 的 app -> 點擊左上方的 + 號,如下圖
2-2. 在 Create New Module 視窗選擇 Import .JAR/.AAR Package -> 點擊 next


2-3.在 import Module from Library 視窗的 File Name: 選擇第1步驟的 AAR 檔案位置 -> 點擊 OK -> Subproject name: 不必修改 -> 點擊 Finish -> Gradle 會自動更新,更新完成後點擊 OK

2-4.右鍵點擊專案 -> Open Module Settings -> 左方 Modules 選擇 app -> 點擊右上方 Dependencies -> 點擊最右上方的 + 號 -> 選擇 3 Module dependency


2-5.在 Choose Modules 視窗選擇 CommonTool-debug -> 點擊 OK -> 在 Project Structure 視窗點擊 OK -> 完成

 

Orignal From: Android Studio 在專案中引用 AAR

在Activity中判斷滑動手勢

在 Activity 判斷滑動手勢主要是透過 View.OnTouchListener 和 GestureDetector.OnGestureListener

透過讓 Activity 繼承 GestureDetector.OnGestureListener 並實作其中的 onFling 方法,在 onFling 方法中判斷第1個按下點的 X 座標和第2個按下點的 X 座標是否超過標準值來決定是否為滑動手勢。

經過測試即使是嵌入在 Activity 的 ListView 也可以成功偵測滑動手勢。
import android.support.constraint.ConstraintLayout;  
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class FlingGestureExampleMainActivity extends AppCompatActivity implements View.OnTouchListener,
GestureDetector.OnGestureListener{

private static final String TAG = FlingGestureExampleMainActivity.class.getSimpleName();

//main ui
private ConstraintLayout mBasicLayout;
private GestureDetector mGesetureDetector;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fling_gesture_example_main_activity);

mBasicLayout = findViewById(R.id.basic_layout_activity);
mBasicLayout.setOnTouchListener(this);
mBasicLayout.setLongClickable(true);
mGesetureDetector = new GestureDetector((GestureDetector.OnGestureListener) this);
}

@Override
public boolean onDown(MotionEvent e) {
return false;
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}

@Override
public void onLongPress(MotionEvent e) {

}

@Override
public boolean onFling(MotionEvent firstMotion, MotionEvent secondMotion, float velocityX, float velocityY) {
final int FLING_MIN_DISTANCE = 100;
final int FLING_MIN_VELOCITY = 200;

boolean isFlingToLeft = firstMotion.getX() - secondMotion.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY;
if (isFlingToLeft) {
Log.d(TAG, "to left");
}
boolean isFlingToRight = firstMotion.getX() - secondMotion.getX() < FLING_MIN_DISTANCE && Math.abs(velocityX) < FLING_MIN_VELOCITY;
if (isFlingToRight) {
Log.d(TAG, "to right");
}
return false;
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return mGesetureDetector.onTouchEvent(motionEvent);
}
}

主要的重點為讓 Activity實作 onDown, onShowPress, on SingleTapUp, onScroll, onLongPress, onFling, onTouch 方法,
並在 onFling 方法判斷滑動的邏輯。
以及第23~26行的手勢和 layout 物件的連結

layout檔(fling_gesture_example_main_activity.xml)
<?xml version="1.0" encoding="utf-8"?>  
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/basic_layout_activity"
tools:context=".FlingGestureExampleMainActivity">

<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
.../>

</android.support.constraint.ConstraintLayout>

重點為 ConstraintLayout 建立 id,以便在程式碼中參考。

以上便是讓 Activity 加入滑動手勢的判斷,若是想讓該 Activity中
的 ListView 也可偵測滑動,作法相當簡單,直接讓該 ListView 呼叫 setOnTouchListener 和 setLongClickable 即可。
        mPatrolPointListView = getListView();  
mPatrolPointListViewAdapter =
new PatrolPointListViewAdapter(this, mPatrolPointListPresenter.getPatrolPointItemLayout(firstPage));
setListAdapter(mPatrolPointListViewAdapter);
...

mPatrolPointListView.setOnTouchListener(this);
mPatrolPointListView.setLongClickable(true);

 

Orignal From: 在Activity中判斷滑動手勢

android.util.Log 列印訊息內容太多無法完全顯示

使用 android.util.Log 列印訊息時,若訊息內容太多就會出現無法完全顯示訊息內容的情況。

解決的方法就是分段列印,如下
    private void logLongMessage(String TAG, String message) {  
int maxMessageSize = 2000;
for (int i = 0; i <= message.length() / maxMessageSize; i++) {
int start = i * maxMessageSize;
int end = (i + 1) * maxMessageSize;
end = end > message.length() ? message.length() : end;
Log.d(TAG, message.substring(start, end));
}
}

 

Orignal From: android.util.Log 列印訊息內容太多無法完全顯示

使用 Room DAO 存取資料 (Room)

要使用 Room 存取資料,需要使用 DAO。

這組 DAO 物件形成了 Room 的主要組件,因為每個 DAO 都包含提供對資料庫的抽象訪問方法。

通過使用 DAO 類別而不是查詢構建器或直接查詢來訪問資料庫,使用者可以分離出資料庫架構的不同元件。

此外,DAO 可在測試應用程序時輕鬆模擬資料庫訪問。

注意:
在加入 DAO 類別之前,先在 app 的 build.gradle 加入相依性。

DAO 可以是 interface,也可以是抽象類別。如果是抽象類別,它可以選擇有一個構造函數,它將 RoomDatabase 作為唯一的參數。Room 在編譯時期建立每個 DAO 實作。

注意:
Room 並不支援在 Main thread 上存取資料庫,因為可能會長時間鎖定 UI。

如果要讓 Room 支援在其他執行緒存取資料庫則必須在建構時呼叫allowMainThreadQueries() 方法。

異步查詢 – 若是查詢會返回 LiveData 或 Flowable 實例 - 不受此規則的約束,因為它們在需要時在後台線程上異步運行查詢

 

Define methods for convenience


可以使用 DAO 類別表示多個便捷查詢。

Insert


當建立 DAO 方法並使用 @Insert 註釋時,Room 會生成一個實現,該實現在單一事務(single transaction)中將所有參數插入到資料庫中。
@Dao  
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);

@Insert
public void insertBothUsers(User user1, User user2);

@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}

如果 @Insert 方法只接收 1 個參數,則它可以返回 long,這是該插入項目的新 rowId。如果參數是陣列或集合,則應返回 long [] 或 List <Long>。

有關更多詳細信息,請參閱 @Insert 的文件,以及 SQLite documenation for rowid tables。

Update


Update 修改資料庫中作為參數給出的一組實體。它使用每個實體的主鍵做查詢。
@Dao  
public interface MyDao {
@Update
public void updateUsers(User... users);
}

雖然通常沒有必要,但可以讓此方法返回一個 int 值,表示資料庫中更新的行數。

 

Delete


Delete 從資料庫中刪除一組從參數傳入的實體。使用主鍵來查找要刪除的實體。
@Dao  
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}

雖然通常沒有必要,但可以讓此方法返回一個 int 值,表示從資料庫中刪除的行數。

 

Query for information


@Query 是 DAO 類別中使用的主要註釋。它可以對資料庫執行讀/寫操作。

每個 @Query 方法都在編譯時進行驗證,因此如果查詢出現問題,則會發生編譯錯誤而不是運行時失敗。

Room 也會驗證查詢的返回值,避免返回的物件中的屬性名稱與查詢的相應列名稱不相符,Room 使用以下兩個方法之一

  • 如果只有一些屬性名稱相符,它會發出警告。

  • 如果沒有屬性名稱相符,則會發出錯誤。


Simple queries
@Dao  
public interface MyDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}

以上是一個非常簡單的查詢,可以取得所有用戶。在編譯時,Room 知道正在查詢用戶表中的所有列。如果查詢包含語法錯誤,或者資料庫中不存在該資料表,則在應用程序編譯時,Room 會顯示包含相應消息的錯誤。

Passing parameters into the query


大多數情況下,需要將參數傳遞給查詢以執行過濾操作,例如僅顯示年齡超過特定年齡的用戶。

要傳入參數,請在 Room 註釋中使用方法參數,如以下代碼段所示
@Dao  
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}

在編譯時處理此查詢時,Room 會將:minAge 綁定參數與 minAge 方法的參數。

Room 使用參數名稱檢查是否相符。如果存在不相符,則在應用編譯時會發生錯誤。

還可以在查詢中多次傳遞多個參數或引用它們,如以下代碼段所示:
@Dao  
public interface MyDao {
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
"OR last_name LIKE :search")
public List<User> findUserWithName(String search);
}

 

Returning subsets of columns


大多數情況下,只需要獲得實體的幾個屬性。

例如,UI 可能只顯示用戶的名字和姓氏,而不是用戶的每一個訊息。

通過僅提取 UI 顯示的列,可以節省寶貴的資源,並且讓查詢可以更快地完成。

Room 允許從查詢中返回任何基於 Java 的對象,只要結果可以映射到返回的對象即可。

例如,可以建立以下(POJO)來獲取用戶的名字和姓氏
public class NameTuple {  
@ColumnInfo(name = "first_name")
public String firstName;

@ColumnInfo(name = "last_name")
public String lastName;
}

現在,可以在查詢方法中使用此 POJO:
@Dao  
public interface MyDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}

Room 了解查詢返回 first_name 和 last_name 列的值,並且這些值可以映射到 NameTuple 類別的屬性中。因此,Room 可以生成正確的代碼。

如果查詢返回太多列,或者有 NameTuple 類別中不存在的列,則 Room 會顯示警告。

注意:
這些 POJO 也可以使用 @Embedded 註釋。

 

Passing a collection of arguments


某些查詢可能要求傳入可變數量的參數,並且在運行時之前不知道參數的確切數量。

例如,可能希望從區域子集中檢索有關所有用戶的信息。

Room 了解參數何時表示集合,並根據提供的參數數量在運行時自動擴展它。
@Dao  
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

 

Observable queries


執行查詢時,通常希望應用程序的UI在資料更改時自動更新。

要實現此目的,請在查詢方法描述中使用 LiveData 類型的返回值。

Room 會產生所有必要的代碼,以便在更新資料庫時更新 LiveData。
@Dao  
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

注意:

從版本 1.0 開始,Room 使用查詢中訪問的表列表來決定是否更新 LiveData 的實例。

 

Reactive queries with RxJava


Room 為 RxJava2 類型的返回值提供以下支持:

  • @Query methods: Room supports return values of type Publisher, Flowable, and Observable

  • @Insert, @Update, and @Delete methods: Room 2.1.0 and higher supports return values of type Completable, Single<T>, and Maybe<T>


要使用此功能,請在 app 的 build.gradle 文件中包含最新版本的 rxjava2:
dependencies {  
implementation 'androidx.room:room-rxjava2:2.1.0-alpha02'
}

以下代碼示範如何使用這些返回類型:
@Dao  
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);

// Emits the number of users added to the database.
@Insert
public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

// Makes sure that the operation finishes successfully.
@Insert
public Completable insertLargeNumberOfUsers(User... users);

/* Emits the number of users removed from the database. Always emits at
least one user. */
@Delete
public Single<Integer> deleteUsers(List<User> users);
}

有關更多詳細信息,請參閱 Google Developers Room and RxJava。

 

Direct cursor access


如果需要直接存取返回的行,則可以從查詢中返回 Cursor 物件,如以下代碼段所示:
@Dao  
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}

警告:
非常不建議使用 Cursor API,因為它不能保證行是否存在或行包含的值。

 

Query multiple tables


某些查詢可能需要訪問多個表來計算結果。Room 允許編寫任何查詢,因此也可以查詢多個資料表。

此外,如果響應是可觀察的數據類型(如 Flowable 或 LiveData),則會監視查詢中引用的所有資料表以檢查是否無效。

以下代碼段顯示如何查詢不同的資料表:
@Dao  
public interface MyDao {
@Query("SELECT * FROM book " +
"INNER JOIN loan ON loan.book_id = book.id " +
"INNER JOIN user ON user.id = loan.user_id " +
"WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);
}

還可以從這些查詢中返回 POJO。
@Dao  
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName " +
"FROM user, pet " +
"WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();

// You can also define this class in a separate file, as long as you add the
// "public" access modifier.
static class UserPet {
public String userName;
public String petName;
}
}

 

Orignal From: 使用 Room DAO 存取資料 (Room)

在資料庫中建立 View (Room)

Room 2.1.0 及更高版本提供對 SQLite 資料庫的 View 功能,允許使用者將查詢封裝到類別中。Room 將這些查詢支持的類別稱為 View,作用和使用 DAO 的簡單資料物件相同。

注意:
與實體(Entitiy)一樣,可以針對 View 運行 SELECT 語句。但是無法對 View 進行 INSERT, UPDATE 或 DELETE 語句。

 

Create a view


要建立 View,請將 @DatabaseView 註釋加入類別。將註釋的值設置為該類別應表示的查詢。以下代碼段提供了一個 View 範例:
@DatabaseView("SELECT user.id, user.name, user.departmentId," +  
"department.name AS departmentName FROM user " +
"INNER JOIN department ON user.departmentId = department.id")
public class UserDetail {
public long id;
public String name;
public long departmentId;
public String departmentName;
}

 

 

Associate a view with your database


要將該 View 作為資料庫的一部分,請在 @Database 註釋中加入 views 屬性
@Database(entities = {User.class}, views = {UserDetail.class},  
version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}

 

Orignal From: 在資料庫中建立 View (Room)

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Affiliate Network Reviews