App开发完成后通常会上架到使用商店,海外一般上架GooglePlay。除了直接在Google Play Console后台操作外,还能够运用Google Play Developer Publishing API上传aab。本文介绍如何运用Google Play Developer Publishing API上传aab。

官方文档

前置步骤

1. 敞开API权限

  • 在Google Play Console后台设置中找到API权限,挑选相关项目。
使用Google Play Publishing API 上传aab
  • 挑选相关已有的Google Cloud项目或许创立新项目,挑选完后保存。
使用Google Play Publishing API 上传aab
  • 相关完之后,能够从此页面翻开对应的Google Cloud项目。
使用Google Play Publishing API 上传aab

2. 创立服务账号和密钥

  • 在Google Cloud项目页面挑选API和服务。
使用Google Play Publishing API 上传aab
  • 在API和服务页面,挑选凭证->创立凭证->服务账号。
使用Google Play Publishing API 上传aab
  • 在创立服务账号页面填写账号称号,点击创立并持续。
使用Google Play Publishing API 上传aab
  • 挑选角色,我这边选的是Owner,点击持续。
使用Google Play Publishing API 上传aab
  • 能够挑选对其他用户账号授予权限,也能够不填,点击完成。
使用Google Play Publishing API 上传aab
  • 点击已创立的服务账号,进入服务账号页面。
使用Google Play Publishing API 上传aab
  • 在服务账号页面,挑选密钥->增加密钥->创立新密钥。
使用Google Play Publishing API 上传aab
  • 在创立私钥页面,挑选JSON类型,点击创立,保存生成的JSON文件。
使用Google Play Publishing API 上传aab

3. 在API权限中增加服务账号

  • 回来Google Play Console,在API权限中,挑选管理中心权限。
使用Google Play Publishing API 上传aab
  • 在约请页面中,选中发布版本相关的权限,点击约请用户。
使用Google Play Publishing API 上传aab

运用Publishing API

官方提供了封装了API恳求的库,便于运用,无需自己创立http恳求和解析呼应。

增加库

在对应模块的build.gradle中增加代码,如下:

dependencies {
    implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20230511-2.0.0")
    implementation("com.google.auth:google-auth-library-oauth2-http:1.16.1")
}

上传aab

注意:在运用API上传aab之前,确保已经在Google Play Console后台任意途径上传过同包名的aab。

能够发布的途径:

public class Channel {
    /**
     * 正式版
     */
    public static String PRODUCT = "production";
    /**
     * 开放式测试
     */
    public static String TEST_BETA = "beta";
    /**
     * 封闭式测试
     */
    public static String TEST_ALPHA = "alpha";
    /**
     * 内部测试
     */
    public static String TEST_INTERNAL = "internal";
}

使用发布状况:

public class PublishStatus {
    /**
     * 草稿
     */
    public static final String DRAFT = "draft";
    /**
     * 按阶段发布,一起需求设置发布份额(userFraction)
     */
    public static final String IN_PROGRESS = "inProgress";
    /**
     * 停止按阶段发布
     */
    public static final String HALTED = "halted";
    /**
     * 直接发布
     */
    public static final String COMPLETE = "completed";
}

使用文件类型(现在主要运用aab):

public class FileType {
    public static final String APK = "application/vnd.android.package-archive";
    public static final String AAB = "application/octet-stream";
}

获取凭证、上传aab:

public class UploadAabNormal {
    /**
     * 使用名
     */
    public static final String APPLICATION_NAME = "PublishTest";
    /**
     * 使用包名
     */
    public static final String PACKAGE_NAME = "com.chenyihong.exampledemo";
    /**
     * aab存放途径
     */
    public static final String AAB_FILE_PATH = "ExampleDemo.aab";
    /**
     * 服务账号装备文件存放途径
     */
    public static final String SERVER_CLIENT_PATH = "server_client.json";
    /**
     * 发布的途径
     */
    public static final String CHOSEN_CHANNEL = Channel.TEST_INTERNAL;
    /**
     * 使用内更新优先级0-5
     */
    public static final int IN_APP_UPDATE_PRIORITY = 0;
    /**
     * 发布状况
     */
    public static final String PUBLISH_STATUS = PublishStatus.DRAFT;
    /**
     * 分阶段发布份额
     */
    public static final double USER_FRACTION = 0.05;
    /**
     * 更新版本名
     */
    public static final String RELEASE_NAME = "test upload from java";
    /**
     * 更新内容文本
     */
    public static final String RELEASE_NOTE = "this is test for upload aab";
    public static void main(String[] args) {
        InputStream serverClientStream = null;
        try {
            ClassLoader classLoader = UploadAabNormal.class.getClassLoader();
            if (classLoader == null) {
                return;
            }
            serverClientStream = classLoader.getResourceAsStream(SERVER_CLIENT_PATH);
            URL aabResource = classLoader.getResource(AAB_FILE_PATH);
            if (serverClientStream != null && aabResource != null) {
                // 获取凭证
                GoogleCredentials googleCredentials = GoogleCredentials.fromStream(serverClientStream).createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));
                if (googleCredentials != null) {
                    if (googleCredentials.getAccessToken() == null) {
                        googleCredentials.refresh();
                    }
                    AndroidPublisher publisher = new AndroidPublisher.Builder(GoogleNetHttpTransport.newTrustedTransport(), GsonFactory.getDefaultInstance(), new HttpCredentialsAdapter(googleCredentials))
                            .setApplicationName(APPLICATION_NAME)
                            .build();
                    // 创立一个新的更改
                    AndroidPublisher.Edits edits = publisher.edits();
                    AndroidPublisher.Edits.Insert editRequest = edits.insert(PACKAGE_NAME, null);
                    String editId = editRequest.execute().getId();
                    // 上传aab
                    Bundle uploadBundle = edits.bundles().upload(PACKAGE_NAME, editId, new FileContent(FileType.AAB, new File(aabResource.toURI().getPath()))).execute();
                    Long uploadBundleVersionCode = (long) uploadBundle.getVersionCode();
                    // 设置发布途径
                    Track track = new Track();
                    track.setTrack(CHOSEN_CHANNEL);
                    // 设置更新相关信息
                    TrackRelease trackRelease = new TrackRelease();
                    // 设置更新版本号
                    trackRelease.setVersionCodes(Collections.singletonList(uploadBundleVersionCode));
                    // 设置更新版本名
                    trackRelease.setName(RELEASE_NAME);
                    // 设置更新内容
                    trackRelease.setReleaseNotes(Collections.singletonList(new LocalizedText().setText(RELEASE_NOTE)));
                    // 设置使用内更新优先级(0-5,等级4、5为强制更新)
                    trackRelease.setInAppUpdatePriority(IN_APP_UPDATE_PRIORITY);
                    // 设置发布状况
                    trackRelease.setStatus(PUBLISH_STATUS);
                    if (PublishStatus.IN_PROGRESS.equals(trackRelease.getStatus()) || PublishStatus.HALTED.equals(trackRelease.getStatus())) {
                        // 分阶段发布时,设置发布份额
                        trackRelease.setUserFraction(USER_FRACTION);
                    }
                    track.setReleases(Collections.singletonList(trackRelease));
                    edits.tracks().update(PACKAGE_NAME, editId, track.getTrack(), track).execute();
                    // 提交此次更改
                    edits.commit(PACKAGE_NAME, editId).execute();
                }
            }
        } catch (GeneralSecurityException | IOException | URISyntaxException e) {
            e.printStackTrace();
        } finally {
            try {
                if (serverClientStream != null) {
                    serverClientStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上传超时

执行了上面的代码后,发现上传aab报错,超时异常。

使用Google Play Publishing API 上传aab

官方文档和Demo没有相关示例。自己尝试了一番后,得出解决方案如下:

  1. 自定义ConnectionFactory,装备超时时刻。
public class TimeoutConnectionFactory implements ConnectionFactory {
    private final Proxy proxy;
    private final int timeout;
    public TimeoutConnectionFactory() {
        this(null, 0);
    }
    public TimeoutConnectionFactory(int timeout) {
        this(null, timeout);
    }
    public TimeoutConnectionFactory(Proxy proxy, int timeout) {
        this.proxy = proxy;
        this.timeout = timeout;
    }
    @Override
    public HttpURLConnection openConnection(URL url) throws IOException, ClassCastException {
        HttpURLConnection urlConnection = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy));
        if (timeout != 0) {
            // 设置超时时刻
            urlConnection.setConnectTimeout(timeout);
            urlConnection.setReadTimeout(timeout);
        }
        return urlConnection;
    }
}
  1. 自定义TimeoutNetHttpTransport,保存GoogleNetHttpTransport装备并装备自定义的ConnectionFactory
public class TimeoutNetHttpTransport {
    public static NetHttpTransport newTimeoutTransport(int timeout) throws GeneralSecurityException, IOException {
        MtlsProvider mtlsProvider = MtlsUtils.getDefaultMtlsProvider();
        KeyStore mtlsKeyStore = null;
        String mtlsKeyStorePassword = null;
        if (mtlsProvider.useMtlsClientCertificate()) {
            mtlsKeyStore = mtlsProvider.getKeyStore();
            mtlsKeyStorePassword = mtlsProvider.getKeyStorePassword();
        }
        if (mtlsKeyStore != null && mtlsKeyStorePassword != null) {
            return new NetHttpTransport.Builder()
                    .trustCertificates(GoogleUtils.getCertificateTrustStore(), mtlsKeyStore, mtlsKeyStorePassword)
                    .setConnectionFactory(new TimeoutConnectionFactory(timeout))
                    .build();
        }
        return new NetHttpTransport.Builder()
                .trustCertificates(GoogleUtils.getCertificateTrustStore())
                .setConnectionFactory(new TimeoutConnectionFactory(timeout))
                .build();
    }
}
  1. 自定义TimeoutCredentialsAdapter,保存HttpCredentialsAdapter装备并装备超时时刻。
public class TimeoutCredentialsAdapter implements HttpRequestInitializer, HttpUnsuccessfulResponseHandler {
    private static final Logger LOGGER = Logger.getLogger(HttpCredentialsAdapter.class.getName());
    private static final Pattern INVALID_TOKEN_ERROR = Pattern.compile("\s*error\s*=\s*"?invalid_token"?");
    static final String BEARER_PREFIX = AuthHttpConstants.BEARER + " ";
    private final Credentials credentials;
    private final int timeout;
    public TimeoutCredentialsAdapter(Credentials credentials, int timeout) {
        Preconditions.checkNotNull(credentials);
        this.credentials = credentials;
        this.timeout = timeout;
    }
    public Credentials getCredentials() {
        return credentials;
    }
    @Override
    public void initialize(HttpRequest request) throws IOException {
        request.setUnsuccessfulResponseHandler(this);
        if (timeout != 0) {
            request.setConnectTimeout(timeout);
            request.setReadTimeout(timeout);
            request.setWriteTimeout(timeout);
        }
        if (!credentials.hasRequestMetadata()) {
            return;
        }
        HttpHeaders requestHeaders = request.getHeaders();
        URI uri = null;
        if (request.getUrl() != null) {
            uri = request.getUrl().toURI();
        }
        Map<String, List<String>> credentialHeaders = credentials.getRequestMetadata(uri);
        if (credentialHeaders == null) {
            return;
        }
        for (Map.Entry<String, List<String>> entry : credentialHeaders.entrySet()) {
            String headerName = entry.getKey();
            List<String> requestValues = new ArrayList<>(entry.getValue());
            requestHeaders.put(headerName, requestValues);
        }
    }
    @Override
    public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry) throws IOException {
        boolean refreshToken = false;
        boolean bearer = false;
        List<String> authenticateList = response.getHeaders().getAuthenticateAsList();
        // if authenticate list is not null we will check if one of the entries contains "Bearer"
        if (authenticateList != null) {
            for (String authenticate : authenticateList) {
                if (authenticate.startsWith(BEARER_PREFIX)) {
                    // mark that we found a "Bearer" value, and check if there is a invalid_token error
                    bearer = true;
                    refreshToken = INVALID_TOKEN_ERROR.matcher(authenticate).find();
                    break;
                }
            }
        }
        // if "Bearer" wasn't found, we will refresh the token, if we got 401
        if (!bearer) {
            refreshToken = response.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED;
        }
        if (refreshToken) {
            try {
                credentials.refresh();
                initialize(request);
                return true;
            } catch (IOException exception) {
                LOGGER.log(Level.SEVERE, "unable to refresh token", exception);
            }
        }
        return false;
    }
}
  1. 修正获取凭证、上传aab代码(这里只列出需求调整的部分,其余与之前共同)。
public class UploadAab {
    /**
     * 超时时刻
     */
    private static final int TIMEOUT = 2 * 60 * 1000;
    public static void main(String[] args) {
        ...
        NetHttpTransport httpTransport = TimeoutNetHttpTransport.newTimeoutTransport(TIMEOUT);
        TimeoutCredentialsAdapter httpCredentialsAdapter = new TimeoutCredentialsAdapter(googleCredentials,TIMEOUT);
        AndroidPublisher publisher = new AndroidPublisher.Builder(httpTransport, GsonFactory.getDefaultInstance(), httpCredentialsAdapter)
                .setApplicationName(APPLICATION_NAME)
                .build();
    }
}

修正完后就能够正常上传了。上传完成后在Google Play Console后台就能够看到刚刚上传的aab。

使用Google Play Publishing API 上传aab

示例

演示代码已在示例Demo中增加。

ExampleDemo github

ExampleDemo gitee

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。