分類
Uncategorized

Room (Persistence Library) 使用紀錄

Room 為 Android 官方推薦的 ORM 框架,該框架封裝了 SQLite 為底層並提供高層介面以方便使用者快速開發數據儲存。
官方連結:https://developer.android.com/topic/libraries/architecture/room
以下紀錄如何安裝及簡單的使用技巧。

1.加入Room 到你的專案中

1-1.在 Project 的 build.gradle 加入

allprojects {
    repositories {
        jcenter()
        google()
    }
}

1-2.在 Module 的 build.gradle 加入

dependencies {
...
    def room_version = "1.1.1"
    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin
...

 

2.建立相關元件

2-1.首先建立要儲存的類別(User)

import android.arch.persistence.room.Embedded;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
@Entity(tableName = "user")
public class User {
  @PrimaryKey
  private int id;
  private int listNumber;
  private boolean active;
  private String name;
  private String description;
  @Embedded
  private Account account;
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public boolean getActive() {
    return active;
  }
  public void setActive(boolean active) {
    this.active = active;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getDescription() {
    return description;
  }
  public void setDescription(String description) {
    this.description = description;
  }
  public int getListNumber() {
    return listNumber;
  }
  public void setListNumber(int listNumber) {
    this.listNumber = listNumber;
  }
  public Account getAccount() {
    return account;
  }
  public void setAccount(Account account) {
    this.account = account;
  }
}

注意以下幾點:
第5行使用 @Entity 註解並指定資料表名稱,也可以不指定資料表名稱,預設會使用類別名稱當作資料表名稱。
第8~9行使用 @PrimaryKey 指定主鍵,主鍵為 id
第15~16行自定義類別 Account 必須使用 @Embedded 註明
第18~64行為一般的 getter and setter 方法
自定義類別 Account 如下

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
@Entity(tableName = "account")
public class Account {
  @PrimaryKey
  @ColumnInfo(name = "account_id")
  private int id;
  @ColumnInfo(name = "account_name")
  private String name;
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

注意以下幾點
和 User 類別類似,唯一需要注意的是第 9 行和第 12 行的 @ColumnInfo() 註解。
當某個類別(Account)為另一個類別(User)的嵌入類別時,若欄位名稱有相同必須透過 @ColumnInfo 指定另一個名稱。
以第 9 行 name = “account_id” 為例,就是告訴 Room,Account 的 id 在資料表中欄位名稱為 account_id,避免和 User 的 id 欄位名稱重覆。

2-2.建立DAO(UserDAO)

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import java.util.List;
@Dao
public interface UserDao {
  @Query("SELECT * FROM user")
  public List<User> getAllUser();
  @Query("SELECT * FROM user WHERE active = :active")
  public List<User> getUserActive(boolean active);
  @Query("SELECT * FROM user WHERE listNumber < :lessNumber")
  public List<User> getUserListNumberLessThan(int lessNumber);
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  public void insertUser(User users);
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  public void insertUsers(List<User> users);
  @Update
  public void updateUsers(User... users);
  @Update
  public void updateUser(User user);
  @Delete
  public void deleteUser(User user);
  @Delete
  public void deleteUsers(List<User> users);
}

UserDAO 即為操作 User 的 Data Access Object,關於 User 的資料庫操作介面全部都會定義在這裡。只需要定義介面就好,Room 會自動實作其內容。
注意以下幾點
第9~10行使用建立 interface 並使用 @Dao 標示
第12~13行使用 @Query 標示該介面為 query 動作並在括號中指定要動作的內容。
Room 提供了4種註解@Query, @Update, @Insert, @Delete 這4種註解分別提供不同的動作,各個動作可以指定的內容請參考 API。
以第12~13行為例,呼叫 getAllUser 就等於 select * from user 代表回傳已儲存的所有 user 內容。
第15~16行代表回傳已儲存的所有 user 內容且 active 欄位必須符合傳入的參數(true or false)。
第18~19行代表回傳已儲存的所有 user 內容且 listnumber 欄位必須小於指定的參數。
第21~22行為插入資料用,而 onConflict = OnConflictStrategy.REPLACE 代表若主鍵發生重複,那就把新的資料取代已儲存的資料。
第24~25行為插入資料用,類似第21~22行。差別在於可以傳入 list。
第27~28行為更新資料用,會根據主鍵以傳入的資料取代已儲存的資料。
第30~31行為更新資料用,類似第27~28行。差別在於可以傳入 list。
第33~34行為刪除資料用,會根據主鍵刪除已儲存的資料。
第36~37行為刪除資料用,類似第33~34行。差別在於可以傳入 list。

2-3.建立一個抽象類別並繼承RoomDatabase

@Database(version = 1, entities = {User.class})
public abstract class RoomDBWrapper extends RoomDatabase {
  private static RoomDBWrapper sUnique = Room
      .databaseBuilder(SingletonApplication.getInstance(), RoomDBWrapper.class, "user.db")
      .allowMainThreadQueries().build();
  public static RoomDBWrapper getInstance() {
    return sUnique;
  }
  public abstract UserDao getUserDao();
}

注意以下幾點
1.該類別主要用途為建立資料庫並管理 DAO,可以考慮套用 Singleton Pattern。
2.必須是抽象類別並繼承 RoomDatabase
第1行使用 @Database 註解並指定 version(版本),entities(要儲存類別,如有多個就必須一一加入)
第4行建立 sUnique 變數,之後都透過該變數來取得 DAO 並進行相關操作。
第5行的 SingletonApplication 是自定義類別,繼承 Application 如下

public class SingletonApplication extends Application
{
    private static SingletonApplication sUniqueInstance;
    public static SingletonApplication getInstance()
    {
        return sUniqueInstance;
    }
...

user.db 為資料庫名稱。
第6行注意 allowMainThreadQueries(),呼叫該方法代表予許操作資料庫的動作進行在 ui thread 上,若沒有呼叫該方法,預設是不能在 ui thread 上操作資料。其實也不建議在 ui thread 上進行資料庫操作,避免 ANR。
第8~10行為提供對外存取 sUnique 的方法。
第12行為要儲存類別的 DAO。

3.使用方式

3-1.Insert 資料

  private void insert() {
    for (int i = 0; i < 10; ++i) {
      User user = new User();
      user.setId(i);
      user.setListNumber(i);
      user.setName("name " + i);
      if (i % 2 == 0) {
        user.setActive(true);
      } else {
        user.setActive(false);
      }
      user.setDescription("empty");
      Account account = new Account();
      account.setId(i);
      account.setName("name " + i);
      user.setAccount(account);
      RoomDBWrapper.getInstance().getUserDao().insertUser(user);
    }
  }

第3~16行為建立要 insert 的資料。
第17行即呼叫 RoomDBWrapper 進行實際 insert 的動作。
insert 方法完成後 user 資料表就會有 10 筆 user 資料。

3-2.Query 資料

  private void queryAllUsers() {
    List<User> allUser = RoomDBWrapper.getInstance().getUserDao().getAllUser();
  }

3-3.Update資料

  private void updateUser5() {
    User user = new User();
    user.setId(5);
    user.setName("updateUser5");
    RoomDBWrapper.getInstance().getUserDao().updateUser(user);
  }
  private void updateUsers() {
    for (int i = 0; i < 10; ++i) {
      User user = new User();
      user.setId(i);
      user.setName("name " + i + 10);
      RoomDBWrapper.getInstance().getUserDao().updateUsers(user);
    }
  }

第3行當 user.setId(5) 之後,並將該 user 傳入第4行 updateUser(user)方法後,就會取代已儲存資料中主鍵(id)為 5 的項目。
第11行 setId 的參數範圍從 0~9,當呼叫第13行 updateUsers(user) 後,就會取代已儲存資料中主鍵(id)範圍從 0~9 的項目。

3-4. Delete 資料

  private void deleteUser2() {
    User user = new User();
    user.setId(2);
    RoomDBWrapper.getInstance().getUserDao().deleteUser(user);
  }

第3行指定 setId(2)
第4行呼叫 deleteUser(user),即代表刪除已儲存資料中,主鍵(id)為 2 的項目。

分類
Uncategorized

android 裝置開機後收到廣播通知(BootUpReceiver)

一般要收到系統的廣播通知都是類似的做法。
1.首先建立要接收系統開機完成廣播通知的Receiver

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class BootUpReceiver extends BroadcastReceiver {
  private static final String TAG = BootUpReceiver.class.getSimpleName();
  @Override
  public void onReceive(Context context, Intent intent) {
    Toast.makeText(context, "boot up!!!", Toast.LENGTH_SHORT).show();
  }
}

onReceive方法中即為收到通知後要做的動作。
2.在AndroidManifest.xml新增開機通知權限以及BootUpReceiver的註冊。

...
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
    <receiver
      android:enabled="true"
      android:exported="true"
      android:name=".alarm.BootUpReceiver">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
      </intent-filter>
    </receiver>
...

要注意android:name必須對應BootUpReceiver的檔案路徑。

分類
Uncategorized

使用 RingtoneManager 播放提示音(NOTIFICATION),鬧鐘(ALARM),來電鈴聲(RINGTONE)

1.播放預設的提示音

 public static void playDefaultNotification(Context context) {
    Uri defaultNotificationURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    Ringtone soundOfDefaultNotification = RingtoneManager.getRingtone(context, defaultNotificationURI);
    soundOfDefaultNotification.play();
  }

其中RingtoneManager.TYPE_NOTIFICATION為提示音的設定。
若想播放預設的鬧鐘只要改為RingtoneManager.TYPE_ALARM
播放預設的鬧鐘只要改為RingtoneManager.TYPE_RINGTONE
2.播放裝置內其他的提示音
2-1.首先取得裝置內的提示音清單

 public static Map<String, String> getSoundTitleAndUri(Context context, int type) {
    RingtoneManager manager = new RingtoneManager(context);
    manager.setType(type);
    Cursor cursor = manager.getCursor();
    Map<String, String> titleAndUri = new HashMap<>();
    while (cursor.moveToNext()) {
      String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
      String id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX);
      String uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
      titleAndUri.put(title, uri+"/"+id);
    }
    return titleAndUri;
  }

使用的方式如下(取得提示音的清單)

getSoundTitleAndUri(context, RingtoneManager.TYPE_NOTIFICATION);

回傳的Map其key值即為提示音的名稱,value值為提示音的Uri。
若想取得的是鬧鐘清單,只要改為RingtoneManager.TYPE_ALARM即可
若想取得的是來電鈴聲清單,只要改為RingtoneManager.TYPE_RINGTONE即可
若把該Map印出來如下。

key:Alloy value:content://media/internal/audio/media/89
key:Arc value:content://media/internal/audio/media/90
key:Bezel value:content://media/internal/audio/media/91
...

Alloy為提示音的名稱,其URI為 content://media/internal/audio/media/89
最後要播放指定的提示音如下

 public static void playSound(Context context, String uri) {
    Uri soundUri = Uri.parse(uri);
    Ringtone sound = RingtoneManager.getRingtone(context, soundUri);
    sound.play();
  }

使用的方法為

playSound(context,"content://media/internal/audio/media/89");//play Alloy
分類
Uncategorized

Android Get MIME Type and open File by Intent

取得MIME之後再搭配Intent即可根據MIME Type開啟應用程式。
public static String getMIMEType(Uri uri)
{
String mimeType = null;
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
mimeType = SingletonApplication.getInstance().getContentResolver().getType(uri);
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(uri.toString()).toLowerCase());
}
return mimeType;
}
注意參數Uri 的使用如下
File externalStorageDirectoryFilePath = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),”/readme.docx”);
Log.d(TAG,”File MIME:”+getMIMEType(Uri.fromFile(externalStorageDirectoryFilePath));
其中 externalStorageDirectoryFilePath.getAbsolutePath()) 為回傳外部儲存裝置的目錄路徑,
參數Environment.DIRECTORY_DOCUMENTS為指定回傳外部儲存裝置的目錄路徑的
Documents目錄路徑。因此externalStorageDirectoryFilePath.getAbsolutePath()); 回傳路徑如下
/storage/emulated/0/Documents/readme.docx
docx的MIME為
application/vnd.openxmlformats-officedocument.wordprocessingml.document
txt的MIME為
text/plain
接著使用Intent開啟檔案
File externalStorageDirectoryFilePath = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),”/readme.docx”);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(externalStorageDirectoryFilePath,
getMIMEType(Uri.fromFile(externalStorageDirectoryFilePath)));
activity.getContext().startActivity(intent);

分類
Uncategorized

Android layout支援多尺寸螢幕

詳細內容參考官方網站 https://developer.android.com/guide/practices/screens_support.html
以下紀錄簡略記錄。
若是只有1個layout file,Android自動縮放layout上的UI元件來搭配目前運行的裝置。
但因為不同裝置之間的螢幕尺寸差異太大,即使經過自動縮放,UI元件的外觀或位置也會不正常。如5吋手機和10吋平板使用同1個layout file。
因此比較好的方式為根據不同螢幕尺寸加入不同的layout file,讓android自動判斷套用不同layout file。
實作方式為在res目錄下增加相對應的目錄,layout-small,layout-normal,layout-large,layout-xlarge 各目錄會支援不同的螢幕尺寸,然後在各個目錄中新增並調整layout file 的元件。
e.g.
目前必須支援6吋手機以及10吋平板,經過實驗,android 在6吋手機上會套用layout-normal目錄中的layout file,而10吋會套用 layout-xlarge目錄的layout file。
因此新增layout-normal以及layout-xlarge目錄,並放置對應的layout file(activity_main.xml)
最後在到各個activity_main.xml去調整UI。

分類
Uncategorized

Vrapper plugin hotkey

ref: http://vrapper.sourceforge.net/documentation/?topic=optional_plugins#ipmotion
1.vil -> 選擇該行,不包括空白。
2.via -> 選擇argument。
3.vif -> 選擇函式內容。
4.vii -> 選擇縮排。
5.gcc -> 註釋
6.gd -> 移動到宣告處
7.gR -> 重新命名(重構)
8.gr -> 顯示重構選單
9.gm -> 顯示source選單
10. :fm -> format

分類
Uncategorized

android logcat

1.發生異常之後,直接查看最後1個異常以及該異常的第1個棧記錄。E.G.
02-27 14:01:06.126 23653 23653 E AndroidRuntime: java.lang.RuntimeException: Unable to pause activity {com.codefoxx.namingexample/com.codefoxx.namingexample.Mai
nActivity}: java.lang.ArithmeticException: divide by zero
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3660)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3604)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4393)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread.access$1000(ActivityThread.java:150)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:102)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:168)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:5885)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
02-27 14:01:06.126 23653 23653 E AndroidRuntime: Caused by: java.lang.ArithmeticException: divide by zero
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at com.codefoxx.namingexample.MainActivity.onPause(MainActivity.java:60)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.Activity.performPause(Activity.java:6374)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.Instrumentation.callActivityOnPause(Instrumentation.java:1412)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3639)
02-27 14:01:06.126 23653 23653 E AndroidRuntime:        ... 10 more

第14行為最後一個異常,第15行為該異常的第1個棧記錄。

2.使用ADB + LOGCAT COMMAND LINE

使用格式如下

adb -s abcdefghijklnmopqrstuvwxyz logcat LoginPresenter:d LoginActivity:d *:w

LoginPresenter:D -> 表示只會顯示TAG為LoginPresenter而優先性為Debug
LoginActivity:D -> 表示只會顯示TAG為LoginActivity而優先性為Debug
adb -s abcdefghijklnmopqrstuvwxyz ->表示在連接多數裝置的情況指定其中之一裝置。
*:w -> 表示設定所有TAG(除了在該指令之前的)的優先性為Fatal。
若改為*:S則會將所有TAG(除了在該指令之前的)的優先性設為Silent,而Silent則代表不會輸出任何訊息。
以上的logcat指令代表:
會顯示TAG為LoginPresenter而優先性為Debug,以及TAG為LoginActivity而優先性為Debug,以及其他的TAG只有Fatal會顯示

分類
Uncategorized

.gitignore file pattern

詳細內容參考這篇
https://www.atlassian.com/git/tutorials/saving-changes/gitignore

#忽略所有以name命名的目錄和其子目錄,包含其內的檔案
name/
#忽略所有以name命名的目錄和檔案
name
分類
Uncategorized

The Simple Logging Facade for Java (SLF4J) 使用紀錄

SLF4J主要是提供”方便使用”log的工具,照著以下步驟即可在專案中使用
Step1.
到官網(https://www.slf4j.org/)下載slf4j-api-xxx.jar以及slf4j-simple-xxx.jar
Step2.
把以上的jar檔import到專案中
Step3.
使用方式,首先設定Logger,e.g.,

private Logger mLogger = LoggerFactory.getLogger(SLF4JTest.class);
//SLF4JTest.class通常會代換為使用該mLogger的class

接著印出訊息,e.g.,

        mLogger.info("in test");
        mLogger.debug("in test");
        mLogger.error("in test");
        mLogger.trace("in test");
        mLogger.warn("in test");

最後是簡單的使用測試案例。e.g.,

public class SLF4JTest extends TestCase
{
    private Logger mLogger = LoggerFactory.getLogger(SLF4JTest.class);
    public SLF4JTest(String name) {
        super(name);
    }
    public void test()
    {
        mLogger.info("in test");
        mLogger.debug("in test");
        mLogger.error("in test");
        mLogger.trace("in test");
        mLogger.warn("in test");
    }
}

其他詳細的使用方式請參考官網。

分類
Uncategorized

android 取得裝置資訊(android.os.Build.class.getFields())

source code
note:不一定所有的值都取的到

    public static void getDeviceInfo(){
        Field[] deviceInfo = android.os.Build.class.getFields();
        for (Field one : deviceInfo) {
            try {
                Log.d(TAG, "Name:" + one.getName() + " Value:" + one.get(one));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
    }

output

06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:ASUSCID Value:ASUS
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:ASUSSKU Value:WW
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:AUTO_START Value:true
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:BOARD Value:msm8952
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:BOOTLOADER Value:unknown
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:BRAND Value:asus
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:CHARACTERISTICS Value:tablet
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:COUNTRYCODE Value:TW
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:CPU_ABI Value:arm64-v8a
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:CPU_ABI2 Value:
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:CTA Value:false
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:DEVICE Value:ASUS_P00I
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:DISPLAY Value:NRD90M.WW_ASUS_P00I-V5.4.6-20170815
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:FINGERPRINT Value:asus/WW_ASUS_P00I/ASUS_P00I:7.0/NRD90M/WW_ASUS_P00I-V5.4.6-20170815:user/release-keys
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:HARDWARE Value:qcom
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:HOST Value:mec7
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:ID Value:NRD90M
06-07 10:27:56.327 24184 24184 D DeviceUtility: Name:ISASUSVERMAX Value:false
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:IS_DEBUGGABLE Value:false
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:IS_EMULATOR Value:false
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:MANUFACTURER Value:asus
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:MODEL Value:ASUS_P00I
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:PERMISSIONS_REVIEW_REQUIRED Value:false
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:PRODUCT Value:WW_ASUS_P00I
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:RADIO Value:unknown
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:SERIAL Value:GCNPCX1079367R5
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:SUPPORTED_32_BIT_ABIS Value:[Ljava.lang.String;@3f04769
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:SUPPORTED_64_BIT_ABIS Value:[Ljava.lang.String;@74695ee
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:SUPPORTED_ABIS Value:[Ljava.lang.String;@ed08a8f
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:TAGS Value:release-keys
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:TIME Value:1502780830000
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:TYPE Value:user
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:UNKNOWN Value:unknown
06-07 10:27:56.328 24184 24184 D DeviceUtility: Name:USER Value:android