2016年11月2日 星期三

Android: 利用Google Drive API撰寫apk檔案上傳器

前言

在Android的開發上,每當建置完apk後,在版本控制或是將apk釋出給測試人員或專案管理者測試時,有時相關測試人員會來向開發者尋求apk檔,倘若該情形是很頻繁的釋出,那這件事情就會變得很擾人。

我們可以透過Jenkins或其他持續整合(Continuous Integration, CI)環境來將我們的apk釋出至DropBox環境或其他空間,當然,若你沒有建立CI,也不想每次建置完apk後,又要手動複製檔案至某個儲存空間,或許可以利用Google Drive API來將你的檔案自動上傳,這篇文章便是教你如何開發一個apk檔案上傳器。

Java實作

Android: 如何在Gradle建立Build Variants及更換APK檔名這篇文章中,我們在使用"gradle assembleDebug/assembleRelease"的指令時,會同時建置出很多apk,所以我會利用這個專案來解釋實作。
  • 申請憑證
    • 由於要在程式內使用Google Drive API,我們需要至Google Developer Console申請一個應用程式內用來識別的憑證。
      • 先在Google Developer Console建立新專案,這裡我命名為GDUSample


      • 點選憑證

      • 選擇OAuth用戶端ID


      • 選擇其他,並輸入一個名稱,這邊我一樣命名為GDUSample

      • 建立後,就能看見憑證的資料,這時選擇下載Json

      • 至資訊主頁點選啟用API,並搜尋Google Drive API



  • 撰寫程式碼
    • 待憑證和API啟用後,我們則要使用Google Drive API進行開發。由於這個檔案上傳器是利用Java程式來開發,就使用你習慣的IDE即可,這裡我是使用Eclipse,並開啟一個新的JAVA專案。

    • 匯入Drive API

    • 選擇Drive API v2,由於我已經匯入,所以下圖會顯示"installed"

    • 匯入下列lib

    • 將先前下載憑證的Json,命名為"client_secret.json",並放置於專案下。



    • 初始HttpTransport與FileDataStoreFactory物件
    public static final String APPLICATION_NAME = "Drive API Java Quickstart";
    public static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"), getJarPath());
    public static FileDataStoreFactory DATA_STORE_FACTORY;
    public static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    public static HttpTransport HTTP_TRANSPORT;
    public static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE);

static {
        try {
            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
            DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
        } catch (Throwable t) {
            t.printStackTrace();
            System.exit(1);
        }
}

    • 取得Credential物件
      • 在程式內使用Google Drive API時,我們需要先利用先前下載的憑證,藉由FileInputStream來產生一個GoogleClientSecrets,並且藉由這個物件,取得Credential物件,程式碼片段下。
public static Credential authorize() throws IOException {
        System.out.println("path: " + DATA_STORE_DIR.getPath());

        FileInputStream fileInputStream = new FileInputStream(getJarPath() + "/client_secret.json");
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
                new InputStreamReader(fileInputStream));

        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
                clientSecrets, SCOPES).setDataStoreFactory(DATA_STORE_FACTORY).setAccessType("offline").build();

        Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
        System.out.println("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath());

        return credential;
}

    • 利用Credential取得Drive物件
public static Drive getDriveService() throws IOException {
        Credential credential = authorize();
        return new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
}

    • 利用Drive物件的Insert方法,在Google Drive上新增資料夾與檔案
      • 定義欲在Google Drive新增資料夾或檔案的ID。例如我想在名為GoogleDriveUploader這個資料夾中,新增我的apk資料夾和檔案,所以我需要先取得該資料夾的ID,並將該ID定義於程式當中,如下圖所示。


public static final String apksLocationId = "0B-0iN5m8YqglMHJxdy13Z0JkU3c";

      • 使用Insert方法新增資料夾。
        • 這邊資料夾的命名方式是以當天的年月日命名,所以會使用到Date物件。
        • Insert方法的使用很簡單,只要先產生一個Google Drive的File物件後,接著設定Title與MimeType即可。
        • parentId則是上述定義的Id
public File createRemoteApkFolder(String parentId) throws IOException {
        File fileMetadata = new File();
        StringBuffer folderName = new StringBuffer();
        String fileTitle = folderName.append("gduSampleFolder").append(this.util.getDateTime()).toString();

        fileMetadata.setTitle(fileTitle);
        fileMetadata.setMimeType("application/vnd.google-apps.folder");

        if (parentId != null && parentId.length() > 0) {
            fileMetadata.setParents(Arrays.asList(new ParentReference().setId(parentId)));
        }

        File remoteFileFolder = this.service.files().insert(fileMetadata).setFields("id").execute();

        return remoteFileFolder;
}

        • 使用Insert方法新增檔案
          • 新增檔案方式與資料夾無異,只是我們需要指定欲上傳檔案的路徑(filePath)。
          • 新增檔案的MimeType,由於我是將所有apk打包成zip檔,所以我將MimeType指定為"application/octet-stream"。
public File createRemoteApkFiles(String fileTitle, String parentId, String filePath, String mediaContentMimeType)
            throws IOException {
        File fileMetadata = new File();
        fileMetadata.setTitle(fileTitle);
        fileMetadata.setMimeType(mediaContentMimeType);

        if (parentId != null && parentId.length() > 0) {
            fileMetadata.setParents(Arrays.asList(new ParentReference().setId(parentId)));
        }

        java.io.File localFile = new java.io.File(filePath);
        FileContent mediaContent = new FileContent(mediaContentMimeType, localFile);
        File remoteFile = this.service.files().insert(fileMetadata, mediaContent).setFields("id").execute();

        return remoteFile;
}

        • 將程式打包成Runnable Jar File





        • 產生出來的Runnable Jar File需要和你下載的“client_secret.json”放在同一個目錄下,基本上就可以執行。可以在Terminal使用"java -jar GoogleDriveUploader.jar"執行,如下圖所示。

        • 第一次會需要使用者驗證,所以當你執行該檔案,則會跳出要求使用者允許的頁面,按下允許就可以,之後也無需驗證。

撰寫簡單的Shell Script

其實撰寫完Java程式已經算完成,但我們可以撰寫一個Shell Script,讓我們每次執行完"gradle assebmleDebug/assembleRelease"指令後,將所有apk打包成zip,並上傳至Google Drive,甚至可以再利用CI環境整合,當上傳完成後,則發送信件通知相關測試人員。CI部分若有時間,我則會在之後的文章加入。

  • 將你的Jar File與client_secret.json置於專案下,並於Android專案下新增File,且使用.sh副檔名,如下圖所示。

  • 撰寫Shell Script,程式碼如下。
    • $(pwd): 指的是檔案當下所處的位置
    • gradle clean assembleDebug: 則是執行建置Debug環境的apk指令
    • zip -r "路徑/檔名" "路徑/欲打包的檔案": zip則是執行將檔案打包成你所要zip檔
    • java -jar $(pwd)/GDUSample.jar: 執行該路徑下的Jar File
#!/bin/sh
echo Current file path: $(pwd)/${0}

gradle clean assembleDebug

zip -r $(pwd)/app/build/outputs/apk/ReleaseApks.zip $(pwd)/app/build/outputs/apk/*.apk

java -jar $(pwd)/GDUSample.jar

sleep 90

  • 撰寫完後,我們就可以執行該檔案,就可以直接為我們建置apk,最後直接上傳至Google Drive了,如下圖所示。




Sample Code(Google Drive Uploader): https://github.com/xavier0507/GoogleDriveUploaderSample.git

Sample Code(Build Variants Sample): https://github.com/xavier0507/Build-Variants-Sample.git


2 則留言:

  1. 作者你好,
    請問getJarPath()是哪一個lib定義的function?
    如果不是的話,請問其function body是如何定義的呢?

    回覆刪除
    回覆
    1. Hi, 您好!
      getJarPath是我自己定義的function, 可以參考我的Sample code:
      https://github.com/xavier0507/GoogleDriveUploaderSample/blob/master/src/com/xy/gdu/GoogleDriveManager.java

      刪除