最近公司接了一个韩国的渠道,还是国内的SDK好啊,都给你整完了。
不是专门搞Android IOS SDK这块的,如有问题请指教,谢谢。
一.Firebase,FCM,Google Login,fb Login这里我把Google,Firebase,Facebook相关的都放在一起了,因为这些东西有很多的关联。
以下是官方文档,推荐先看一遍。
Firebase
FCM
Google Login
Facebook Login
顶部加上
// 声明是要使用谷歌服务框架 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
...
...
...
这里的AppFirebase是接收FCM信息用的Java代码,下面会贴出。
android:name="com.???.AppFirebase"
其他没啥好注意的。如过怕有遗漏,最好按官方文档走一遍配置。
3.AppFirebase
public class AppFirebase extends FirebaseMessagingService{
private static final String TAG = "[AppFirebase]";
public static AppActivity mActivity;
// firebase
private FirebaseAuth mAuth;
// google client-server Id 这个会有提供给的,或者在提供的 google-services.json 里找
private String mGoogleSCId = "";
// google
private GoogleSignInClient mGoogleSignInClient;
// facebook
private CallbackManager fbCallbackManager;
// 好像是因为AndroidManifest.xml里面的设置,需要有一个无参的生成函数
// 每次收到消息都会创建一个
// 每次都会创建一个会不会有问题呢?
public AppFirebase() {}
public AppFirebase(AppActivity 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();
}
// 登录
public void onLogin(String channel){
//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") );
LoginManager.getInstance().registerCallback(fbCallbackManager, new FacebookCallback() {
@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() { mActivity.onLoinFailed(); }
//授权失败
@Override
public void onError(FacebookException error) { mActivity.onLoinFailed(); }
});
}
}
// google登录回调在这
// 你的主Activity别忘了加个调用!!!!!
public void onActivityResult(int requestCode, int resultCode, Intent data){
//google登录
if (requestCode == 1001) {
Task 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() {
@Override
public void onComplete(@NonNull Task task) {
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() {
@Override
public void onComplete(@NonNull Task 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() {
public void onComplete(@NonNull Task 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 {
// 登录
if( behavior.equals("login") ){ mActivity.onLoinFailed(); }
}
}
});
}
public void checkFirebaseUserAuth() {
FirebaseUser currentUser = mAuth.getCurrentUser();
if (currentUser != null) {
String displayName = currentUser.getDisplayName();
String email = currentUser.getEmail();
String uid = currentUser.getUid();
// 如果过查到用户信息说明已经登录过而且没过期或者没有登出
// 直接上传服务端,登录成功
// uploadServer(currentUser);
}
}
// 登出
public void onLogout(){
// google,facebook and so on
FirebaseAuth.getInstance().signOut();
// fb
LoginManager.getInstance().logOut();
// google
mGoogleSignInClient.signOut();
}
//测试用的,获得当前设备的token,填到firebase的那里,就可以专门发送消息了
private void getFCMRegisterTokenTest(){
FirebaseMessaging.getInstance().getToken()
.addonCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task 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();
}
});
}
// [START receive_message]
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
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 ( 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]
@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);
}
// [END on_new_token]
private void scheduleJob() {
// [START dispatch_job]
// oneTimeWorkRequest work = new OneTimeWorkRequest.Builder(MyWorker.class)
// .build();
// WorkManager.getInstance(this).beginWith(work).enqueue();
// [END dispatch_job]
}
private void handleNow() {
Log.d(TAG, "Short lived task is done.");
}
private void sendRegistrationToServer(String token) {
// TODO: Implement this method to send token to your app server.
}
private void sendNotification(RemoteMessage.Notification message) {
if(null == mActivity) return;
NotificationManager notificationManager = (NotificationManager) mActivity.getSystemService(Context.NOTIFICATION_SERVICE);
//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");
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "foreground");
//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, mActivity.getClass());
PendingIntent pIntent = PendingIntent.getActivity(this, 1001, intent, PendingIntent.FLAG_NO_CREATE);
//setContentIntent 将意图设置到通知上
builder.setContentIntent(pIntent);
//通知默认的声音 震动 呼吸灯
builder.setDefaults(NotificationCompat.DEFAULT_ALL);
builder.setWhen( System.currentTimeMillis() );
//构建通知
Notification notification = builder.build();
//将构建好的通知添加到通知管理器中,执行通知
notificationManager.notify(0, notification);
}else{
//为了版本兼容 选择V7包下的NotificationCompat进行构造
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
//setTicker 在5.0以上不显示Ticker属性信息
builder.setTicker( message.getTicker() );
//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, mActivity.getClass());
PendingIntent pIntent = PendingIntent.getActivity(this, 1001, intent, PendingIntent.FLAG_NO_CREATE);
//setContentIntent 将意图设置到通知上
builder.setContentIntent(pIntent);
//通知默认的声音 震动 呼吸灯
builder.setDefaults(NotificationCompat.DEFAULT_ALL);
//构建通知
Notification notification = builder.build();
// notification.priority = NotificationManager.importANCE_MAX;
//将构建好的通知添加到通知管理器中,执行通知
notificationManager.notify(0, notification);
}
}
}
a.mActivity
这里有个问题,我把登录和FCM功能集成了,并且在AndroidManifest填了FCM接收的JAVA类名。FCM其实用的不是同一个AppFirebase,我也不知道为啥,就把主Activity的变量设成static,好让不同的AppFirebase使用。
b.getFCMRegisterTokenTest()这个方法得到的token,用于在Firebase Cloud Message里用于测试消息的。把本机的token填上,然后点测试就可以直接收到信息。
参考文章:
Android计入Google Firebase之消息推送
关于 Android O 通知渠道总结
要继承 FirebaseMessagingService 并且实现相关接口。
-
如果接入正确,那么 onMessageReceived(…) 会收到消息。当App在后台的时候,FCM收到消息会自动处理,显示在通知栏里。
-
如果需要APP在前台也显示消息,那么可以参考代码里 sendNotification(…) ,如果不需要APP在前台时显示通知消息,那么可以将此代码去掉。
参考文章:
Android_Google登录和Facebook登录并使用Firebase身份验证
其实就是先从Google/Facebook登录之后,拿到人家的token再去Firebase登录,用Firebase的token登录自己的服务器。
二.Google Play 支付(结算)建议先看一遍官方文档。
官方文档
官方DEMO
Google Play分为游戏内购商品和订阅商品。这里只说内购商品的流程。
在完全不知道Google Play支付的流程下去弄,导致我白花费了很多时间。
所以我们先从Google Play支付的流程开始看。
参考文章:
1.教你接入Google谷歌支付V3版本,图文讲解(Android、Unity)图来自文章中,这张图很清楚了,Google Play支付的流程图,以及该在什么地方处理
图来自文章。可以完全按照这个流程来接入你的Google Play支付。
其他参考文章:
google计费接入,Billing结算库支付
Google pay接入流程+无需真正付款
Cocos2dx-Lua游戏接入GooglePlay SDK支付
Android集成Google Pay流程以及注意事项
Android Google应用内支付(新的集成方式)
部分问题QA:
Google Pay支付遇到的问题
这些都文章讲的都很清楚了,接入过程不多赘述。自己遇到的坑点。
1.消耗以及确认订单- 不消耗用户就再买此商品就会提示已拥有
- 不确认订单三天后就会退款
这两个步骤请放在服务器确认并发放相应物品的通知后进行,不要在收到Google Play的回传信息时处理,否则用户可能会收不到商品,但又确实花钱了。
2.补单得补单。之前一直都不需要客户端操作的,但是这次的要。。。
billingClient.queryPurchasesAsync(BillingClient.SkuType.?, new PurchasesResponseListener() {
@Override
public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List list) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
Log.d(TAG, "订单:" + list.size() );
Log.d(TAG, "订单数据:n" + list.toString() );
for (Purchase purchaseHistoryRecord : 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.
每次登录,或者固定间隔调用一次就可以了。
三.一些其他问题记录- Facebook KeyHash生成方法
这是facebook登录要的
2.Android studio 多渠道打包(包括不同的包使用不同的资源文件、不同的包写不同的代码,包名等等)
一个工程下,分多个包
3.【已解决】Android Studio编译OsmAnd出现警告:GeoPointParserUtil.java使用或覆盖了已过时的 API。有关详细信息请使用-Xlint:deprecation重新编译



