unity 3D + Google Play In-app Billing (IAB)(转) 热度 3
git clone https://code.google.com/p/marketbilling/
(附註 : 也許現在已經更新了,但文章撰寫的當下,若是從 SDK Manager 勾選 Google Play Billing Library 下載的版本有 Bug,所以才由 git 下載。 ref :
http://stackoverflow.com/questions/14397343/google-play-in-app-billing-version-3-crash-on-item-already-owned-and-missing )
其中 -cp 指示編譯時參考之 library (android-10, Android 2.3.3)。編譯完成後輸入 :
jar cvfM ./iiabs.jar com/
產生的 iiabs.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。
注意要刪除資料夾 com,以免後續執行 jar 時,打包到舊的 com package。
2. 編譯 class IabHelper
a. 此類別由九個檔案組成,方便用來介接 Google IAB,九個 java 檔案應該位於前步驟下載之程式碼的 marketbilling/v3/src/com/example/android/trivialdrivesample/util 底下。
b. 逐一修改九個檔案裡的 "package com.example.android.trivialdrivesample.util" 改為你的 package name,例如 package com.macaronics.iab.util。
c. 建立欲編譯的檔案的 list,在 Terminal 底下的話直接輸入 cat > sources 然後依序輸入九個檔案檔名後按 control+d 儲存離開。

其中 @sources 表示參考檔案 sources 裡的路徑。此外,引入的 library 除了 android-10/android.jar (Android 2.3.3) 外,還包含前一步驟所編譯的檔案 iiabs.jar。完成後接著輸入 :
jar cvfM ./iabhelper.jar com/
產生的 iabhelper.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。
3. 處理 onActivityResult 的結果訊息
a. 為了讓 IabHelper 能在 Activity 結束時處理 Activity 的結果,這裡我們需要自己撰寫繼承 UnityPlayerActivity 的類別來將結果 Relay 給 IabHelper。底下是範例的程式碼 :
package com.macaronics.iab;import com.unity3d.player.UnityPlayerActivity;import android.os.Bundle;import android.util.Log;import android.content.Intent;public class overrideActivity extends UnityPlayerActivity { public interface cbEvent{ public boolean cbEvent(int requestCode, int resultCode, Intent data); } protected cbEvent ie; static protected overrideActivity inst; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); inst =this; // print debug message to logcat Log.d("overrideActivity", "onCreate called!"); } @Override public void onDestroy(){ super.onDestroy(); inst =null; Log.d("overrideActivity", "onDestroy called!"); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data){ Log.d("overrideActivity", "onActivityResult called!"); boolean ret =false; if (ie !=null){ try{ ret =ie.cbEvent(requestCode, resultCode, data); } catch(Exception e){ ret =false; } } if (ret ==false){ super.onActivityResult(requestCode, resultCode, data); } } static public void registerOnActivityResultCBFunc(final cbEvent pcbfunc){ if (inst !=null) inst.ie =pcbfunc; }} |
其中函數 registerOnActivityResultCBFunc 只能記憶單一個 call back function,讀者可以把功能擴充成使用佇列來記憶更多的 call back function。
b. 同樣的編譯這個 class :
javac ./overrideActivity.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar:/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar -d .
其中引入的 library 有 android-10/android.jar (Android 2.3.3),及 Unity 的 classes.jar。然後輸入 :
jar cvfM ./overrideActivity.jar com/
產生的 overrideActivity.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。
4. 撰寫 iabWrapper.java
a. 主要用於介接 IabHelper 及 Unity ,提供基本的下單功能。程式碼分別敘述如下 :
package com.macaronics.iab;import com.unity3d.player.UnityPlayer;import android.app.Activity;import android.util.Log;import android.os.Bundle;import android.os.Looper;import android.content.Intent;import java.util.*;import com.macaronics.iab.util.*;import com.macaronics.iab.overrideActivity; |
值得注意的是這裡引入 UnityPlayer,步驟2編譯的 IabHelper (com.macaronics.iab.util),及步驟3編譯的 overrideActivity (com.macaronics.iab.overrideActivity)。
底下是 class iabWrapper 的私有變數部分 :
public class iabWrapper{ private Activity mActivity; private IabHelper mHelper; private String mEventHandler; ... |
其中 mEventHandler 是回呼 Unity 時,接收訊息的 GameObject 名稱。此 class 有一個 Constructor 函數,三個成員函數及兩個給 IabHelper 回呼的函數,分別敘述如下 :
Constructor 函數在呼叫時給予 GooglePlay API PublicKey (由 GooglePlay 提供),以及回呼 Unity 時,接收訊息的 GameObject 之名稱 :
public iabWrapper(String base64EncodedPublicKey, String strEventHandler){ mActivity =UnityPlayer.currentActivity; mEventHandler =strEventHandler; if (mHelper !=null){ dispose(); } mHelper =new IabHelper(mActivity, base64EncodedPublicKey); mHelper.enableDebugLogging(true); mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result){ if (!result.isSuccess()){ //回呼 Unity GameObject 之函數 "msgReceiver", 並傳送字串訊息 (JSON 格式) UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"code\":\"1\",\"ret\":\"false\",\"desc\":\""+result.toString()+"\"}"); dispose(); return; } //回呼 Unity GameObject 之函數 "msgReceiver", 並傳送字串訊息 (JSON 格式) UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"code\":\"1\",\"ret\":\"true\",\"desc\":\""+result.toString()+"\"}"); //register mHelper //向 overrideActivity 註冊 onActivityResult 回呼函數,並將資料 Relay 給 mHelper overrideActivity.registerOnActivityResultCBFunc( new overrideActivity.cbEvent(){ public boolean cbEvent(int requestCode, int resultCode, Intent data) { if (mHelper.handleActivityResult(requestCode, resultCode, data)){ return true; } else{ return false; } } } ); } });} |
其中的 UnitySendMessage 用來呼叫 Unity GameObject 之函數 "msgReceiver", 並傳送字串訊息 (JSON 格式)。此外值得注意的是此 Constructor 函數還向 overrideActivity 註冊用來接收 onActivityResult 訊息的回呼函數。底下的dispose 函數主要用於釋放 mHelper :
public void dispose(){ if (mHelper !=null) { mHelper.dispose(); } mHelper =null;} |
purchase 函數會啟動購買商品的介面,其中第一項參數是 Product SKU (在 GooglePlay 設定的產品 SKU),第二項是回呼 onActivityResult 函數時用來分辨購買動作的 reqCode,第三項是用來確認此筆講買是否合法的 payloadString (購買成功之後, GooglePlay 回覆的資訊裡會包含此字串。)。
public void purchase(String strSKU, String reqCode, String payloadString){ int intVal =Integer.parseInt(reqCode); if (mHelper !=null) mHelper.launchPurchaseFlow(mActivity, strSKU, intVal, mPurchaseFinishedListener, payloadString);} |
底下是完成購買之後回呼的函數 (onIabPurchaseFinished),這裡把接收的資訊以 JSON 格式轉送給 Unity :
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =new IabHelper.OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { if (result.isFailure()){ UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"code\":\"2\",\"ret\":\"false\",\"desc\":\"\",\"sign\":\"\"}"); return; } boolean ret =false; String result_json =""; String result_sign =""; if (purchase !=null){ ret =true; result_json =purchase.getOriginalJson().replace('\"', '\''); result_sign =purchase.getSignature(); } UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"code\":\"2\",\"ret\":\""+ret+"\",\"desc\":\""+result_json+"\",\"sign\":\""+result_sign+"\"}"); }}; |
產品在購買 (Purchase) 之後若再次購買則會失敗,在 Console 裡可以看到 "Item already owned" 訊息。若產品的類型屬於消耗性的產品,則需要執行 consume 指令才可再次購買 :
public void consume(String itemType, String jsonPurchaseInfo, String signature){ String transedJSON =jsonPurchaseInfo.replace('\'', '\"'); if (mHelper ==null) return; Purchase pp =null; try{ pp =new Purchase(itemType, transedJSON, signature); } catch(Exception e){ pp=null; } if (pp !=null){ final Purchase currpp =pp; mActivity.runOnUiThread(new Runnable(){ public void run(){ mHelper.consumeAsync(currpp, mConsumeFinishedListener); } }); }} |
其中參數 itemType 在此範例為 "inapp",jsonPurchaseInfo 及 signature 是在 purchase 完成後, IabHelper 回呼 onIabPurchaseFinished 所給的資料。consume 完成之後回呼的函數如下 :
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =new IabHelper.OnConsumeFinishedListener() { public void onConsumeFinished(Purchase purchase, IabResult result) { if (result.isSuccess()){ Log.d("iabWrapper", "Consumption successful. Provisioning"); UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"code\":\"3\",\"ret\":\"true\",\"desc\":\""+purchase.getOriginalJson().replace('\"', '\'')+"\",\"sign\":\""+purchase.getSignature()+"\"}"); } else{ UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"code\":\"3\",\"ret\":\"false\",\"desc\":\"\",\"sign\":\"\"}"); } }}; |
b. 編譯 iabWrapper.java
javac ./iabWrapper.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar:/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar:/Users/macaronics/UnityIabProj/Assets/Plugins/Android/iabhelper.jar:/Users/macaronics/UnityIabProj/Assets/Plugins/Android/overrideActivity.jar -d .
其中引入的 library 有 android-10/android.jar (Android 2.3.3), Unity 的 classes.jar,先前編譯的 iabhelper.jar 及 overrideActivity.jar。然後輸入 :
jar cvfM ./iabWrapper.jar com/
產生的 iabWrapper.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下( 此範例將檔案置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android/) 。
5. 設定 Permission ( AndroidManifest.xml )
若要執行 GooglePlay Billing 功能則要設定 AndroidManifest.xml,將底下的 AndroidManifest.xml 置於 /Users/macaronics/UnityIabProj/Assets/Plugins/Android 資料夾。
<?xml version="1.0" encoding="utf-8"?><manifest android:versionCode="1" android:versionName="1.0" android:installLocation="preferExternal" package="com.macaronics.iab" <supports-screens android:anyDensity="true" android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" /> <application android:label="@string/app_name" android:icon="@drawable/app_icon" android:debuggable="false"> <activity android:label="@string/app_name" android:name="com.macaronics.iab.overrideActivity" android:screenOrientation="portrait" android:configChanges="locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:label="@string/app_name" android:name="com.unity3d.player.UnityPlayerActivity" android:screenOrientation="portrait" android:configChanges="locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" /> <activity android:label="@string/app_name" android:name="com.unity3d.player.UnityPlayerNativeActivity" android:screenOrientation="portrait" android:configChanges="locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" > <meta-data android:name="android.app.lib_name" android:value="unity" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" /> </activity> <activity android:label="@string/app_name" android:name="com.unity3d.player.VideoPlayer" android:screenOrientation="behind" android:configChanges="locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" /> </application> <uses-feature android:glEsVersion="0x20000" /> <uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /></manifest> |
注意其中的 package="com.macaronics.iab" 要改成你自己的 package 名稱,與 Unity 裡的 Bundle Identifier 相同。此外,這裡還設定了主要啟動的 Activity 為 overrideActivity,以及 uses-permission。
到目前為止 /Users/macaronics/UnityIabProj/Assets/Plugins/Android 資料夾底下應該會有這些檔案 (iabhelper.jar, iabWrapper.jar, iiabs.jar, overrideActivity.jar, AndroidManifest.xml ) :

6. Unity 部分的 iabWrapper.cs
利用 AndroidJNI 介接 Java 程式 (注意為了接收 java 回傳的字串訊息,此 script (component) 必須加入到名為 iabWrapper 的 GameObject 裡)。 :
using UnityEngine;using System.Collections;using System.Collections.Generic;public class iabWrapper : MonoBehaviour { public delegate void cbFunc(object[] retarr); cbFunc iabSetupCB =null; cbFunc iabPurchaseCB =null; cbFunc iabConsumeCB =null; AndroidJavaObject mIABHelperObj =null; static iabWrapper g_inst =null; void Start(){ g_inst =this; } static public void init(string base64EncodedPublicKey, cbFunc tmpIabSetupCBFunc){ if (g_inst ==null) return; g_inst.iabSetupCB =tmpIabSetupCBFunc; dispose(); g_inst.mIABHelperObj =new AndroidJavaObject("com.macaronics.iab.iabWrapper", new object[2]{base64EncodedPublicKey, "iabWrapper"}); } static public void dispose(){ if (g_inst ==null) return; if (g_inst.mIABHelperObj !=null){ g_inst.mIABHelperObj.Call("dispose"); g_inst.mIABHelperObj.Dispose(); g_inst.mIABHelperObj =null; } } ... |
其中 init 負責呼叫 iabWrapper.java 之建構函數並建立該物件,dispose 則是負責刪除釋放 iabWrapper.java 物件。底下是主要的兩個功能,purchase 和 consume :
static public void purchase(string strSKU, int reqCode, string payload, cbFunc tmpIabPurchaseCBFunc){ if (g_inst ==null) return; g_inst.iabPurchaseCB =tmpIabPurchaseCBFunc; if (g_inst.mIABHelperObj !=null){ g_inst.mIABHelperObj.Call("purchase", new object[3]{strSKU, reqCode.ToString(), payload}); }}static public void consume_inapp(string strPurchaseJsonInfo, string strSignature, cbFunc tmpIabConsumeCBFunc){ if (g_inst ==null) return; g_inst.iabConsumeCB =tmpIabConsumeCBFunc; if (g_inst.mIABHelperObj !=null){ g_inst.mIABHelperObj.Call("consume", new object[3]{"inapp", strPurchaseJsonInfo, strSignature}); }} |
其中先將 callback 函數記錄起來,然後呼叫其對應的 java function。最後一部分是 msgReceiver,負責接收 java 回呼的結果並將資訊回呼對應的 callback function (先前呼叫 purchase 或是 consume 所給予的 callback function)。
void msgReceiver(string msg){ if (g_inst ==null) return; //parse json Dictionary<string, object> cache =(Dictionary<string, object>)MiniJSON.Json.Deserialize(msg); //dispatch msg if (cache.ContainsKey("code")==true){ int val =0; int.TryParse((string)cache["code"], out val); switch(val){ case 0:{ //unknown Debug.Log("Unity-iabWrappe :cannot parse cache[code]"); } break; case 1:{ //OnIabSetupFinishedListener if (cache.ContainsKey("ret")==true){ string retval =(string)cache["ret"]; if (retval =="true"){ //可使用 if (iabSetupCB !=null){ iabSetupCB( new object[1]{true} ); } } else if (retval =="false"){ //不可使用 if (iabSetupCB !=null) { iabSetupCB( new object[1]{false} ); } }else{ Debug.Log("Unity-iabWrapper :cannot parse cache[ret], code=1"); } } } break; case 2:{ //onIabPurchaseFinished if (cache.ContainsKey("ret")==true){ string retval =(string)cache["ret"]; if (retval =="true"){ //可使用 if (iabPurchaseCB !=null){ iabPurchaseCB( new object[3]{true, (string)cache["desc"], (string)cache["sign"]} ); } } else if (retval =="false"){ //不可使用 if (iabPurchaseCB !=null) { iabPurchaseCB( new object[3]{false, "", ""} ); } } else{ Debug.Log("Unity-iabWrapper :cannot parse cache[ret], code=2"); } } } break; case 3:{ //OnConsumeFinishedListener if (cache.ContainsKey("ret")==true){ string retval =(string)cache["ret"]; if (retval =="true"){ //可使用 if (iabConsumeCB !=null) { iabConsumeCB( new object[3]{true, (string)cache["desc"], (string)cache["sign"]} ); } }else if (retval =="false"){ //不可使用 if (iabConsumeCB !=null) { iabConsumeCB( new object[3]{false, "", ""} ); } } else{ Debug.Log("Unity-iabWrapper :cannot parse cache[ret], code=3"); } } } break; } }} |
程式碼先將 JSON 字串轉為 object 物件並依照物件種類做對應的處理,其中 purchase 的 callback (case 2),回傳的參數依序為 : 1. 是否成功,2. purchase 結果字串以及 3. purchase 結果之 signature (可作為驗証之用,參考這篇)。
7. 實作 IAB APP
建立一個 script 名為 main,並將此 script 加入至 Camera。這裡實作一個 purchase 功能,並在 purchase 完成之後立刻執行 consume。
using UnityEngine;using UnityEngine;using System.Collections; public class main : MonoBehaviour { // Use this for initialization void Start () { iabWrapper.init( "PUBLIC_KEY", delegate(object[] ret){ if (true ==(bool)ret[0]){ Debug.Log("iab successfully initialized"); } else{ Debug.Log("failed to initialize iab"); } }); } void OnGUI(){ if (GUI.Button(new Rect(0, 0, 100, 100), "purchase")){ iabWrapper.purchase("PRODUCT_SKU", 10001, "PRODUCT_SKU_AND_USER_ID_AND_DATE", delegate(object[] ret){ if (false ==(bool)ret[0]){ Debug.Log("purchase cancelled"); } else{ string purchaseinfo =(string)ret[1]; string signature =(string)ret[2]; iabWrapper.consume_inapp(purchaseinfo, signature, delegate(object[] ret2){ if (false ==(bool)ret2[0]) { Debug.Log("failed to consume product"); } }); } }); } } void OnApplicationQuit(){ iabWrapper.dispose(); }} |
其中的 PUBLIC_KEY,更換成你的 Public Key,代入 purchase 函數的參數更改成你的參數。範例原始碼可以從這裡取得 : http://github.com/phardera/unity3d_googleplay_iab.git
unity playforward
unity 3D + Google Play In-app Billing (IAB)(转) 热度 3的更多相关文章
- (转)stage 3d or unity 3d
这个是这样子的.stage3d如果不使用flascc的话,性能会卡在as上面.你没卡是因为你用的效果还不够高级.往深了走的高端应用就是卡as性能上.这不是你代码能优化了的.我们后来都改用flascc写 ...
- Circular progress bar in Unity 3D
Circular progress bar in Unity 3D - UnityScripthttp://stackoverflow.com/questions/22662706/circular- ...
- 微信成为首批支持iPhone 6s /Plus 上 3D Touch 功能的 App
2015苹果新品发布会上微信成为首批支持iPhone 6s 和 iPhone 6s Plus 上 3D Touch 功能的 App.通过 3D Touch,微信用户将可以通过更精减的操作完成基本任务, ...
- 编译包含Google Play服务App的SDK版本问题
编译包含Google Play服务App的SDK版本问题 错误信息:No Resouce identifier found for attribute 'touchscreen BlocksFoc ...
- Unity 3D 游戏上线之后的流水总结
原地址:http://tieba.baidu.com/p/2817057297?pn=1 首先.unity 灯光烘焙 :Unity 3D FBX模型导入.选项Model 不导入资源球.Rig 不导入骨 ...
- C#程序员整理的Unity 3D笔记(十五):Unity 3D UI控件至尊–NGUI
目前,UGUI问世不过半年(其随着Unity 4.6发布问世),而市面上商用的产品,UI控件的至尊为NGUI:影响力和广度(可搜索公司招聘Unity 3D,常常能看到对NGUI关键词). NGUI虽然 ...
- C#程序员整理的Unity 3D笔记(十三):Unity 3D基于组件的思想
如果你接触过<设计模式>.软件架构的编程思想,就会知道优秀的设计准则:“组合优于继承的”. 这句话很简短,但开始学习OOP的时候,真切的是—-不太好理解(以我个人当初学习为例). OOP的 ...
- 【图说】Eclipse与Unity 3D协同工作
原地址:http://blog.csdn.net/h570768995/article/details/9355313 Eclipse开发过程中总会碰到很多的难题,如何利用好工具帮助我们更快捷的开发也 ...
- C#程序员整理的Unity 3D笔记(十):Unity3D的位移、旋转的3D数学模型
遇到一个想做的功能,但是实现不了,核心原因是因为对U3D的3D数学概念没有灵活吃透.故再次系统学习之—第三次学习3D数学. 本次,希望实现的功能很简单: 如在小地图中,希望可以动态画出Player当前 ...
随机推荐
- HTML表格的基本操作
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xht ...
- springboot1.5.4 idea 自动保存编译更新
maven dependencies增加 <dependency> <groupId>org.springframework.boot</groupId> < ...
- c++ 容器学习 理论
[转载]http://blog.csdn.net/acosoft/article/details/4395468 在面向对象的语言中,大多引入了容器的概念.那么 什么 是 容器?实质上就是一组相同类型 ...
- [实战]MVC5+EF6+MySql企业网盘实战(24)——视频列表
写在前面 上篇文章实现了文档列表,所以实现视频列表就依葫芦画瓢就行了. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) ...
- js的浮点(小数)数+-*/
//除法 function accDiv(arg1,arg2){ var t1=0,t2=0,r1,r2; try{t1=arg1.toString().split(".")[1] ...
- LoadRunner学习笔记log函数
lr_log_message 只是记会写到本地vuser的log里面. lr_message和lr_output_message基本相同,它们会同时写到vuser的log和发送到controller里 ...
- C++ 实现的一个打印日历程序
C++ 实现的一个打印日历程序 说明:总共有三个文件 1.month.h 为定义函数的头文件 2.month.cpp 为函数的实现代码 3.mainprog.cpp 为主函数的实现代码 month.h ...
- mvc bundle的介绍及使用 转载自 http://www.ityouzi.com/archives/mvc-bundleconfig.html
Asp.Net MVC4 BundleConfig文件合并.压缩,网站优化加速 浏览器在向服务器发送请求的时候,请求的文件链接数量是有限制的,如果页面文件少就没有什么问题了,如果文件太多就会导致链接失 ...
- 【转】eval()函数用法
eval 功能:将字符串str当成有效的表达式来求值并返回计算结果. 语法: eval(source[, globals[, locals]]) -> value 参数: source:一个Py ...
- GIT修改邮箱
git报错-->! [remote rejected] master -> master (push declined due to email privacy restrictions) ...