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 BoMimplementation platform('com.google.firebase:firebase-bom:28.4.2')// Declare the dependencies for the Firebase Cloud Messaging and Analytics librariesimplementation 'com.google.firebase:firebase-analytics'// When using the BoM, you don't specify versions in Firebase library dependenciesimplementation 'com.google.firebase:firebase-messaging'implementation 'com.google.firebase:firebase-core'// ========== google ==========// google signimplementation '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 paydef billing_version = "4.0.0"implementation "com.android.billingclient:billing:$billing_version"// google playimplementation 'com.google.android.gms:play-services-location:18.0.0'// ========== facebook ==========// facebook loginimplementation '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();// 初始化MultiDexMultiDex.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><!--FirebaseFirebase Cloud Messaging--><serviceandroid: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-dataandroid: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 incomingnotification message. See README(https://goo.gl/6BKBk7) for more. --><meta-dataandroid:name="com.google.firebase.messaging.default_notification_color"android:resource="@mipmap/ic_launcher" /><!-- 自定义通知渠道 --><meta-dataandroid: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;// firebaseprivate FirebaseAuth mAuth;private String mGoogleSCId = "";// googleprivate GoogleSignInClient mGoogleSignInClient;// facebookprivate 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>() {@Overridepublic void onComplete(@NonNull Task<String> task) {if (!task.isSuccessful()) {Log.w(TAG, "Fetching FCM registration token failed", task.getException());return;}// Get new FCM registration tokenString token = task.getResult();// Log and toastLog.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;}//googleif( 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 FirebaseGoogleSignInAccount 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 appropriatelymActivity.onLoinFailed();}}//fb登录if (fbCallbackManager != null) {fbCallbackManager.onActivityResult(requestCode, resultCode, data);}}// google -> firebaseprivate void firebaseAuthWithGoogle(String token){try {AuthCredential credential = GoogleAuthProvider.getCredential(token, null);mAuth.signInWithCredential(credential).addOnCompleteListener(mActivity, new OnCompleteListener<AuthResult>() {@Overridepublic 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 -> firebaseprivate 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>() {@Overridepublic 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 onFirebaseAuth.getInstance().signOut();// fbLoginManager.getInstance().logOut();// googlemGoogleSignInClient.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>() {@Overridepublic 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>() {@Overridepublic 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();}}@Overridepublic void onCancel() {Log.d(TAG, "facebook login failed[onCancel] ");mActivity.onLoinFailed();}//授权失败@Overridepublic 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 initFirebaseMessaging.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 FCMServiceFirebaseMessaging.getInstance().setAutoInitEnabled(true);}}@Overridepublic 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]@Overridepublic 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/39bRNJLog.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]@Overridepublic void onDeletedMessages() {super.onDeletedMessages();}@Overridepublic void onMessageSent(String s) {super.onMessageSent(s);}@Overridepublic 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*/@Overridepublic 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 initFirebaseMessaging.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 FCMServiceFirebaseMessaging.getInstance().setAutoInitEnabled(true);}

最后,非常简单。

  • 当我们不需要收到FCM的push时

      //关闭自动生成,不关闭会自动生成tokenFirebaseMessaging.getInstance().setAutoInitEnabled(false);//删除tokenFirebaseMessaging.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 支付(结算)

建议先看一遍官方文档。
官方文档
官方DEMO

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支付和服务端验证

部分问题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() {@Overridepublic 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 dataAdBrixRm.setEventUploadTimeInterval(AdBrixRm.AdBrixEventUploadTimeInterval.MIN);Log.d(TAG, "adbrix sdk version " + AdBrixRm.SDKVersion() );AdBrixRm.setLogListener(new AdBrixRm.LogListener() {@Overridepublic void onPrintLog(int i, String s) {Log.d(TAG, "msg " + s);}});AdBrixRm.setLocalPushMessageListener(new AdBrixRm.onTouchLocalPushListener() {@Overridepublic 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 belowLog.d(TAG, "onLoginAndLogout " + isLogin + " id " + userId);AdBrixRm.login(userId);}else{// When a user log out, send empty("") stringLog.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 APIAdBrixRm.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 APIAdBrixRm.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 APIAdBrixRm.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还是和上面两个挺不一样的。

  1. 下载它的demo
  2. 导入IAP6Helper module
  3. 添加依赖
    不知道为什么,我添加不进来。。。

只好走另一个不安全的方法。在settings.gradle里增加工程

//samsung lib
include ':IAP6Helper'
project(':IAP6Helper').projectDir = new File(settingsDir, 'app/IAP6Helper')

然后继续第三步。

2.无法测试

接是接完了,但是无法测试。

  1. 测试机是荣耀,安装Galaxy Store后,本地区/国家不支持。
  2. 模拟器,雷电模拟器,修改了定位和使用adb设置了一些android的属性,也不行。

学习了一波如何修改Andorid的prop

修改android的地区码

Android内提供了一些prop基础配置
因为Galaxy Store锁区了,所以修改Android的属性数据,来让我们可以进入。

用的是雷电模拟器

adb连接雷电模拟器失败
adb 如何连接多个设备
adb connect命令连接多个Android设备

如何更改Android模拟器中的移动国家/地区代码(MCC)?
【SIM】MCC(移动国家码)和 MNC(移动网络码)

运营商MCC,MNC大全

  1. 雷电模拟器自带了adb,用那个,然后

     // 已连接会提示已连接"./adb" connect 127.0.0.1:5555
    
  2. 查看设备

     C:\Users\????>adb devicesList of devices attachedemulator-1111  device127.0.0.1:30054 device
    
  3. 连接设备,使用它的shell

     如果是IP就用IP,设备名称就用设备名称adb connect 127.0.0.1:30054adb connect emulator-1111
    
  4. 查看有哪些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]...
    
  5. 修改MCC,MNC

    MCC(移动国家码)和 MNC(移动网络码)

     比如韩国的:MCC是450MNC是02-08所以他是 45002-45008// setprop <property name> <new MCC MNC>setprop gsm.operator.numeric 45002// 韩国的缩写是KR,语言是ko// 比如咱们是zh-CN,韩国就是ko-KRsetprop 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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 refrerrermReferrerClient = InstallReferrerClient.newBuilder(mActivity).build();}// mReferrerClient 连接到 google play storepublic void link2GooglePlayStore(){mReferrerClient.startConnection(new InstallReferrerStateListener() {@Overridepublic void onInstallReferrerSetupFinished(int responseCode) {switch (responseCode) {case InstallReferrerClient.InstallReferrerResponse.OK:// Connection established.Log.d(TAG, "Connection established");mIsLink = true;new Thread(new Runnable(){@Overridepublic 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;}}@Overridepublic 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(){@Overridepublic 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(){@Overridepublic void run() {try {//发送网络请求} catch (RemoteException e) {e.printStackTrace();}}
}).start();

2.获取Android DeviceID

我记录在了这里:

【Android】开发 需求&&问题 记录

七.一些其他问题记录

  • Facebook KeyHash生成方法
    这是facebook登录要

【Android】 Firebase登录、FCM、Google Play支付、adbrix、ONEStore、Galaxy Store、Google Install Referrer相关推荐

  1. 教你接入Google谷歌支付V3版本,图文讲解(Android、Unity)

    文章目录 一.前言 二.Google支付官方文档 三.Google支付Github Demo工程 四.Google支付流程图 五.Google支付接口讲解 1.初始化(监听支付事件) 2.连接Goog ...

  2. firebase登录验证_如何使用Firebase通过三步向身份验证本机添加身份验证

    firebase登录验证 Authentication allows us to secure our apps, or limit access for non-user members. Auth ...

  3. firebase登录验证_使用Firebase进行电话号码身份验证

    firebase登录验证 介绍 (Introduction) Ever since Firebase was introduced, I thought it would have a signifi ...

  4. Google Play支付 接入配置

    简述 公司业务出海接入 google play支付渠道时,往往不知道该如何在google play侧配置.业务在google play侧配置可以划分为: 准备google play账号 业务应用上传 ...

  5. Google Play支付失败问题解决

    一.确保Google Play配置正确 1.在Google Play Console上传测试版本APK,并发布测试版本 确定上传APK的包名.版本号.APK签名 确定APK上传时的Public Key ...

  6. coco2d-x游戏开发google play Google In-app-Billing 支付接入

    android google play接入一样的操作借用了网上别人博客的部分 改正的一些错误,主要介绍 cocos2d-x google play 接入不一样的部分 如果没有GooglePlay此处附 ...

  7. google play支付提示“此版本的应用程序未配置为通过Google Play结算。有关详情,请访问帮助中心。”

    [google play支付] 提示:遇到错误提示"此版本的应用程序未配置为通过Google Play结算.有关详情,请访问帮助中心." 原因是,当前版本VersionCode比上 ...

  8. 安卓集成google内支付

    Google Play Console创建应用 我的应用是后台人员完成的,具体的可以参考Google Play Console创建应用其中需要注意的是添加测试人员只是测试APK包,需要进行内支付测试则 ...

  9. Android注册登录页面

    Android注册登录页面 需求 分析 项目目录 .java domain JsonBean.java UserInfo.java utils GetJsonDataUtil.java Login.j ...

最新文章

  1. [Hibernate]在VS2010中应用NHibernate 3.2与MySQL
  2. 计算机视觉与深度学习 | 激光雷达(Lidar)发展史及其应用
  3. 计算机视觉与深度学习 | K-means聚类算法在计算机视觉中的应用之图像分割
  4. 第七课 循环神经网络与自然语言处理
  5. 读者试读怎么评价这本书
  6. ExtJS Grid中文字链接点击事件的更合理的处理办法
  7. 一个拨号上网的批处理文件
  8. 【唐宇迪】opencv实战学习
  9. [郝斌/王卓]数据结构C语句—链表
  10. PS亮度蒙版插件TKActions V5 Mac版
  11. windows编程经典书籍+VC++学习路线资料
  12. 一零二、Vue中自定义emoji表情包
  13. 鸿蒙系统王维,王维这句诗有多美?先被欧阳修“偷”了,后被苏轼“偷”了
  14. 用python进行简单的数据分析和数据可视化
  15. oracle中的dual详解
  16. 【转载】阿里云服务器忘记了实例密码怎么办
  17. GEA 4.1234 矩阵 矢量 点 四元数
  18. 扫雷• 规则: (1)随机产生 8*8 的雷分布图;• (2)用户“y”表示游戏,“q”退出游戏;• (3)让用户输入行列号,用键盘“m”标雷,“k”打开;• (4)打开区域为雷或者全部区
  19. 电脑自动安装软件怎么办?亲测有效
  20. java导出表功能_表格导出功能实践

热门文章

  1. nunjucks.js模板渲染
  2. 绿色IT实现的阻碍 IT企业污染严重
  3. 使用vue-admin-template基础模板开发后台管理系统必会技能
  4. KILE生成S19或者BIN文件
  5. 《锋利的jQuery》学习总结
  6. 信号与系统学习笔记(大纲)
  7. 微信小应用第一天(简介)
  8. 第八十三章 Caché 函数大全 $ZDATE 函数
  9. 【运维面试】面试官:你觉得网站访问慢的原因有哪些?
  10. IDEA修改中文字体为微软雅黑