OKHttp 基本使用紀錄

OKHttp 是支援HTTP & HTTP/2 的開源框架,透過它可以很方便發送HTTP請求。
具有以下特性:
1.API設計簡單,可以透過幾行code即可發送HTTP請求
2.支援同步,異步請求。同步請求會block目前的Thread,異步請求則不會block

OKHttp官網連結

最新版本釋出

以下紀錄最基本的get, post, post with json format 的使用方式。


1.相依性


在 module 的 build.gradle 加入
dependencies {  
...
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}

Note:目前(2018/09/28)最新版本為3.11.0

2. Using in code


為了封裝OKHttp建立了OKHttpWrapper 類別,外部皆透過該類別來使用OKHttp。
OKHttpWrapper.java
import android.util.Log;  
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONObject;

public class OKHttpWrapper {

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

private static OkHttpClient sOKHttpClient = new OkHttpClient();

public static void sendGet(String URL, Map<String, String> requestParameter, Callback callback) {
Set<String> keys = requestParameter.keySet();
for (String value : keys) {
URL = new StringBuilder(URL + "&" + value + "=" + requestParameter.get(value)).toString();
}
Request request = new Request.Builder().url(URL).build();
Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

public static void sendPost(String URL, Map<String, String> requestParameter, Callback callback) {
FormBody.Builder builder = new FormBody.Builder();
Set<String> keys = requestParameter.keySet();
for (String key : keys) {
builder.add(key, requestParameter.get(key));
}

Request request = new Request.Builder().url(URL).post(builder.build()).build();

Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

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);
}

}

第19行建立OKHttpClient實體,各個請求皆透過該實體來動作。
第21行sendGet方法為發送GET使用,第1個參數為接收請求的URL,第2個為請求的參數,第3個參數為發送請求之後的回應。
第31行sendPost方法為發送POST使用,第1個參數為接收請求的URL,第2個為請求的參數,第3個參數為發送請求之後的回應。
第44行sendPostWithJSONFormat方法為發送POST但參數的格式為JSON,第1個參數為接收請求的URL,第2個為請求的參數,第3個參數為發送請求之後的回應。

可以看到透過OKHttp發送請求相當簡單,接下來示範外部如何呼叫OKHttpWrapper。接收請求的Server使用httpbin
以下為外部
  private void testSendRequest(String name, String id) {  
Map<String, String> httpParameter = new HashMap<>();
httpParameter.put("Name", name);
httpParameter.put("ID", id);

OKHttpWrapper.sendPostWithJSONFormat(
"http://httpbin.org/post", httpParameter, new Callback() {
@Override
public void onFailure(Call call, IOException e) {

}

@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, TAG+" response:"+response.body().string());
}
});
}

第2~4行為組裝請求的參數。
第6行呼叫OKHttpWrapper的sendPostWithJSONFormat方法
第1個參數為為接受請求的URL,第2個參數為請求的參數,第3個參數為發送請求後的回應,其中onFailure為請求失敗的回應。onResponse則為請求成功的回應。
        testSendRequest("Foxx", "999");  
testSendRequest("Peter", "000");
testSendRequest("May", "111");

Output
 "json": {  
"ID": "999",
"Name": "Foxx"
},

"json": {
"ID": "000",
"Name": "Peter"
},

"json": {
"ID": "111",
"Name": "May"
},

 

Orignal From: OKHttp 基本使用紀錄

Arrays.binarySearch 直接使用於 android resource 的問題

為了方便使用陣列常常會直接在array.xml中宣告2個互相對應的陣列 e.g.,
  <string-array name="type">  
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
<string-array name="code">
<item>A</item>
<item>C</item>
<item>B</item>
<item>E</item>
<item>F</item>
<item>G</item>
<item>D</item>
</string-array>

若直接在code中使用Arrays.binsearch 會出現 index 錯誤 e.g.,
        String[] targetArray = getResources().getStringArray(R.array.type);  
int index = Arrays.binarySearch(targetArray, 3);
String[] codeArray = getResources().getStringArray(R.array.code);

原因是因為Arrays.binarySearch只能使用在已經排序(自然排序)過的陣列。

若該陣列還未排序會丟出Exception,說明index有問題。

比較快的解決方式就是自己寫取得對應的元素 e.g.,
    private int findSpecifyIndexInArray(String[] targetArray, String specify){  
for (int i = 0; i < targetArray.length; ++i) {
if (targetArray[i].equals(specify)) {
return i;
}
}
return -1;
}

String[] targetArray = getResources().getStringArray(R.array.type);
int index = findSpecifyIndexInArray(targetArray, 3);
String[] codeArray = getResources().getStringArray(R.array.code);

 

Orignal From: Arrays.binarySearch 直接使用於 android resource 的問題

Google Cloud Source Repositories 如何將本地端已存在的repo推送到遠端

狀況:本地端已存在git repo,現在想將本地端的repo推送到遠端的新repo

1.先到GCSR新增遠端新repo
到https://source.cloud.google.com/repos ->點擊新增存放區->點擊建立新的存放區->點擊繼續->輸入存放區名稱->選擇已存在專案或新增專案->點擊建立

2.建立本地端repo的認證並推送到遠端
在"請選取將程式碼推送至存放區的選項:"選擇"從本機 Git 存放區推送程式碼"
->在"選取您偏好的驗證方式:"選擇"手動產生的憑證" ->點擊 1.產生及儲存Git憑證->將Configure Git 頁面中的 Windows Users 程式碼複製並貼到本地端的git bash -> 移動到本地端專案路徑並進行 2. 進行將 Cloud Repository 新增為遠端存放區:(這裡注意最好編輯成1行再貼到git bash,如 git remote add google https://xxxx)->最後進行 3.從本機 Git 存放區推送程式碼:
(注意若出現
fatal: remote error:  


Invalid authentication credentials.

Please generate a new identifier:
https://source.developers.google.com/new-password

代表要重新產生憑證,到https://source.developers.google.com/auth/start?scopes=https://www.googleapis.com/auth/cloud-platform
選擇登入帳號,將顯示的程式碼貼到git bash)

 

3.完成後在GCSR的repo便可看到推送的內容。

Orignal From: Google Cloud Source Repositories 如何將本地端已存在的repo推送到遠端

Google Cloud Source Repositories 試用紀錄

GCSR(Google Cloud Source Repositories) 是 google 推出以 git 為基礎的程式碼代管服務。

原先我的side project是自己建git server來放置source code,後來因為硬體故障等等

自己維護需要花費不少時間和成本,加上本身也是google 的愛好者,最後選擇GCSR來試試。

首先談談GCSR的缺點,基本上最大麻煩的點為google 提供的repository必須依附於google cloud platform(GCP)上的專案。

也就是說你必須先了解基本的GCP使用方式才能開始用GCSR。

第2個麻煩的點在於create, clone repository在官方教學中必須透過 google cloud sdk才能使用(也許可以透過別的方式但還未試出),也就是說你必須先安裝 google cloud sdk才能開始使用GCSR。

第3個麻煩的點在於免費版的限制,如下







免費方案
超量


最多 5 位使用者
每位專案使用者每月 $1 美元 (專案使用者達 5 位以上時)

50 GB 儲存空間
每 GB 儲存空間 每月 $0.10 美元

50 GB 輸出
每 GB 輸出每月 $0.10 美元



若以上3個限制您都還可以接受,那就繼續往下看吧。

首先提供官網GCSR的說明。若想直接參考使用方式請看quick start。

1.在Google Cloud Platform建立專案
建議新增獨立的GCP專案來放置repository避免和其他的專案混淆。

2.安裝Google Cloud SDK
安裝SDK的目的就是用來新增,複製專案(應該是可以透過其他方式來做到但還未試出)。
注意安裝完成之後還需要進行初始化的動作,參考這裡。

3.安裝git
Windows 官方推薦使用git-scm

Linux就用command line安裝吧

4.使用Google Cloud SDK(gcloud command line) 建立 repository
gcloud source repos create hello-world

clone repo 到本地端
gcloud source repos clone hello-world

從官網看起來似乎只有 create和clone需要使用 gcloud

其它的操作就可以使用一般的git指令,如 git add, git commit, git push等等

5. GCSR UI
當然就像github,gitlab等等,GCSR也有提供repository的UI操作。
以下為repository的介面,存放區即為repository


以下是搶先試用版的畫面


Orignal From: Google Cloud Source Repositories 試用紀錄

使用 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.播放裝置內其他的提示音。
首先取得裝置內的提示音清單。

  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

TraceView

Traceview 用來追蹤程式執行效能,主要可運用2種方式來產生效能分析圖(.trace)

A-1.使用插入code方式
在想分析的程式碼區塊插入Debug.startMethodTracing()及Debug.stopMethodTracing();
其中Debug.startMethodTracing()可指定分析圖檔名如Debug.startMethodTracing("trace_report"),在/sdcard會產生trace_report.trace檔

A-2.加入<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
否則A-1的方法會丟出Exception

A-3.執行程式

A-4.將產生的分析圖從/sdcard中複製到本機的資料夾中以方便查看,使用以下指令
./adb pull /sdcard/trace_report.trace /資料夾名稱

A-5.使用android-sdk tools的 traceview來開啟.trace檔,使用以下指令
android-sdk-linux/tools$ ./traceview /資料夾名稱/trace_report.trace

B-1.使用DDMS來產生分析圖,執行程式並切換到DDMS

B-2.點擊Start Method Profiling,開始分析

B-3.點擊Stop Method Profiling,結束分析

B-4.自動產生分析檔

以下提供簡單的分析範例,尋找程式效能熱點(HotPoint)
首先這是MainActivity
MainActivity.java
public class MainActivity extends Activity
{
   
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);      
        Debug.startMethodTracing("trace_report");
        TestString testStr = new TestString();
        Debug.stopMethodTracing();
    }

}
相當簡單,只專注在 TestString 物件的建立
接著是TestString
TestString.java
public class TestString
{
    private String mStr = new String();
   
    public TestString(){
        initStr();
    }
   
    private void initStr(){
        for(int i=0; i<10000; ++i){
            mStr = mStr.concat(""+i);
        }
    }
}
在initStr方法內為相當消耗效能的熱點,執行程式產生分析圖如下














在Incl cpu time 佔用 89.6% 為String.concat()方法,總共佔用了9.66秒,針對這裡來修改熱點
修改後的TestString.java如下
public class TestString
{
    private StringBuffer mStr = new StringBuffer();
   
    public TestString(){
        initStr();
    }
   
    private void initStr(){
        for(int i=0; i<10000; ++i){
            mStr = mStr.append(i);
        }
    }
}
採用StringBuffer取代原來的String,重新產生分析圖















可以看到StringBuffer.append()方法仍然為熱點(95.7%),佔用時間卻降到了0.73秒


XmlParser

網路上一堆範例不如自己作一遍還來的了解
直接切入主題,這裡使用DOM來轉換xml,以步驟來說明

Step1.
首先要有xml檔放到assets資料夾中,為了方便整理在assets 資料夾中另外開了一個xmlfiles資料夾,路徑如下

Project/assets/xmlfiles/command_simple_formatter.xml
<?xml version="1.0" encoding="utf-8"?>
<commands>
    <command
        id="1"
        enable="true"
        function_name="function1"
        type="FLOAT" />
    <command
        id="2"
        enable="false"
        function_name="function2"
        type="FIXED" />
</commands>

Step2.
接著建立對應的資料結構,根據xml中的tag來建立

Command.java
public class Command
{
    private int mIndex;
    private String mFunction;
    private boolean mEnable;
    private Type mType;
 
    public enum Type {
        FIXED, FLOAT
    }
 
    public void setType(Type type){
        mType = type;
    }
 
    public Type getType(){
        return mType;
    }
 
    public void setIndex(int index)
    {
        mIndex = index;
    }

    public void setFunction(String function)
    {
        mFunction = function;
    }

    public int getIndex()
    {
        return mIndex;
    }

    public String getFunction()
    {
        return mFunction;
    }
 
    public boolean getEnable(){
        return mEnable;
    }
 
    public void setEnable(boolean enable){
        mEnable = enable;
    }
}

Step3.
接著開始撰寫轉換的本體,這裡我帶入Template Method,
XmlParser.java
public abstract class XmlParser
{
    private Context mContext;

    public XmlParser(Context context) {
        mContext = context;
    }

    public final List<?> parserProcess(String xmlFileName)
    {
        Document document = prepareParseComponent(xmlFileName);
        Element root = getXmlBasicRootElement(document);
        List<?> javaDatas = parseXmlContentToJavaObjects(root);

        return javaDatas;
    }
 
    private Document prepareParseComponent(String xmlFileName)
    {
        DocumentBuilder documentBuilder = getDocumentBuilder();
        InputStream inputStream = getXmlInputStream(xmlFileName);
        Document document = null;
        try {
            document = documentBuilder.parse(inputStream);
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return document;
    }

    protected abstract List<?> parseXmlContentToJavaObjects(Element root);
 
    private DocumentBuilder getDocumentBuilder()
    {
        DocumentBuilder documentBuilder = null;
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        try {
            documentBuilder = builderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }

        return documentBuilder;
    }

    /**
     *
     * @param xmlFileName : xml file need put in assets folder
     * @return InputStream
     */
    private InputStream getXmlInputStream(String xmlFileName)
    {
        AssetManager assetManager = mContext.getAssets();
        InputStream inputStream = null;
        try {
            inputStream = assetManager.open(xmlFileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }

    private Element getXmlBasicRootElement(Document document)
    {
        Element root = document.getDocumentElement();
        return root;
    }
}

Step4.
建立繼承XmlParser的類別,主要複寫parseXmlContentToJavaObjects方法
CommandSimpelXmlParser.java
public class CommandSimpleXmlParser extends XmlParser
{
    private String mElementName = "command";
    private String mId = "id";
    private String mFunctionName = "function_name";
    private String mEnable = "enable";
    private String mType = "type";
 
    public CommandSimpleXmlParser(Context context) {
        super(context);
    }

    @Override
    protected List<Command> parseXmlContentToJavaObjects(Element root)
    {
        NodeList commandNode = root.getElementsByTagName(mElementName);
        int commandsNodeSize = commandNode.getLength();
        return transXmlToCommand(commandNode,commandsNodeSize);
    }
 
    private List<Command> transXmlToCommand(NodeList nodeList,int size){
        List<Command> result = new ArrayList<Command>();
        for (int i = 0; i < size; ++i) {
            Command command = new Command();
            Element commandElement = (Element) nodeList.item(i);
            command.setIndex(transXmlIdTagToCommand(
                    commandElement, mId));
            command.setFunction(transXmlFunctionNameTagToCommand(
                    commandElement, mFunctionName));
            command.setEnable(transXmlEnableTagToCommand(
                    commandElement, mEnable));
            command.setUIType(trnasXmlTypeToCommand(
                    commandElement, mType));
            result.add(command);
            command = null;
        }
        return result;
    }
 
    private UIType trnasXmlTypeToCommand(Element element,String type){
        return Command.Type.valueOf(element.getAttribute(type));
    }
 
    private int transXmlIdTagToCommand(Element element,String id){
        return Integer.valueOf(element.getAttribute(id));
    }
 
    private String transXmlFunctionNameTagToCommand(Element element,String functionName){
        return element.getAttribute(functionName);
    }
 
    private boolean transXmlEnableTagToCommand(Element element,String enable){
        return Boolean.valueOf(element.getAttribute(enable));
    }
}

Step5.
使用轉換
XmlParserMainActivity.java
public class XmlParserMainActivity extends Activity
{
    private static final String TAG = "XmlParserMainActivity";
    private static final String COMMAND_SIMPLE_XML_FILE_PATH = "xmlfiles/command_simple_formatter.xml";
 
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        examParseSimpleXml(COMMAND_SIMPLE_XML_FILE_PATH);
    }

    private void examParseSimpleXml(String xmlFilePath)
    {
        XmlParser simpleCommandXmlParser = new CommandSimpleXmlParser(this);
        List<Command> commands = (List<Command>) simpleCommandXmlParser
                .parserProcess(xmlFilePath);
    }

}

Twitter Delicious Facebook Digg Stumbleupon Favorites More

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