目录
1、示例代码

下面将通过demo中的代码具体说明如何使用本SDK。
APPID,APPKEY,PayCode设置。
在demo程序中,APPID,APPKEY,PayCode默认赋值为00000000000。请根据在2.3节中操作,将生成的APPID,APPKEY,PayCode分别填入。


        // 计费应用信息(demo测试)
       private static final String APPID = "00000000000";
        private static final String APPKEY = "00000000000";
       // 计费点信息
        private static final String LEASE_PAYCODE = "00000000000"; // 单次
      


OnPurchaseListener接口实现


开发者使用SDK,需要自定义实现一个接口OnPurchaseListener,该接口用来将订购,查询,退订各业务的结果或者状态返回给APP。Demo中具体实现如下:


   public class IAPListener implements OnPurchaseListener {
    private final String TAG = "IAPListener";
   private Demo context;
   private IAPHandler iapHandler;
    //
    public IAPListener(Context context, IAPHandler iapHandler) {
    this.context = (Demo) context;
    this.iapHandler = iapHandler;
   }
   @Override
     public void onAfterApply() {
    }
   @Override
   public void onAfterDownload() {
   // TODO Auto-generated method stub
    }
   @Override
   public void onBeforeApply() {
   // TODO Auto-generated method stub
    }
   @Override
   public void onBeforeDownload() {
    // TODO Auto-generated method stub
    }
   //初始化结束接口。开发者调用了初始化接口后,最后结果将在此函数中被调用
    @Override
   public void onInitFinish(int code) {
    Log.d(TAG, "Init finish, status code = " + code);
   Message 
    message= iapHandler.obtainMessage(IAPHandler.INIT_FINISH);
    String result = "初始化结果:" + Purchase.getReason(code);
    message.obj = result;
   message.sendToTarget();
   }
   //订购结束接口。
   @Override
   public void onBillingFinish(int code, HashMap arg1) {
   Log.d(TAG, "billing finish, status code = " + code);
    String result = "";
   Message message = iapHandler.obtainMessage(IAPHandler.BILL_FINISH);
   if (code == PurchaseCode.ORDER_OK || (code == PurchaseCode.AUTH_OK)) {
    /**
   * BILL_SUCCEED,表示订购成功 AUTH_SUCCEED,表示该商品已经订购。
   */
    result = "订购结果:订购成功。";
    } else {
    /**
   * 表示订购失败。
   */
    result = "订购结果:" + Purchase.getReason(code);
    }
   message.obj = result;
   message.sendToTarget();
   }
   //查询结束接口
    @Override
   public void onQueryFinish(int code, HashMap arg1) {
   Log.d(TAG, "license finish, status code = " + code);
    Message message = iapHandler.obtainMessage(IAPHandler.QUERY_FINISH);
    String result = "查询成功,该商品已购买";
    if (code != PurchaseCode.QUERY_OK) {
    /**
   * 查询不成功
   */
    result = "查询结果:" + Purchase.getReason(code);
    } else {
    String leftDay = (String) arg1.get(OnPurchaseListener.LEFTDAY);
   if (leftDay != null && leftDay.trim().length() != 0) {
    result = result + ",剩余时间 : " + leftDay;
   }
   String orderID =(String)arg1.get(OnPurchaseListener.ORDERID);
   if (orderID != null && orderID.trim().length() != 0) {
    result = result + ",OrderID : " + orderID;
    }
   }
   message.obj = result;
   message.sendToTarget();
   }
   //退订接口:
   public void onUnsubscribeFinish(int code) {
   // TODO Auto-generated method stub
    String result = "退订结果:" + Purchase.getReason(code);
   System.out.println(result);
   context.dismissProgressDialog();
    }
      

1.1、SDK初始化

本SDK初始化很简单,只需要实例化SDK中Purchase类即可,再根据APP的实际情况设置相应的参数。Demo中的代码如下:


      @Override
     public void onCreate(Bundle savedInstanceState) {
     /**
     * IAP组件初始化.包括下面3步。
      */
      /**
     * step1.实例化PurchaseListener。实例化传入的参数与您实现PurchaseListener接口的对象有关。
      * 例如,此Demo代码中使用IAPListener继承PurchaseListener,其构造函数需要Context实例。
      */
      listener = new IAPListener(this, iapHandler);
     /**
     * step2.实例化Purchase对象。在实例化Purhcase对象后,必须为purchase实例setAppInfo 
      *接口。该接口函数需要传入APPID,APPKEY。
      */
      purchase = Purchase.getInstance();
      try {
     purchase.setAppInfo(APPID, APPKEY);
     } catch (Exception e1) {
      e1.printStackTrace();
     }
   /**
   * step3.IAP组件初始化开始,
   * 参数PurchaseListener,初始化函数需传入step1时实例化的onPurchaseListener。
    */
    purchase.init(listener);
    }
   private void showProgressDialog(String text) {
    if (mProgressDialog == null) {
    mProgressDialog = new ProgressDialog(Demo.this);
    mProgressDialog.setIndeterminate(true);
   LayoutInflater inflater = getLayoutInflater();
    View view = inflater.inflate(R.layout.layout, null);
    mProgressDialog.setView(view);
    mProgressDialog.setMessage("请稍后.....");
   }
   if (!mProgressDialog.isShowing()) {
   mProgressDialog.show();
   }
   }
      

Demo中Purhcase类实例化后,设置了网络超时和SDK的init接口。
如果不设置网络超时,则默认网络超时为10s,建议按默认值,如果设置的话,建议设置5s以上,12s以内。如果不调用init接口,则用户在订购解密将等待较长时间,建议在APP初始化或者数据加载过程中调用此函数。

2、SDK订购,查询,退订接口的调用

2.1、订购接口调用


订购分为租赁,永久性购买,可重复购买这三种类型的订购,各类型根据paycode来区分。此版本应用内计费SDK包含有4个订购接口


简化版订购接口


public String order(Context context, String paycode,OnPurchaseListener listener)
需要传入Activity实例,paycode,以及OnPurchaseListener的实例,此接口可以订购单件商品。而且此接口将会返回此次交易的交易ID。用户或者开发者可以通过此交易ID去查询交易是否成功。
注:此接口不能用作租赁类型的续订接口。


一般性订购接口


public String order(Context context, String paycode, int orderCount,OnPurchaseListener listener)
此接口和简化版接口相比,增加了一个int型参数,也就是指此接口支持一次订购多件商品。


完整版订购接口


public String order(Context context, String paycode, int orderCount,boolean nextCycle, OnPurchaseListener listener)
此接口和一般性接口相比,增加了一个boolean型参数,也就是指此接口支持商品续订。可以用于租赁类型商品的续订。


用户可透传数据的订购接口


public String order(Context context, String paycode, int orderCount,String data,OnPurchaseListener listener)
此接口增加了一个String型参数,是给用户透传到服务器的自定义数据,字符串数据类型,中间不能有空格。


2.2、查询接口调用


此版本应用内计费的查询分为两个接口。一个是根据交易ID查询,查询该交易是否存在。另一个是根据paycode查询,查询当前商品是否已经订购。

根据交易ID查询
public void query(Context context, String paycode, String tradeID,OnPurchaseListener listener)
使用交易ID查询,主要目的是查询该笔交易是否成功。例如,如果您记录以前某次交易的交易ID,您可以根据此接口查询是否交易成功。

根据paycode查询
public void query(Context context, String paycode,OnPurchaseListener listener)
此版本与之前版本接口保持一致,用于查询当前商品状态是否已经订购。
注意:可重复计费的商品订购成功30秒以后使用此查询接口,将查不到已订购状态。

退订接口调用
根据计费点进行退订,仅对包月类型计费点有效
public void unsubscribe(Context context, String paycode,OnPurchaseListener listener)

2.3、获取渠道ID

应用内计费sdk中包含有渠道配置文件mmiap.xml。开发者可以读取这个文件取得当前程序包的渠道id。
调用方法如下:
Step1:读取mmiap.xml文件


       private static final String CHANNEL_FILE = "mmiap.xml";
       public static String getResFileContent(String filename, Context context) {
        InputStream is = context.getClass().getClassLoader()
        .getResourceAsStream(filename);
       if (is == null) {
       return null;
        }
        StringBuilder builder = new StringBuilder();
        String content = "";
        BufferedReader bufferedReader = new BufferedReader(
        new InputStreamReader(is));
        try {
        while (bufferedReader.ready()) {
        content = bufferedReader.readLine();
        builder.append(content);
        }
        bufferedReader.close();
        } catch (IOException e) {
        return null;
        }
        return builder.toString();
        }
       

Step2:解析上面函数返回的string,最终得到channelid


   Step2:解析上面函数返回的string,最终得到channelid
   public static String LoadChannelID(Context context) {
   if (channelID != null) {
    return channelID;
   }
   // 载入渠道字符串
    String channleStr = getResFileContent(context, CHANNEL_FILE); 
    // 解析文件
   XmlPullParserFactory factory;
   try {
   factory = XmlPullParserFactory.newInstance();
   XmlPullParser parser = factory.newPullParser();
   byte[] data = channleStr.getBytes();
    ByteArrayInputStream bais = new ByteArrayInputStream(data); 
    parser.setInput(bais, "utf-8");
   int event = parser.getEventType();
    while (event != XmlPullParser.END_DOCUMENT) {
   switch (event) {
    case XmlPullParser.START_DOCUMENT:
    break;
    case XmlPullParser.START_TAG:
   String tag = parser.getName();
    if ("channel".equals(tag)) {
    channelID = parser.nextText();
    }
   break;
    case XmlPullParser.END_TAG:
   break;
    }
   event = parser.next();
    }
   } 
    catch (XmlPullParserException e) {
    channelID = null;
   return null;
    } 
    catch (IOException e) {
   channelID = null;
   return null;
    }
   return channelID;
   }
       

3、应用混淆

如果您的应用需要混淆,请参照下面的格式修改proguard.cfg。然后使用ProGuard工具对您的应用进行混淆。


   -optimizationpasses 5
   -dontusemixedcaseclassnames
   -dontskipnonpubliclibraryclasses
    -dontpreverify
    -dontwarn
   -dontnote
   -verbose
    -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
    -libraryjars libs/mmbilling.jar
   -keep public class * extends android.app.Activity
   -keep public class * extends android.app.Application
    -keep public class * extends android.app.Service
    -keep public class * extends android.content.BroadcastReceiver
    -keep public class * extends android.content.ContentProvider
    -keep public class * extends android.app.backup.BackupAgentHelper
   -keep public class * extends android.preference.Preference
    -keep public class com.android.vending.licensing.ILicensingService
    -keepclasseswithmembernames class * {
   native ;
    }
   -keepclasseswithmembernames class * {
   public (android.content.Context, android.util.AttributeSet);
    }
   -keepclasseswithmembernames class * {
   public (android.content.Context, android.util.AttributeSet, int);
   }
   -keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
   }
   -keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
    }
   -keep class com.ccit.** {*; }
   -keep class ccit.** { *; }
    -keep class mm.purchasesdk.**
   -keep class mm.purchasesdk.** {*;}
        

具体修改如下,如果要混淆某个包,则在proguard.cfg中增加:


         –keep class 应用某个包名.**
       

注意:您在混淆时,请务必在cfg文件中添加红色的代码,否则可以会导致您的程序运行出错。蓝色部分的代码,如果您使用eclipse工具混淆则需要添加,如果是ant编写的混淆脚本则不需要添加。 如果您使用ant脚本编译,可以参照build.xml中的混淆脚本修改您的脚本


       <target  name="optimize" depends="compile">   
<echo>optimize classes are put to "${out.absolute.dir}".</echo>  
<jar basedir="${out.classes.absolute.dir}" destfile="${out.absolute.dir}/temp.jar"/>  
<java jar="${jar.proguard}\proguard.jar" fork="true" failonerror="true"> 
<jvmarg value="-Dmaximum.inlined.code.length=32"/>
<arg value="@${jar.proguard}\proguard.cfg"/> 
<arg value="-injars ${out.absolute.dir}/temp.jar"/> 
<arg value="-outjars ${out.absolute.dir}/optimized.jar"/> 
<arg value="-libraryjars ${SDK.dir}/platforms/android-4/android.jar"/>                  
<arg value="-libraryjars ./libs/mmbilling.jar"/> 
</java> 
<delete file="${out.absolute.dir}/temp.jar"/>  
<delete dir="${out.classes.dir}" failonerror="false" />  
<mkdir dir="${out.classes.dir}"/>  
<unzip src="${out.absolute.dir}/optimized.jar" dest="${out.classes.absolute.dir}"/>  
<delete file="${out.absolute.dir}/optimized.jar"/>  

</target>

Proguard的路径在local.properties中进行修改。Demo中有个文件夹proguard中存放的是proguard.jar文件和proguard.cfg