App开发完成后通常会上架到使用商店,海外一般上架GooglePlay。除了直接在Google Play Console后台操作外,还能够运用Google Play Developer Publishing API上传aab。本文介绍如何运用Google Play Developer Publishing API上传aab。
官方文档
前置步骤
1. 敞开API权限
- 在Google Play Console后台设置中找到API权限,挑选相关项目。

- 挑选相关已有的Google Cloud项目或许创立新项目,挑选完后保存。

- 相关完之后,能够从此页面翻开对应的Google Cloud项目。

2. 创立服务账号和密钥
- 在Google Cloud项目页面挑选API和服务。

- 在API和服务页面,挑选凭证->创立凭证->服务账号。

- 在创立服务账号页面填写账号称号,点击创立并持续。

- 挑选角色,我这边选的是Owner,点击持续。

- 能够挑选对其他用户账号授予权限,也能够不填,点击完成。

- 点击已创立的服务账号,进入服务账号页面。

- 在服务账号页面,挑选密钥->增加密钥->创立新密钥。

- 在创立私钥页面,挑选JSON类型,点击创立,保存生成的JSON文件。

3. 在API权限中增加服务账号
- 回来Google Play Console,在API权限中,挑选管理中心权限。

- 在约请页面中,选中发布版本相关的权限,点击约请用户。

运用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报错,超时异常。

官方文档和Demo没有相关示例。自己尝试了一番后,得出解决方案如下:
- 自定义
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;
}
}
- 自定义
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();
}
}
- 自定义
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;
}
}
- 修正获取凭证、上传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。

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