【Android】 Firebase登录、FCM、Google Play支付、adbrix、ONEStore、Galaxy Store、Google Install Referrer
Android SDK接入
最近接了一个韩国的渠道,之前没有接过这些原生的sdk,头大啊。本来也不是搞android开发的,只能强搞了。还是国内的好啊,都给你整合完了。
如有问题,麻烦大佬指教一二,非常感谢。
一.Firebase,FCM,Google Login,fb Login
这里我把Google,Firebase,Facebook相关的都放在一起了,因为这些东西有很多的关联。
以下是官方文档,推荐先看一遍。
Firebase
FCM
Google Login
Facebook Login
1.相应app的build.gradle
顶部加上
// 声明是要使用谷歌服务框架
apply plugin: 'com.google.gms.google-services'
在 dependencies 里加上
dependencies {
// ========== firebase ==========
implementation 'com.google.firebase:firebase-auth:20.0.4'
// Import the Firebase BoM
implementation platform('com.google.firebase:firebase-bom:28.4.2')
// Declare the dependencies for the Firebase Cloud Messaging and Analytics libraries
implementation 'com.google.firebase:firebase-analytics'
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-core'
// ========== google ==========
// google sign
implementation 'com.google.android.gms:play-services-auth:19.0.0'
implementation 'androidx.work:work-runtime:2.5.0'
implementation 'com.google.android.gms:play-services-analytics-impl:17.0.0'
// google pay
def billing_version = "4.0.0"
implementation "com.android.billingclient:billing:$billing_version"
// google play
implementation 'com.google.android.gms:play-services-location:18.0.0'
// ========== facebook ==========
// facebook login
implementation 'com.facebook.android:facebook-login:9.0.0'
......
}
如果出现这个问题,请看完链接内容再继续看文档。
Error: Cannot fit requested classes in a single dex file
以下都是上面链接的内容:
项目貌似有点大,已经超过65k个方法。一个dex已经装不下了,需要个多个dex,也就是multidex ,因为Android系统定义总方法数是一个short int,short int 最大值为65536。
android {
defaultConfig {
// 这里添加
multiDexEnabled true
}
}
dependencies {
// 引入multidex库
implementation 'com.android.support:multidex:1.0.3'
...
...
}
在自定义的 application 中初始化 MultiDex
@Override
public void onCreate() {
super.onCreate();
// 初始化MultiDex
MultiDex.install(this);
}
2.AndroidManifest.xml
<application
...
<activity
...
</activity>
<!-- =================================== facebook =================================== -->
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<activity android:name="com.facebook.FacebookActivity"
android:configChanges= "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />
<activity android:name="com.facebook.CustomTabActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/fb_login_protocol_scheme" />
</intent-filter>
</activity>
<!--
Firebase
Firebase Cloud Messaging
-->
<service
android:name="com.???.AppFCMReceiver"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher"/>
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@mipmap/ic_launcher" />
<!-- 自定义通知渠道 -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/app_notification_channel_id" />
</application>
...
...
...
<!-- 权限相关 -->
<!-- google pay -->
<uses-permission android:name="com.android.vending.BILLING" />
<!-- google -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
这里的AppFCMReceiver是接收FCM信息用的Java代码,下面会贴出。
android:name="com.???.AppFCMReceiver"
其他没啥好注意的。如过怕有遗漏,最好按官方文档走一遍配置。
3.AppFirebase
public class AppFirebase {
private static final String TAG = "[AppFirebase]";
public static ???Activity mActivity;
// firebase
private FirebaseAuth mAuth;
private String mGoogleSCId = "";
// google
private GoogleSignInClient mGoogleSignInClient;
// facebook
private CallbackManager fbCallbackManager;
public AppFirebase(???Activity m){
mActivity = m;
mAuth = FirebaseAuth.getInstance();
// ======================== facebook ========================
fbCallbackManager = CallbackManager.Factory.create();
// ======================== google ========================
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(mGoogleSCId)
.requestEmail()
.build();
// Build a GoogleSignInClient with the options specified by gso.
mGoogleSignInClient = GoogleSignIn.getClient(mActivity, gso);
// ======================== fcm ========================
//测试用的,正式的时候别打开
getFCMRegisterTokenTest();
initSDK();
}
//测试用的,获得当前设备的token,填到firebase的那里,就可以专门发送消息了
private void getFCMRegisterTokenTest(){
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
@Override
public void onComplete(@NonNull Task<String> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "Fetching FCM registration token failed", task.getException());
return;
}
// Get new FCM registration token
String token = task.getResult();
// Log and toast
Log.d(TAG, token.toString());
Toast.makeText(mActivity, token.toString(), Toast.LENGTH_SHORT).show();
}
});
}
/**
======================================================================================
Firebase Login
======================================================================================
**/
// 登录
public void onLogin(String channel){
// 自动登录
if( checkFirebaseUserAuth() ){
Log.d(TAG, "auto login!");
firebaseGetAuthIdToken("login");
return;
}
//google
if( channel.equals("google") ){
Log.d(TAG, "start google login");
//启动登录,在onActivityResult方法回调
mActivity.startActivityForResult(mGoogleSignInClient.getSignInIntent(), 1001);
//facebook
}else if( channel.equals("facebook") ){
Log.d(TAG, "start facebook login");
LoginManager.getInstance().logInWithReadPermissions(mActivity, Arrays.asList("email", "public_profile") );
}
}
// google登录回调在这
public void onActivityResult(int requestCode, int resultCode, Intent data){
//google登录
if (requestCode == 1001) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
// Google Sign In was successful, authenticate with Firebase
GoogleSignInAccount account = task.getResult(ApiException.class);
if (account != null) {
//firebase验证google登录
Log.d(TAG, "firebaseAuthWithGoogle:" + account.getId() );
firebaseAuthWithGoogle(account.getIdToken() );
}else{ mActivity.onLoinFailed(); }
} catch (ApiException e) {
e.printStackTrace();
// Google Sign In failed, update UI appropriately
mActivity.onLoinFailed();
}
}
//fb登录
if (fbCallbackManager != null) {
fbCallbackManager.onActivityResult(requestCode, resultCode, data);
}
}
// google -> firebase
private void firebaseAuthWithGoogle(String token){
try {
AuthCredential credential = GoogleAuthProvider.getCredential(token, null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(mActivity, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
Log.d(TAG, "");
if ( task.isSuccessful() ) {
firebaseGetAuthIdToken("login");
} else {
mActivity.onLoinFailed();
}
}
});
} catch (Exception e) {
e.printStackTrace();
mActivity.onLoinFailed();
}
}
// facebook -> firebase
private void firebaseAuthWithFacebook(String token) {
// Log.d(TAG, "firebaseAuthWithFacebook token " + token.toString() );
try {
AuthCredential credential = FacebookAuthProvider.getCredential(token);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(mActivity, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if ( task.isSuccessful() ) {
Log.d(TAG, "firebaseAuthWithFacebook onComplete " + token.toString() );
firebaseGetAuthIdToken("login");
} else { mActivity.onLoinFailed(); }
}
});
} catch (Exception e) {
e.printStackTrace();
mActivity.onLoinFailed();
}
}
// 获取用户唯一标识
private void firebaseGetAuthIdToken(String behavior){
FirebaseUser user = mAuth.getCurrentUser();
user.getIdToken(true)
.addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
public void onComplete(@NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
String idToken = task.getResult().getToken();
// 登录
if( behavior.equals("login") ){
Log.d(TAG, "firebaseLogin idToken:" + idToken.toString() );
mActivity.onLoginSuccess(user.getUid().toString(), idToken);
}
} else {
Log.d(TAG, "firebas login fialed msg : " + task.toString() );
// 登录
if( behavior.equals("login") ){ mActivity.onLoinFailed(); }
}
}
});
}
/**
* 检查登录状态
* 如需要自动自动可接入,在授权登录成功后,本地会在一定期限内保存用户信息
*/
public boolean checkFirebaseUserAuth() {
FirebaseUser currentUser = mAuth.getCurrentUser();
if (currentUser != null) {
return true;
}
return false;
}
// 登出
public void onLogout(){
// google,facebook and so on
FirebaseAuth.getInstance().signOut();
// fb
LoginManager.getInstance().logOut();
// google
mGoogleSignInClient.signOut();
}
// 删除账户
public void onDeleteAccount(){
if(null == mAuth){
mActivity.onDeleteResult("failed");
return;
}
FirebaseUser user = mAuth.getCurrentUser();
if(null == user){
mActivity.onDeleteResult("failed");
return;
}
user.delete()
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful() ) {
onLogout();
mActivity.onDeleteResult("success");
}else{
mActivity.onDeleteResult("failed");
}
}
});
}
// 初始化部分功能
private void initSDK(){
LoginManager.getInstance().registerCallback(fbCallbackManager, new FacebookCallback<LoginResult>() {
@Override
public void onSuccess(LoginResult loginResult) {
//facebook授权成功,去firebase验证
if (loginResult != null) {
AccessToken accessToken = loginResult.getAccessToken();
if (accessToken != null) {
String token = accessToken.getToken();
firebaseAuthWithFacebook(token);
}else{mActivity.onLoinFailed();}
}else{mActivity.onLoinFailed();}
}
@Override
public void onCancel() {
Log.d(TAG, "facebook login failed[onCancel] ");
mActivity.onLoinFailed();
}
//授权失败
@Override
public void onError(FacebookException error) {
Log.d(TAG, "facebook login failed[onError] " + error.toString() );
mActivity.onLoinFailed();
}
});
}
}
a.Firebase使用Google和Facebook登录
参考文章:
Android_Google登录和Facebook登录并使用Firebase身份验证
其实就是先从Google/Facebook登录之后,拿到人家的token再去Firebase登录,用Firebase的token登录自己的服务器。
b.getFCMRegisterTokenTest()
这个方法得到的token,用于在Firebase Cloud Message里用于测试消息的。把本机的token填上,然后点测试就可以直接收到信息。
4.Firebase Cloud Message
public class AppFCMService extends FirebaseMessagingService {
public static String TAG = "[AppFCMService]";
public static ???Activity MainActivity;
// 和token相关
public static void checkFCMEnabled(){
if(!MainActivity.canReceivePush){
Log.d(TAG, "disableFCM");
// Disable auto init
FirebaseMessaging.getInstance().setAutoInitEnabled(false);
new Thread(() -> {
// Remove InstanceID initiate to unsubscribe all topic
// TODO: May be a better way to use FirebaseMessaging.getInstance().unsubscribeFromTopic()
FirebaseMessaging.getInstance().deleteToken();
}).start();
}else{
Log.d(TAG, "enableFCM");
// Enable FCM via enable Auto-init service which generate new token and receive in FCMService
FirebaseMessaging.getInstance().setAutoInitEnabled(true);
}
}
@Override
public void onCreate(){
Log.d(TAG, "onCreate");
super.onCreate();
}
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
// [START receive_message]
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "onMessageReceived " + remoteMessage.toString() );
super.onMessageReceived(remoteMessage);
// [START_EXCLUDE]
// There are two types of messages data messages and notification messages. Data messages
// are handled
// here in onMessageReceived whether the app is in the foreground or background. Data
// messages are the type
// traditionally used with GCM. Notification messages are only received here in
// onMessageReceived when the app
// is in the foreground. When the app is in the background an automatically generated
// notification is displayed.
// When the user taps on the notification they are returned to the app. Messages
// containing both notification
// and data payloads are treated as notification messages. The Firebase console always
// sends notification
// messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
// [END_EXCLUDE]
// TODO(developer): Handle FCM messages here.
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
Log.d(TAG, "From: " + remoteMessage.getFrom() );
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData() );
if (/* Check if data needs to be processed by long running job */ true) {
// For long-running tasks (10 seconds or more) use WorkManager.
// scheduleJob();
} else {
// Handle message within 10 seconds
// handleNow();
}
}
// Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody() );
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
sendNotification( remoteMessage.getNotification() );
}
// [END receive_message]
@Override
public void onDeletedMessages() {
super.onDeletedMessages();
}
@Override
public void onMessageSent(String s) {
super.onMessageSent(s);
}
@Override
public void onSendError(String s, Exception e) {
super.onSendError(s, e);
}
// [START on_new_token]
/**
* There are two scenarios when onNewToken is called:
* 1) When a new token is generated on initial app startup
* 2) Whenever an existing token is changed
* Under #2, there are three scenarios when the existing token is changed:
* A) App is restored to a new device
* B) User uninstalls/reinstalls the app
* C) User clears app data
*/
@Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
super.onNewToken(token);
AppFCMService.checkFCMEnabled();
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// FCM registration token to your app server.
sendRegistrationToServer(token);
}
// [END on_new_token]
/**
* Schedule async work using WorkManager.
*/
private void scheduleJob() {
// [START dispatch_job]
// OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(MyWorker.class)
// .build();
// WorkManager.getInstance(this).beginWith(work).enqueue();
// [END dispatch_job]
}
/**
* Handle time allotted to BroadcastReceivers.
*/
private void handleNow() {
Log.d(TAG, "Short lived task is done.");
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM registration token with any
* server-side account maintained by your application.
*
* @param token The new token.
*/
private void sendRegistrationToServer(String token) {
// TODO: Implement this method to send token to your app server.
}
/**
* Create and show a simple notification containing the received FCM message.
*
* @param message FCM message body received.
*/
private void sendNotification(RemoteMessage.Notification message) {
Log.d(TAG, message.toString());
NotificationManager notificationManager = (NotificationManager) MainActivity.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder;
//Android8.0要求设置通知渠道
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("foreground", "foreground", NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(true); //设置是否显示角标
//设置是否应在锁定屏幕上显示此频道的通知
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
//设置渠道描述
channel.setDescription("foreground");
notificationManager.createNotificationChannel(channel);
// createNotificationChannelGroups();
notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("foreground", "foreground"));
// setNotificationChannelGroups(channel);
channel.setGroup("foreground");
builder = new NotificationCompat.Builder(this, "foreground");
} else {
//为了版本兼容 选择V7包下的NotificationCompat进行构造
builder = new NotificationCompat.Builder(this);
//setTicker 在5.0以上不显示Ticker属性信息
builder.setTicker(message.getTicker());
}
if(null == builder) return;
//setContentTitle 通知栏通知的标题
builder.setContentTitle(message.getTitle());
//setContentText 通知栏通知的详细内容
builder.setContentText(message.getBody());
//setAutoCancel 点击通知的清除按钮是否清除该消息(true/false)
builder.setAutoCancel(true);
//setLargeIcon 通知消息上的大图标
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
//setSmallIcon 通知上面的小图标
builder.setSmallIcon(R.mipmap.ic_launcher);//小图标
//创建一个意图
Intent intent = new Intent(this, MainActivity.getClass());
PendingIntent pIntent = PendingIntent.getActivity(this, 1001, intent, PendingIntent.FLAG_NO_CREATE);
//setContentIntent 将意图设置到通知上
builder.setContentIntent(pIntent);
builder.setWhen(System.currentTimeMillis());
//通知默认的声音 震动 呼吸灯
builder.setDefaults(NotificationCompat.DEFAULT_ALL);
//构建通知
Notification notification = builder.build();
// notification.priority = NotificationManager.IMPORTANCE_MAX;
//将构建好的通知添加到通知管理器中,执行通知
notificationManager.notify(0, notification);
}
}
参考文章:
Android计入Google FireBase之消息推送
关于 Android O 通知渠道总结
继承 FirebaseMessagingService 并且实现相关接口。
1.两种通知消息
-
Notification Message 通知消息。FCM Console发送的都是这种消息也就是说
- 当App在后台时,自动弹出到通知栏,我们无法用代码控制。
- 当App在前台时,不会自动弹出到通知栏,可以在 onMessageReceived() 收到数据。
-
Data Message 数据消息。 不论App在前后台,都只会在 onMessageReceived() 收到数据。
2.开关FCM
目前遇到了一个很棘手的问题。运营方想使用 FCM Console 来发送信息,但是FCM Console 发送的都是 Notification Message。
- App在后台时,无法用代码控制。
- FCM属于Android的Service,但是无法使用 stopSelf() 来关闭 FCM 功能。
- 调用 onDestory() 会导致程序崩溃。
后来发现,App在后台时,自动弹出收到的 notification message 和 FirebaseMessagingService 是无关的。所以无论你怎么操作继承于 FirebaseMessagingService 类,都是阻止不了它弹出的。
3.FCM 到底是如何接入的?
运营商一直和我说其他开发商也是可以通过 FCM Console 发送消息的。但是 FCM Console 发送的消息属于 Notification Message ,App在后台时,应该是不可控的才对。
a.主题订略
Firebase on Android - I want to disable firebase notifications on Android client
if(!canReceivePush){
FirebaseMessaging.getInstance().subscribeToTopic(”Android");
}else{
FirebaseMessaging.getInstance().unsubscribeFromTopic("Android");
}
通过如上代码,以及 FCM Console 后台的操作
可以让用户只收到自己想收到的主题信息。
这种方法有两个问题。
b.其实只是和token相关而已
也是醉了,害我搞了好久,还一直咨询运营那边。
官方文档也没有直接告诉你,如何开关。而且它也没有什么开关的代码。并且和 服务无关,真正管理的是它的token。
Firebase Cloud Messaging - Handling logout
if(关闭FCM服务){
Log.d(TAG, "disableFCM");
// Disable auto init
FirebaseMessaging.getInstance().setAutoInitEnabled(false);
new Thread(() -> {
// Remove InstanceID initiate to unsubscribe all topic
// TODO: May be a better way to use FirebaseMessaging.getInstance().unsubscribeFromTopic()
FirebaseMessaging.getInstance().deleteToken();
}).start();
}else{
Log.d(TAG, "enableFCM");
// Enable FCM via enable Auto-init service which generate new token and receive in FCMService
FirebaseMessaging.getInstance().setAutoInitEnabled(true);
}
最后,非常简单。
-
当我们不需要收到FCM的push时
//关闭自动生成,不关闭会自动生成token FirebaseMessaging.getInstance().setAutoInitEnabled(false); //删除token FirebaseMessaging.getInstance().deleteToken();
-
需要收到FCM的push时
//打开自动生成,然后就会自动生成 token ,自动调用 onNewToken() FirebaseMessaging.getInstance().setAutoInitEnabled(true);
c.FCM丰富的功能(拓展部分)
FCM其实还要很多很丰富的功能,比如:
- 服务器接入他的sdk
- 精简的js服务端,不需要自己搞个服务器,可以直接用它的。
- 数据分析
等等。
Android service ( 一 ) 三种开启服务方法
d.Firebase-FCM服务端开发
我查略的官方文档,以及开发商给的文档。都有提及token,以及发送token。
/**
* There are two scenarios when onNewToken is called:
* 1) When a new token is generated on initial app startup
* 2) Whenever an existing token is changed
* Under #2, there are three scenarios when the existing token is changed:
* A) App is restored to a new device
* B) User uninstalls/reinstalls the app
* C) User clears app data
*/
@Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
super.onNewToken(token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// FCM registration token to your app server.
sendRegistrationToServer(token);
}
我看了源码,发现。这个token是要我们自己的服务器去收集并实现的。
我们自己的服务器得实现一套用户设备token表,然后发送消息时,可以屏蔽我们不想发送的设备。
目前自己还没有使用这种方法。
二.Google Play 支付(结算)
Google Play分为游戏内购商品和订阅商品。这里只说内购商品的流程。
在完全不知道Google Play支付的流程下去弄,导致我白花费了很多时间。
所以我们先从Google Play支付的流程开始看。
参考文章:
1.教你接入Google谷歌支付V3版本,图文讲解(Android、Unity)
图来自文章中,这张图很清楚了,Google Play支付的流程图,以及该在什么地方处理
2.Google play 支付流程(App内购)
图来自文章。可以完全按照这个流程来接入你的Google Play支付。
其他参考文章:
google计费接入,Billing结算库支付
Google pay接入流程+无需真正付款
Cocos2dx-Lua游戏接入GooglePlay SDK支付
Android集成Google Pay流程以及注意事项
Android Google应用内支付(新的集成方式)
Google支付和服务端验证
Google Pay 5.0
Google pay5.0版本接入支付和订阅功能
Android Google支付接入
部分问题QA:
Google Pay支付遇到的问题
这些都文章讲的都很清楚了,接入过程不多赘述。自己遇到的坑点。
1.消耗以及确认订单
- 不消耗用户就再买此商品就会提示已拥有
- 不确认订单三天后就会退款
这两个步骤请放在服务器确认并发放相应物品的通知后进行,不要在收到Google Play的回传信息时处理,否则用户可能会收不到商品,但又确实花钱了。
2.透传“订单号”
为了方便购买,公司的 productId 不是和In-App商品一一对应的,所以我们需要再订单内加上自己内部使用的额订单号。
设置订单号
// 购买调起时,使用 setObfuscatedAccountId() 设置咱们的订单号
BillingResult response =
billingClient.launchBillingFlow(
mActivity,
BillingFlowParams
.newBuilder()
.setSkuDetails(“Google的productId")
// 这里本来的意思存放用户信息,类似于国内的透传参数,我这里传的我们的订单号。
// 老版本使用DeveloperPayload字段,最新版本中这个字段已不可用了
.setObfuscatedAccountId(orderId)
.build()
);
获取订单号
purchase.getAccountIdentifiers().getObfuscatedAccountId()
3.补单
得补单。之前一直都不需要客户端操作的,但是这次的要。。。
billingClient.queryPurchasesAsync(BillingClient.SkuType.?, new PurchasesResponseListener() {
@Override
public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> list) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
Log.d(TAG, "订单:" + list.size() );
Log.d(TAG, "订单数据:\n" + list.toString() );
for (Purchase purchase : list) {
// Process the result.
}
}
}
});
一般都是用queryPurchasesAsync获取订单的,看到也有用的queryPurchaseHistoryAsync,但是这种获取到的purchase是没有isAcknowledged的。
queryPurchases() vs queryPurchaseHistoryAsync() in order to ‘restore’ functionality?
摘抄自上面的链接:
You should use queryPurchases. That gives you all the current active (non-consumed, non-cancelled, non-expired) purchases for each SKU.
queryPurchaseHistoryAsync won’t do what you need because it will only give you a list of the most recent purchases for each SKU. They may have expired, been cancelled or been consumed, and there’s no way to tell. Therefore this response can’t be used to tell what purchases to apply in your app.
So far as I can see, the only valid use for queryPurchaseHistoryAsync is to provide a user with a list of their purchase history. It’s a bit of an oddball.
Note also: queryPurchases is synchronous so in most cases it needs to be run in some kind of background worker thread. I run mine in an AsyncTask.
每次登录,或者固定间隔调用一次就可以了。
三.Adbrix
一个用于数据收集的sdk,似乎只有韩国那边在用
1.Deeplink 和 DefferdDeeplink
你知道App推广神技“Deferred Deeplink”吗?
-
什么是Deeplink?
Deeplink是App应用中的深度链接,如果把App看做一个网站,那么Deeplink就是网站中的一个页面,比如产品页面,活动促销页面等。Deeplink在App市场推广运营中有很好的意义: -
什么是Deferred Deeplink?
Deferred Deeplink可以看做是Deeplink的一个升级技术,可以翻译为 ”延后深度链接“。如果用户没有安装过推广中的App,如何使用Deeplink技术啊?用户得先去应用商店下载App,然后安装打开App, 这样Deeplink不就没有用了?
确实如此,因此Deeplink只针对手机中已经安装过App的用户才有用。而升级版本的Deferred Deeplink却可以解决这个问题:
Deferred Deeplink可以先判断用户是否已经安装了App应用,如果没有则先引导至App应用商店中下载App, 在用户安装App后跳转到指定App页面Deeplink中。
使用Deeplink的广告商可以在用户点击广告后直接进入指定的Appp, 而没有使用Deeplink的App广告只能在点击后将用户跳转到App首页上。
以上内容来自文章。
这个功能不一定得接,看需求。我是没有接的。
public class AppAdbrix{
private AppActivity mActivity;
private static final String TAG = "[AppAdbrix]";
// 找运营商要
private static String mAppKey = "";
private static String mSecretKey = "";
public AppAdbrix(AppActivity m) {
mActivity = m;
}
public void onCreate() {
// ======================== adbrix ========================
AbxActivityHelper.initializeSdk(mActivity, mAppKey, mSecretKey);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// mActivity.registerActivityLifecycleCallbacks(new AbxActivityLifecycleCallbacks());
// }
// every 120 seconds upload event data
AdBrixRm.setEventUploadTimeInterval(AdBrixRm.AdBrixEventUploadTimeInterval.MIN);
Log.d(TAG, "adbrix sdk version " + AdBrixRm.SDKVersion() );
AdBrixRm.setLogListener(new AdBrixRm.LogListener() {
@Override
public void onPrintLog(int i, String s) {
Log.d(TAG, "msg " + s);
}
});
AdBrixRm.setLocalPushMessageListener(new AdBrixRm.onTouchLocalPushListener() {
@Override
public void onTouchLocalPush(String s) {
Log.d(TAG, "local push " + s);
}
});
}
public void onActivityResumed() {
Log.d(TAG, "resume");
AdBrixRm.onResume(mActivity);
}
public void onActivityPaused() {
Log.d(TAG, "pause");
AdBrixRm.onPause();
}
public void onActivityDestroyed() {
Log.d(TAG, "destory");
AdBrixRm.onDestroy(mActivity);
}
/**
* ==================== 数据统计 ====================
*/
/* 基础 */
// 登录登出
public void onLoginAndLogout(boolean isLogin, String userId){
if(isLogin){
// When a user log on, send "user_1234" like the below
Log.d(TAG, "onLoginAndLogout " + isLogin + " id " + userId);
AdBrixRm.login(userId);
}else{
// When a user log out, send empty("") string
Log.d(TAG, "onLoginAndLogout " + isLogin);
AdBrixRm.logout();
}
}
// 支付
public void onPaySuccess(){
if(null == goodsInfo) return;
try {
// 看文档
// Log.d(TAG, "adbrix on purchase");
}catch (JSONException e){
e.printStackTrace();
}
}
/* 其他检测点 */
// 完成新手引导
public void onTutorialCompletion(){
try {
// 看文档
AdBrixRm.AttrModel gameAttr = new AdBrixRm.AttrModel()
.setAttrs("xx", "");
AdBrixRm.GameProperties.TutorialComplete gameProperties
= new AdBrixRm.GameProperties.TutorialComplete()
.setIsSkip(false)
.setAttrModel(gameAttr);
// Tutorial complete API
AdBrixRm.Game.tutorialComplete(gameProperties);
}catch (JSONException e){
e.printStackTrace();
}
}
// 角色生成
public void onCharacterCreation(){
// 看文档
AdBrixRm.AttrModel gameAttr = new AdBrixRm.AttrModel().setAttrs("xx", xx);
AdBrixRm.GameProperties.CharacterCreated gameProperties
= new AdBrixRm.GameProperties.CharacterCreated()
.setAttrModel(gameAttr);
// Create character API
AdBrixRm.Game.characterCreated(gameProperties);
// Log.d(TAG, "adbrix on character creation");
}
//角色升级
public void onLevelUp(){
try {
// 看文档
int level = Integer.valueOf( json.getString("level") );
AdBrixRm.AttrModel gameAttr = new AdBrixRm.AttrModel().setAttrs("uid", json.getString("uid") );
AdBrixRm.GameProperties.LevelAchieved gameProperties
= new AdBrixRm.GameProperties.LevelAchieved()
.setLevel(level)
.setAttrModel(gameAttr);
// Level Acheived API
AdBrixRm.Game.levelAchieved(gameProperties);
}catch (JSONException e){
e.printStackTrace();
}
}
}
- 一些具体的传输数据我没有填,这些都好找,直接看官方文档即可。
- 一些比如,充值,角色创建,升级,有专门的API,不用写事件名称。
唯一坑到我的点
// 以下是官方文档的示例
if (Build.VERSION.SDK_INT >= 14) {
registerActivityLifecycleCallbacks(this);
}
// 以下为我实际使用时,可以通过studio检测的代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mActivity.registerActivityLifecycleCallbacks(new AbxActivityLifecycleCallbacks());
}
不知道是我的问题,还是官方文档的错误,要求的最低Android版本差了十几版本。
并且执行代码的时候,都顺利通过,并且Logcat啥也没输出,我都不知道到底是什么问题。
上面的代码,作用就是注册生命周期的回调的时候,调用如下代码即可。
If you are using your own ActivityLifecycleCallback please add following code on “onActivityResumed” , “onActivityPaused”, “onActivityDestroyed”.
所以,直接在主Activity的这三个生命周期的函数里调用一下就可以了。
public void onActivityResumed() {
Log.d(TAG, "resume");
AdBrixRm.onResume(mActivity);
}
public void onActivityPaused() {
Log.d(TAG, "pause");
AdBrixRm.onPause();
}
public void onActivityDestroyed() {
Log.d(TAG, "destory");
AdBrixRm.onDestroy(mActivity);
}
弄完这个问题项目就正常了,后台也能收到数据了。会有些延迟,过个几分钟看看后台吧。
四.ONEStore(ONE-Store)
ONE-store和Google其实差不多。但是因为ONE-store会自动推送信息给服务端,其实我们是不需要补单的。
看完ONE-store的官方文档后,重新理解了一遍Google。
Google和ONE-store都有消耗型商品和非消耗型商品。
- 消耗型商品,需要使用 consumexxx 这种名称的方法进行消耗;
- 非消耗型商品,需要使用 acknowledgexxx 这种名称的方法;
这两种商品,不走下一步,那么会都是坏单,过一段时间应该都会退回费用。
然而我们怎么判断一个单子是否处理过了呢?用如下代码判断。
x.isAcknowledged()
// 这个acknowledged,是和非消耗型商品的acknowledge无关的
// 消耗型商品调用 consumeXXX ----> isAcknowledged() 为 true
// 非消耗型商品调用 acknowledgeXXX ----> isAcknowledged() 为 true
透传"订单号"
好像就是老版的谷歌
设置订单号
PurchaseFlowParams params = PurchaseFlowParams.newBuilder()
.setProductId(productId)
.setProductType(ProductType.INAPP)
.setDeveloperPayload(orderId)
.build();
获取订单号
purchase.getDeveloperPayload()
PS
测的时候,先把ONE-store打开一次,可以正常连上再测试。
可能是ONE-store App的原因。之前测试的时候,如果不打开一次,那么就会一直连不上ONE-store的服务器,一直报 error code 3
五.Galaxy Store(Samsung)
参考文章
韩国渠道接入三星支付(Android 接入 Samsung in app purchase)
其实和ONE-store一样,还是蛮好接入的,并且都有自己的文档。
1.IAP6Helper module 无法导入
Samsung还是和上面两个挺不一样的。
- 下载它的demo
- 导入IAP6Helper module
- 添加依赖
不知道为什么,我添加不进来。。。
只好走另一个不安全的方法。在settings.gradle里增加工程
//samsung lib
include ':IAP6Helper'
project(':IAP6Helper').projectDir = new File(settingsDir, 'app/IAP6Helper')
然后继续第三步。
2.无法测试
接是接完了,但是无法测试。
- 测试机是荣耀,安装Galaxy Store后,本地区/国家不支持。
- 模拟器,雷电模拟器,修改了定位和使用adb设置了一些android的属性,也不行。
学习了一波如何修改Andorid的prop
修改android的地区码
Android内提供了一些prop基础配置
因为Galaxy Store锁区了,所以修改Android的属性数据,来让我们可以进入。
用的是雷电模拟器
adb连接雷电模拟器失败
adb 如何连接多个设备
adb connect命令连接多个Android设备
如何更改Android模拟器中的移动国家/地区代码(MCC)?
【SIM】MCC(移动国家码)和 MNC(移动网络码)
-
雷电模拟器自带了adb,用那个,然后
// 已连接会提示已连接 "./adb" connect 127.0.0.1:5555
-
查看设备
C:\Users\????>adb devices List of devices attached emulator-1111 device 127.0.0.1:30054 device
-
连接设备,使用它的shell
如果是IP就用IP,设备名称就用设备名称 adb connect 127.0.0.1:30054 adb connect emulator-1111
-
查看有哪些prop
getprop // 会显示如下数据 ... [gsm.network.type]: [LTE] [gsm.nitz.time]: [1524141151210] [gsm.operator.alpha]: [Android] [gsm.operator.iso-country]: [us] [gsm.operator.isroaming]: [false] [gsm.operator.numeric]: [310260] [gsm.sim.operator.alpha]: [Android] [gsm.sim.operator.iso-country]: [us] [gsm.sim.operator.numeric]: [310260] [gsm.sim.state]: [READY] [gsm.version.baseband]: [1.0.0.0] [gsm.version.ril-impl]: [android reference-ril 1.0] ...
-
修改MCC,MNC
MCC(移动国家码)和 MNC(移动网络码)比如韩国的: MCC是450 MNC是02-08 所以他是 45002-45008 // setprop <property name> <new MCC MNC> setprop gsm.operator.numeric 45002 // 韩国的缩写是KR,语言是ko // 比如咱们是zh-CN,韩国就是ko-KR setprop gsm.operator.iso-country KR
还是得用三星的手机测试才行,其他牌子的手机真不好测。
3.代码
Galaxy Store的代码相对较少
public static String TAG = "xxx";
private AppActivity mActivity;
private IapHelper mHelper;
public AppSamsungPay(AppActivity m){
mActivity = m;
mHelper = IapHelper.getInstance(mActivity);
// mHelper.setOperationMode(HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION)//正式模式
// mHelper.setOperationMode(HelperDefine.OperationMode.OPERATION_MODE_TEST_FAILURE);//失败模式
mHelper.setOperationMode(HelperDefine.OperationMode.OPERATION_MODE_TEST);//测试模式
}
// 开始购买
public void onPay(String productId, String orderId){
//productId为商品id, orderId为透传字段, false为是否显示dialog
// orderId里是我需要传的,你自己要传啥问服务端
mHelper.startPayment(productId, orderId, new OnPaymentListener() {
@Override
public void onPayment(ErrorVo _errorVO, PurchaseVo _purchaseVO) {
if( _errorVO == null) {
// 购买失败调用
return;
}
//购买成功
if (_errorVO.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
// 购买成功调用
//购买失败
}else {
// 购买失败调用
}
}
});
}
// 购买完成后的处理(消耗商品)
public void onConsumePurchase(String purchaseId, String originalJson){
mHelper.consumePurchasedItems(purchaseId, new OnConsumePurchasedItemsListener() {
@Override
public void onConsumePurchasedItems(ErrorVo errorVo, ArrayList<ConsumeVo> arrayList) {
if(errorVo == null ) {
//消耗失败调用
return;
}
//消耗成功
if(errorVo.getErrorCode() == IapHelper.IAP_ERROR_NONE){
//消耗成功调用
//消耗失败
}else{
//消耗失败调用
}
}
});
}
//补单
public void onCheckPurchase(){
mHelper.getOwnedList(IapHelper.PRODUCT_TYPE_ALL, new OnGetOwnedListListener() {
@Override
public void onGetOwnedProducts(ErrorVo errorVo, ArrayList<OwnedProductVo> ownedList) {
if( errorVo == null){
//
return;
}
if (errorVo.getErrorCode() != IapHelper.IAP_ERROR_NONE
|| ownedList == null || ownedList.size() <= 0){
//
return;
}
for (int i = 0; i < ownedList.size(); i++) {
OwnedProductVo product = ownedList.get(i);
//未消耗(确认)的商品
if( product.getIsConsumable() ){
...
}
}
}
});
}
透传"订单号"
设置透传参数上面已经有写了,这里就写如何获得
product.getPassThroughParam()
六.Google installreferrer
android-GooglePlay安装来源追踪PlayInstallReferrer
public class XXXXX{
public static String TAG = "[XXXXXX]";
private static final String KEY_UDID = "KEY_UDID";
private String uuid;
private AppActivity mActivity;
private InstallReferrerClient mReferrerClient;
private boolean mIsLink = false;
private String sendUrl = "http://???.php";
public XXXXXXX(AppActivity m){
mActivity = m;
//install refrerrer
mReferrerClient = InstallReferrerClient.newBuilder(mActivity).build();
}
// mReferrerClient 连接到 google play store
public void link2GooglePlayStore(){
mReferrerClient.startConnection(new InstallReferrerStateListener() {
@Override
public void onInstallReferrerSetupFinished(int responseCode) {
switch (responseCode) {
case InstallReferrerClient.InstallReferrerResponse.OK:
// Connection established.
Log.d(TAG, "Connection established");
mIsLink = true;
new Thread(new Runnable(){
@Override
public void run() {
try {
sendInstallReferrerInfo();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
break;
case InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED:
// API not available on the current Play Store app.
Log.d(TAG, "API not available on the current Play Store app.");
stopLink();
break;
case InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE:
// Connection couldn't be established.
Log.d(TAG, "Connection couldn't be established.");
stopLink();
break;
}
}
@Override
public void onInstallReferrerServiceDisconnected() {
stopLink();
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
public void testSend(){
new Thread(new Runnable(){
@Override
public void run() {
try {
sendInstallReferrerInfo();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
// 获取设备信息
// 从安装引荐来源获取详细信息
public void sendInstallReferrerInfo() throws RemoteException {
if(null == mReferrerClient){
Log.d(TAG, "InstallReferrerClient is not exist.");
return;
}
if(mIsLink == false){
Log.d(TAG, "InstallReferrerClient is not link.");
return;
}
ReferrerDetails response = mReferrerClient.getInstallReferrer();
// String referrerUrl = response.getInstallReferrer();
// long referrerClickTime = response.getReferrerClickTimestampSeconds();
// long appInstallTime = response.getInstallBeginTimestampSeconds();
// boolean instantExperienceLaunched = response.getGooglePlayInstantParam();
try {
URL url = new URL(sendUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
connection.connect();
JsonData jdata = new JsonData();
// 一般是要这个数据
jdata.addKV("xxx", response.getInstallReferrer() );
// getUniqueDeviceId() 看下面的文章里
jdata.addKV("deviceId", getUniqueDeviceId() );
String body = jdata.toString();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
writer.write(body);
writer.close();
int responseCode = connection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
InputStream inputStream = connection.getInputStream();
// Log.d(TAG, "inputStream " + inputStream.toString() );
}
Log.d(TAG, "sendInstallReferrerInfo success");
} catch (Exception e) {
Log.d(TAG, "sendInstallReferrerInfo faild");
e.printStackTrace();
}
}
// 断开链接
public void stopLink(){
mIsLink = false;
mReferrerClient.endConnection();
}
1.Android 发送http请求
HttpUrlConnection使用详解
上面的网络代码就是从这里找到的,里面很全,不只有 POST 的
[Android开发错误解决]解决android.os.NetworkOnMainThreadException
在Android 4.0以上,网络连接不能放在主线程上,不然就会报错android.os.NetworkOnMainThreadException。但是4.0下版本可以不会报错。
所以记得,发送请求时,不能直接发送,要新开一个线程。
new Thread(new Runnable(){
@Override
public void run() {
try {
//发送网络请求
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
2.获取Android DeviceID
我记录在了这里:
七.一些其他问题记录
- Facebook KeyHash生成方法
这是facebook登录要