文章目录

  • google pay
    • google play Billing 支持的一次性产品
    • 商品购买流程
    • google pay 实现流程
      • 1. 添加依赖
      • 2. 连接到Google Play
      • 3. 查询商品
      • 4. 唤起支付页面
      • 5. 回调监听
      • 6.查询已购商品
      • 7. 消费已购商品
  • 使用AIDL实现Google pay 支付
    • 什么是AIDL
    • 服务端中的AIDL(如:google play应用)
    • 第三方应用中的AIDL
    • 使用AIDL实现google支付

google pay

  • 更新应用的依赖项。
  • 连接到Google Play。
  • 查询应用内商品详细信息。
  • 允许购买应用内商品。
  • 查询购买的商品。
  • 添加一次性产品特定或订阅特定代码(在单独的页面上介绍)。

官方文档

google play Billing 支持的一次性产品

使用google在购买某个商品后,如果没有对该商品进行消费,是不可以再次购买的,需要对已购商品进行消费后才能再次购买

  • 非消耗性一次性产品

提供永久性效果的产品,不能重新购买,不应表明为已消费

  • 可消耗的一次性产品

提供临时利益,可以回购的产品

商品购买流程

Version 3 API 中的典型购买流程如下所示:

1.您的应用向 Google Play 发送 isBillingSupported 请求,以确定您当前使用的 In-app Billing API 目标版本是否受支持。

2.当您的应用启动或用户登录时,最好向 Google Play 进行查询,确定该用户拥有哪些商品。 要查询用户的应用内购买,请发送 getPurchases 请求。 如果请求成功,Google Play 会返回一个 Bundle,其中包含所购商品的商品 ID 列表、各项购买详情的列表以及购买签名的列表。

3.通常情况下,您需要将可供购买的商品通知用户。 要查询您在 Google Play 中定义的应用内商品的详细信息,应用可以发送 getSkuDetails 请求。 您必须在查询请求中指定商品 ID 列表。如果该请求成功,Google Play 会返回一个包含产品详情(包括商品的价格、标题、说明和购买类型)的 Bundle。

4.如果用户还未拥有某种应用内商品,您可以提示购买。 为了发起购买请求,您的应用会发送 getBuyIntent 请求,指定要购买商品的商品 ID 以及其他参数。 当您在 Developer Console 中创建新的应用内商品时,应记录其商品 ID。

a.Google Play 返回的 Bundle 中包含 PendingIntent,您的应用可用它来启动购买结账 UI。b.您的应用通过调用 startIntentSenderForResult 方法启动待定 Intent。c.结账流程结束后(即用户成功购买商品或取消购买),Google Play 会向您的 onActivityResult 方法发送响应 Intent。 onActivityResult 的结果代码中有一个代码将用于表明购买是成功还是已取消。 响应 Intent 中包含所购商品的相关信息,包括 Google Play 为了对此次购买交易进行唯一标识而生成的 purchaseToken 字符串。 Intent 中还包含使用您的开发者私钥签署的购买签名。

google pay 实现流程

1. 添加依赖

dependencies {...implementation 'com.android.billingclient:billing:1.2'
}

2. 连接到Google Play

创建BillingClient实例并实现监听,开始与google play的连接

// create new Person
private BillingClient mBillingClient;
...
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {@Overridepublic void onBillingSetupFinished(@BillingResponse int billingResponseCode) {if (billingResponseCode == BillingResponse.OK) {// The billing client is ready. You can query purchases here.}}@Overridepublic void onBillingServiceDisconnected() {// Try to restart the connection on the next request to// Google Play by calling the startConnection() method.}
});

3. 查询商品

同过商品的id来查询对应的商品信息

List skuList = new ArrayList<> ();
skuList.add("xxxxx"); //商品id
skuList.add("xxxxx"); //商品id
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
mBillingClient.querySkuDetailsAsync(params.build(),new SkuDetailsResponseListener() {@Overridepublic void onSkuDetailsResponse(int responseCode, List skuDetailsList) {// Process the result.}});

4. 唤起支付页面

在发起支付页面之前,需要通过isFeatureSupported方法检查设备是否支持该商品的销售,可以通过BillingClient.FeatureType查询商品类型

BillingFlowParams flowParams = BillingFlowParams.newBuilder().setSku(skuId).setType(SkuType.INAPP) // SkuType.SUB for subscription.build();
int responseCode = mBillingClient.launchBillingFlow(flowParams);

5. 回调监听

在建立连接那一步中,通过setListener(this),使当前类实现了回调监听的接口

@Override
void onPurchasesUpdated(@BillingResponse int responseCode, List purchases) {if (responseCode == BillingResponse.OK&& purchases != null) {for (Purchase purchase : purchases) {handlePurchase(purchase);}} else if (responseCode == BillingResponse.USER_CANCELED) {// Handle an error caused by a user cancelling the purchase flow.} else {// Handle any other error codes.}
}

6.查询已购商品

当用户购买商品完成后,可以获取到已购的商品列表

mBillingClient.queryPurchaseHistoryAsync(SkuType.INAPP,new PurchaseHistoryResponseListener() {@Overridepublic void onPurchaseHistoryResponse(@BillingResponse int responseCode,List purchasesList) {if (responseCode == BillingResponse.OK&& purchasesList != null) {for (Purchase purchase : purchasesList) {// Process the result.}}}
});

7. 消费已购商品

在购买完成之后,可以获取到已购商品的purchaseToken,consumeAsync消费已购商品

ConsumeResponseListener listener = new ConsumeResponseListener() {@Overridepublic void onConsumeResponse(@BillingResponse int responseCode, String outToken) {if (responseCode == BillingResponse.OK) {// Handle the success of the consume operation.// For example, increase the number of coins inside the user's basket.}
};
mBillingClient.consumeAsync(purchaseToken, listener);

使用AIDL实现Google pay 支付

上面知道,我们可以通过依赖google pay库实现,通过SDK实现支付。除此之外还有另外一种实现方式,也就是通过定义AIDL接口,实现应用间的跨进程通信,从而实现支付

什么是AIDL

AIDL,Android接口定义语言,是为了两个不同的应用间通过Serivce进行通信。也就是说google play对外提供远程Service,其他应用可以并发地访问该Service

服务端中的AIDL(如:google play应用)

一般来说,AIDL文件放在main/com.xxx.xx/下面,然后然后定义对外提供的方法,但注意,它有自己的语法

  • 每个aidl文件只能定义一个接口(单一接口);
  • 默认Java中的基本数据类型都支持,如:int, long, char, boolean;
  • 默认支持String和CharSequence;
  • 默认支持List(可以选择加泛型,需要引入List所在的包),但是List中存储的数据也只能是Java基本数据类型,而且另外一边(客户端或服务端)接受的是ArrayList类型;
  • 默认支持Map(可以选择加泛型,但泛型只能是基本数据类型或String和CharSequence,需要引入Map所在的包),但同样,Map中存储的数据也只能是Java基本数据类型,而且另外一边(客户端或服务端)接受的是HashMap类型;
  • 所有aidl文件中的注释都会出现在自动生成的IBinder接口java文件中(除了导入包语句之前的注释);
  • aidl接口只支持方法,不支持变量。

例如 IServerSerivce.aidl

interface IServerSerivce {String getName(int id);
}

重新构建一下project会在builde-resource-gen…下面生成一个IServerSerivce.java文件
这个文件里面有一个内部静态抽象类Stub,接下来我们需要实现它

例如ServerSerivce.java

import com.xxx.xx.IServerSerivce;
public class ServerSerivce extends Service {/*** 返回ServerSerivce代理对象IBinder给客户端使用* @param intent* @return*/@Overridepublic IBinder onBind(Intent intent) {return mBinder;}private final IServerSerivce.Stub mBinder = new IServerSerivce.Stub() {@Overridepublic String getName(int id) thorws RemoteException {return new String("同学"+id);}}
}

在AndroidManifest.xml中注册该Serivice。其中还要action属性,用于隐式启动该服务

<serviceandroid:name=".service.ServerService"android:exported="true"><intent-filter><action android:name="com.xxx.xxx"/><category android:name="android.intent.category.DEFAULT"/></intent-filter>
</service>

第三方应用中的AIDL

在第三方应用中同样需要AIDL文件,而且和服务端的AIDL一模一样。

第一步 建立服务连接

private ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 这里的IBinder对象service是代理对象,所以必须调用下面的方法转换成AIDL接口对象mServerService = IRemoteService.Stub.asInterface(service);System.out.println("bind success! " + mServerService.toString());}@Overridepublic void onServiceDisconnected(ComponentName name) {mServerService = null;System.out.println(mServerService.toString() +" disconnected! ");}
};

第二步 通过隐式意图绑定远程服务

// 连接绑定远程服务
Intent intent = new Intent();
// action值为远程服务的action,即上面我们在服务端应用清单文件的action
intent.setAction("com.xx.xxx"); //指定的action
intent.setPackage("com.xxx.xx"); //服务端应用的包名
isConnSuccess = bindService(intent, conn, Context.BIND_AUTO_CREATE); //

第三步 在onDestroy解除服务

protected void onDestroy() {super.onDestroy();unbindService(conn);
}

接下来就可以通过mServerService来访问数据。

使用AIDL实现google支付

首先在下载google给出的示例代码
把项目里面的AIDL文件拷贝到自己的项目中,文件路径也要一致
把util的类都拷贝到自己的项目中。service的连接,访问都被封装到了IabHelper类中

第一步 查询记录回调

  // Listener that's called when we finish querying the items and subscriptions we ownIabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {public void onQueryInventoryFinished(IabResult result, Inventory inventory) {Log.d(TAG, "Query inventory finished.");// Have we been disposed of in the meantime? If so, quit.if (mHelper == null) return;// Is it a failure?if (result.isFailure()) {complain("Failed to query inventory: " + result);return;}Log.d(TAG, "Query inventory was successful.");/** Check for items we own. Notice that for each purchase, we check* the developer payload to see if it's correct! See* verifyDeveloperPayload().*/// Do we have the premium upgrade?Purchase premiumPurchase = inventory.getPurchase(purchaseId);mPurchase = premiumPurchase;mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));alert("User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));updateUi();setWaitScreen(false);Log.d(TAG, "Initial inventory query finished; enabling main UI.");}};

在调用查询方法后回调,需要根据自己的情况对查询结果进行处理

第二步 购买成功回调

    // Callback for when a purchase is finishedIabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {public void onIabPurchaseFinished(IabResult result, Purchase purchase) {Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);// if we were disposed of in the meantime, quit.if (mHelper == null) return;if (result.isFailure()) {complain("Error purchasing: " + result);setWaitScreen(false);return;}if (!verifyDeveloperPayload(purchase)) {complain("Error purchasing. Authenticity verification failed.");setWaitScreen(false);return;}Log.d(TAG, "Purchase successful.");if (purchase.getSku().equals(purchaseId)) {// bought the premium upgrade!Log.d(TAG, "Purchase is premium upgrade. Congratulating user.");alert("Thank you for upgrading to premium!");mIsPremium = true;updateUi();setWaitScreen(false);}}};

第三步 消费商品回调

// Called when consumption is completeIabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {public void onConsumeFinished(Purchase purchase, IabResult result) {Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);// if we were disposed of in the meantime, quit.if (mHelper == null) return;// We know this is the "gas" sku because it's the only one we consume,// so we don't check which sku was consumed. If you have more than one// sku, you probably should check...if (result.isSuccess()) {// successfully consumed, so we apply the effects of the item in our// game world's logic, which in our case means filling the gas tank a bitLog.d(TAG, "Consumption successful. Provisioning.");
//                mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1;saveData();alert("Consumption successful");}else {complain("Error while consuming: " + result);}updateUi();setWaitScreen(false);Log.d(TAG, "End consumption flow.");}};

第四步 初始化和解除

public void init() {mHelper = new IabHelper(mContext, base64EncodedPublicKey);// enable debug logging (for a production application, you should set this to false).mHelper.enableDebugLogging(true);// Start setup. This is asynchronous and the specified listener// will be called once setup completes.Log.d(TAG, "Starting setup.");mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {public void onIabSetupFinished(IabResult result) {Log.d(TAG, "Setup finished.");if (!result.isSuccess()) {// Oh noes, there was a problem.complain("Problem setting up in-app billing: " + result);return;}// Have we been disposed of in the meantime? If so, quit.if (mHelper == null) return;// Important: Dynamically register for broadcast messages about updated purchases.// We register the receiver here instead of as a <receiver> in the Manifest// because we always call getPurchases() at startup, so therefore we can ignore// any broadcasts sent while the app isn't running.// Note: registering this listener in an Activity is a bad idea, but is done here// because this is a SAMPLE. Regardless, the receiver must be registered after// IabHelper is setup, but before first call to getPurchases().
//                mBroadcastReceiver = new IabBroadcastReceiver(MainActivity.this);IntentFilter broadcastFilter = new IntentFilter(IabBroadcastReceiver.ACTION);
//                registerReceiver(mBroadcastReceiver, broadcastFilter);// IAB is fully set up. Now, let's get an inventory of stuff we own.Log.d(TAG, "Setup successful. Querying inventory.");try {mHelper.queryInventoryAsync(mGotInventoryListener);} catch (IabHelper.IabAsyncInProgressException e) {complain("Error querying inventory. Another async operation in progress.");}}});
}@Override
public void onDestroy() {// very important:Log.d(TAG, "Destroying helper.");if (mHelper != null) {mHelper.disposeWhenFinished();mHelper = null;}}public void onActivityResult(int requestCode, int resultCode, Intent data) {Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);if (mHelper == null) return;if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {}else {Log.d(TAG, "onActivityResult handled by IABUtil.");}}

第四步 调用支付、查询、消费等方法

方法名 类型 所需参数 含义
launchPurchaseFlow void Activity act, String sku, int requestCode,OnIabPurchaseFinishedListener,String extraData 唤起支付页面
queryInventoryAsync void QueryInventoryFinishedListener listener 查询订单记录
consumeAsync void Purchase purchase, OnConsumeFinishedListener listener 消费指定商品

关于IabHelper
在IabHelper中,它已经帮我们把与远程服务通信的连接,等操作给给封装起来,可以让我们使用者只需要关注实现方法本身,而不用处理服务的连接等,还有一些逻辑的处理等,接下来看看它封装了哪些方法吧

startSetup

public void startSetup(final OnIabSetupFinishedListener listener) {// If already set up, can't do it again.checkNotDisposed();if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");// Connection to IAB servicelogDebug("Starting in-app billing setup.");mServiceConn = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {logDebug("Billing service disconnected.");mService = null;}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {if (mDisposed) return;logDebug("Billing service connected.");mService = IInAppBillingService.Stub.asInterface(service);String packageName = mContext.getPackageName();try {logDebug("Checking for in-app billing 3 support.");// check for in-app billing v3 supportint response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);if (response != BILLING_RESPONSE_RESULT_OK) {if (listener != null) listener.onIabSetupFinished(new IabResult(response,"Error checking for billing v3 support."));// if in-app purchases aren't supported, neither are subscriptionsmSubscriptionsSupported = false;mSubscriptionUpdateSupported = false;return;} else {logDebug("In-app billing version 3 supported for " + packageName);}// Check for v5 subscriptions support. This is needed for// getBuyIntentToReplaceSku which allows for subscription updateresponse = mService.isBillingSupported(5, packageName, ITEM_TYPE_SUBS);if (response == BILLING_RESPONSE_RESULT_OK) {logDebug("Subscription re-signup AVAILABLE.");mSubscriptionUpdateSupported = true;} else {logDebug("Subscription re-signup not available.");mSubscriptionUpdateSupported = false;}if (mSubscriptionUpdateSupported) {mSubscriptionsSupported = true;} else {// check for v3 subscriptions supportresponse = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);if (response == BILLING_RESPONSE_RESULT_OK) {logDebug("Subscriptions AVAILABLE.");mSubscriptionsSupported = true;} else {logDebug("Subscriptions NOT AVAILABLE. Response: " + response);mSubscriptionsSupported = false;mSubscriptionUpdateSupported = false;}}mSetupDone = true;}catch (RemoteException e) {if (listener != null) {listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,"RemoteException while setting up in-app billing."));}e.printStackTrace();return;}if (listener != null) {listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));}}};Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");serviceIntent.setPackage("com.android.vending");List<ResolveInfo> intentServices = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);if (intentServices != null && !intentServices.isEmpty()) {// service available to handle that IntentmContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);}else {// no service available to handle that Intentif (listener != null) {listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,"Billing service unavailable on device."));}}}

在startSetup中,它判断了应用是否支持google pay,然后实现了Service的连接

launchPurchaseFlow

public void launchPurchaseFlow(Activity act, String sku, String itemType, List<String> oldSkus,int requestCode, OnIabPurchaseFinishedListener listener, String extraData)throws IabAsyncInProgressException {checkNotDisposed();checkSetupDone("launchPurchaseFlow");flagStartAsync("launchPurchaseFlow");IabResult result;if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,"Subscriptions are not available.");flagEndAsync();if (listener != null) listener.onIabPurchaseFinished(r, null);return;}try {logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);Bundle buyIntentBundle;if (oldSkus == null || oldSkus.isEmpty()) {// Purchasing a new item or subscription re-signupbuyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType,extraData);} else {// Subscription upgrade/downgradeif (!mSubscriptionUpdateSupported) {IabResult r = new IabResult(IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE,"Subscription updates are not available.");flagEndAsync();if (listener != null) listener.onIabPurchaseFinished(r, null);return;}buyIntentBundle = mService.getBuyIntentToReplaceSkus(5, mContext.getPackageName(),oldSkus, sku, itemType, extraData);}int response = getResponseCodeFromBundle(buyIntentBundle);if (response != BILLING_RESPONSE_RESULT_OK) {logError("Unable to buy item, Error response: " + getResponseDesc(response));flagEndAsync();result = new IabResult(response, "Unable to buy item");if (listener != null) listener.onIabPurchaseFinished(result, null);return;}PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);mRequestCode = requestCode;mPurchaseListener = listener;mPurchasingItemType = itemType;act.startIntentSenderForResult(pendingIntent.getIntentSender(),requestCode, new Intent(),Integer.valueOf(0), Integer.valueOf(0),Integer.valueOf(0));}catch (SendIntentException e) {logError("SendIntentException while launching purchase flow for sku " + sku);e.printStackTrace();flagEndAsync();result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");if (listener != null) listener.onIabPurchaseFinished(result, null);}catch (RemoteException e) {logError("RemoteException while launching purchase flow for sku " + sku);e.printStackTrace();flagEndAsync();result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");if (listener != null) listener.onIabPurchaseFinished(result, null);}}

由上,在我们的程序调用了launchPurchaseFlow方法后,它会先对参数进行判断,判断商品的类型,然后调用service中的购买商品的方法,接收回调,对回调进行分析处理

接下来的方法就不再介绍了,基本是大同小异。相信总结完这么多也基本可以理解使用了。

Android 接入google pay相关推荐

  1. cocos creator Android 接入Google支付sdk

    准备工作 1.手机上安装Google服务 2.一个绑定信用卡的Google账号: 3.Google play客户端: 4..Google Play开发者后台创建应用 5.科学上网的工具 -等等 这些就 ...

  2. android 添加ga_android开发步步为营之70:android接入Google Analytics总结

    求人不如求己,今天项目里要接入Google Analytics,这个是做应用统计分析用的,可以查看当前手机活跃用户,事件点击等等数据,先看看效果: 之前eclipse里面接入已经成功,昨天项目组决定项 ...

  3. cocos creator Android 接入Google登陆sdk

    准备工作 这里就不详细介绍,主要说一下客户端sdk接入 1.手机上安装Google服务 2.一个绑定信用卡的Google账号: 3.Google play客户端: 4..Google Play开发者后 ...

  4. Google Pay接入

    1.由于业务需求,准备接入Google Pay,一开始本人接到这个需求的时候,就开始到Google Pay官网以及Google.百度上搜索如何接入Google Pay,也是有发现一些文章.在我处理了一 ...

  5. 完整流程Google Pay 接入

    前一段时间由于项目需求,产品需要接入Google Pay SDK,然后....大家都懂的...各种搜索,出现的文章要么就是很久以前的,要么就是各种问题,经过一番"泥里打滚"后,还是 ...

  6. 谷歌pay 手续费_您可以使用Google Pay进行的所有操作

    谷歌pay 手续费 Google Pay has really grown over the last several months. It stepped away from the Android ...

  7. apple pay集成_如何将Google Pay集成到您现有的Android应用中

    apple pay集成 You'll learn how to integrate one of the most used payment gateways, GPay, into your exi ...

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

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

  9. Android 应用Google Play广告推广渠道来源区分功能的接入

    应用上架google play之后随之而来的问题是推广,多广告渠道投放后,如何区分用户是通过哪个渠道广告点击进来安装我们的应用.Google官方提供的有两种解决方案,我也是刚摸索清楚,于是想着记录下来 ...

最新文章

  1. 点分治问题 ----------- HDU4812 D Tree [点分治 + 乘法逆元]
  2. Linux-进程、进程组、作业、会话、控制终端详解
  3. OLTP(on-line transaction processing)与OLAP(On-Line Analytical Processing)
  4. python爬图片 beautifulsoup_【Python爬虫】基于BeautifulSoup的微博图片爬虫
  5. .NET项目升级手记:可为空引用
  6. NGINX1.19安装手册
  7. Servlet入门1
  8. MyEclipse提示Errors occurred during the build
  9. 如何清除 MacBook 上的浏览​​器缓存
  10. 防止电子眼拍到车牌的秘籍
  11. Flutter之GridView简析
  12. 大学英语综合教程四 Unit 6 课文内容英译中 中英翻译
  13. RapidMiner Studio入门
  14. matlab绘制奈奎图,matlab画奈奎斯特图
  15. 单片机音频谱曲软件_单片机谱曲软件怎么弄 51单片机蜂鸣器电子琴程序
  16. unity导入导出excel的功能
  17. 163邮箱登录界面在哪里?如何登陆163邮箱呢?邮箱163格式怎么写
  18. *LOJ#2134. 「NOI2015」小园丁与老司机
  19. PyQt5 第一章 PyQt5简介和安装
  20. Python#Typora-Python笔记

热门文章

  1. 愤怒的小鸟游戏开发教程(持续更新)
  2. ARM64开发板配置Java环境 OrangePi
  3. 2.7——golang数据类型【字符串类型】
  4. opencv 数字识别详细教程
  5. CSS3实现边框线条动画特效
  6. 手机app数据爬取难度等级评估
  7. 智能运维 | 我们不一样!告诉你百度云如何做智能流量异常检测
  8. 微软数据中心将到南非!AWS也将要跟进
  9. vscode文件图标消失
  10. Oracle 单实例 Relink Binary Options 说明