SDK 简介

MaxLeap Android SDK 集中整合在 maxleap-sdk-all.zip 文件中。

1 闭源模块

maxleap-sdk-all.zip

下载

包含以下代码库:

maxleap-sdk-core-{版本}.jar 核心库,下面的代码库都依赖它

maxleap-sdk-support-{版本}.jar 用户支持模块,支持用户反馈,FAQ 管理等功能。

目录:maxleap-sdk-support-resources 用户支持模块资源文件。

maxleap-sdk-pay-{版本}.jar 支付模块,支持支付宝支付、微信支付、银联支付。

maxleap-sdk-im-{版本}.jar 实时通讯模块,支持单聊、多聊等功能

maxleap-sdk-social-{版本}.jar 包括社交分享及应用内社交模块,支持发帖、评论/点赞、关注、朋友圈、广场等功能。

maxleap-sdk-qq-{版本}.jar QQ 登录模块

maxleap-sdk-wechat-{版本}.jar 微信登录模块

maxleap-sdk-weibo-{版本}.jar 微博登录模块

maxleap-sdk-core-{版本}.jar

maxleap-sdk-core.jar 内置功能以及使用指南:

2 开源 UI 组件

2.1 MaxLoginUI

登录组件,内置 注册界面,登录界面,其他登录界面(包含手机号登录,第三方平台账号登录)。

组件地址:https://github.com/MaxLeap/Module-MaxLogin-Android

建议使用之前先了解 云数据库(MLObject 的一些基本操作)账户系统

2.2 MaxIMUI

聊天 UI 组件,内置 联系人和群组列表界面,最近聊天列表,聊天界面。

组件地址:https://github.com/MaxLeap/Module-MaxIM-Android

2.3 MaxSocialUI

应用内社交组件,包含 说说发布界面,说说列表界面,广场界面,朋友圈界面等。

组件地址:https://github.com/MaxLeap/Module-MaxSocial-Android

2.4 MaxPayUI

移动支付界面组件。

组件地址:https://github.com/MaxLeap/Module-MaxPay-Android

2.5 MaxShare

依赖于第三方平台 SDK。支持 新浪微博,微信好友,微信朋友圈,QQ好友,QQ空间 分享,但是都需要集成对应平台的 SDK,更详细内容请查阅社交分享使用指南

组件地址:https://github.com/MaxLeap/Module-MaxShare-Android

2.6 MaxIssues

用户反馈组件,通过这个组件,用户可以创建一个会话,用来反馈问题。客服可以与之对话,近似实时聊天。

组件地址:https://github.com/MaxLeap/Module-MaxIssues-Android

2.7 MaxFAQ

常见问题界面组件,包含 问题分类,问题列表,问题答案界面。可以在后台编辑问题,然后在客户端显示出来。

组件地址:https://github.com/MaxLeap/Module-MaxFAQ-Android

SDK 集成

全新项目

使用模板创建 MaxLeap 项目

  1. 获取项目模板,并解压缩至您的 Workspace

    下载 Android 项目模板

  2. 打开项目模板

    Android Studio

    1. 打开 Android Studio,点击 “Import project”
    2. 进入项目模板根目录,选择 setting.gradle
    3. 按照默认配置点击下一步,直到完成

配置 MaxLeap 项目

  1. 连接项目与 MaxLeap 应用

    ApplicationonCreate() 方法中,调用 MaxLeap.initialize 来设置您应用的 Application IDREST API Key

    import android.app.Application;
    import com.maxleap.MaxLeap;
    
    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            MaxLeap.initialize(this, "appid", "restapikey", MaxLeap.REGION_CN);
        }
    }
    
  2. 权限配置

    AndroidManifest.xml 中,给予应用以下权限:

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    
    权限用途是否必需
    ACCESS_NETWORK_STATE检测联网方式,区分用户设备使用的是2G、3G或是WiFi必需
    INTERNET允许应用程序联网,以便向我们的服务器端发送数据必需
    READ_PHONE_STATE获取用户设备的IMEI,通过IMEI来唯一的标识用户可选
    ACCESS_WIFI_STATE获取用户设备的MAC地址,通过MAC地址来唯一的标识用户可选
  3. 配置用户渠道

    AndroidManifest.xml 中配置用户渠道,渠道名可以是 google_play 之类的任意字符串。

    <application>
       <meta-data
            android:name="ml_channel"
            android:value="google_play"/>
    </application>
    
  4. 快速测试项目配置

    为了测试项目是否已经注册至 MaxLeap,我们可以向 ApplicationonCreate() 方法中添加以下代码:

    import android.app.Application;
    import com.maxleap.MaxLeap;
    import com.maxleap.MLQueryManager;
    import com.maxleap.MLQuery
    import com.maxleap.MLObject;
    
    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            MaxLeap.initialize(this, "appid", "restapikey", MaxLeap.REGION_CN);
    
            //测试当前sdk的appid和restapikey配置是否正确。正式环境可移除。
            MaxLeap.checkSDKConnection();
        }
    }
    

    运行应用,查看 Logcat 的输出日志,没有错误的话,您已经完成 MaxLeap SDK 的安装与必要的配置。

已有项目

  1. 安装 SDK 下载 SDK

  2. 将 SDK 添加至项目

    将解压后的所有 maxleap-*.jar 文件,拖拽至项目的 libs 目录中。如果你们的项目没有 libs 目录,那么就在项目的根目录下创建一个:通过右键点击项目 Project,选择 New,接下来点击 Directory 菜单即可创建新目录。

    Android Studio

    build.gradle 文件中添加下述依赖:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

同步Gradle后,即可添加完成。

配置 MaxLeap 项目

  1. 连接项目与 MaxLeap 应用

    ApplicationonCreate() 方法中,调用 MaxLeap.initialize 来设置您应用的 Application IDREST API Key

import android.app.Application;
import com.maxleap.MaxLeap;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        MaxLeap.initialize(this, "appid", "restapikey", MaxLeap.REGION_CN);
    }
}
  1. 权限配置

    AndroidManifest.xml中,给予应用以下权限:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
权限用途是否必需
ACCESS_NETWORK_STATE检测联网方式,区分用户设备使用的是2G、3G或是WiFi必需
INTERNET允许应用程序联网,以便向我们的服务器端发送数据必需
READ_PHONE_STATE获取用户设备的IMEI,通过IMEI来唯一的标识用户可选
ACCESS_WIFI_STATE获取用户设备的MAC地址,通过MAC地址来唯一的标识用户可选
  1. 配置用户渠道

    AndroidManifest.xml 中配置用户渠道,渠道名可以是 google_play 之类的任意字符串。

    <application>
       <meta-data
            android:name="ml_channel"
            android:value="google_play"/>
    </application>
    
  2. 快速测试项目配置

    为了测试项目是否已经注册至 MaxLeap,我们可以向 ApplicationonCreate() 方法中添加以下代码:

    import android.app.Application;
    import com.maxleap.MaxLeap;
    import com.maxleap.MLQueryManager;
    import com.maxleap.MLQuery
    import com.maxleap.MLObject;
    
    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            MaxLeap.initialize(this, "appid", "restapikey", MaxLeap.REGION_CN);
    
           //测试当前sdk的appid和restapikey配置是否正确。正式环境可移除。
           MaxLeap.checkSDKConnection();
        }
    }
    

    运行应用,查看 Logcat 的输出日志,没有错误的话,您已经完成 MaxLeap SDK 的安装与必要的配置。

更多集成配置

除了快速集成的方式来连接项目与maxleap应用,您也可以通过如下几种方式更灵活的配置:

AndroidManifest.xml

  1. AndroidManifest.xml中配置相应的meta-data所对应的value

    <application>
          <meta-data
               android:name="com.maxleap.APPLICATION_ID"
               android:value="appid" />
           <meta-data
               android:name="com.maxleap.REST_API_KEY"
               android:value="restapikey" />
           <meta-data
               android:name="ml_region"
               android:value="CN" />
    </application>
    
  2. 在自定义的Application中调用:

    import android.app.Application;
    import com.maxleap.MaxLeap;
    import com.maxleap.MLQueryManager;
    import com.maxleap.MLQuery
    import com.maxleap.MLObject;
    
    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            MaxLeap.initialize(this);
    
           //测试当前sdk的appid和restapikey配置是否正确。正式环境可移除。
           MaxLeap.checkSDKConnection();
        }
    }
    

    注:代码中配置的优先级会高于AndroidManifest.xml中配置的优先级。如果您同时在xml和代码中配置,将以代码配置为准。

Options参数初始化

import android.app.Application;
import com.maxleap.MaxLeap;
import com.maxleap.MLQueryManager;
import com.maxleap.MLQuery
import com.maxleap.MLObject;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        MaxLeap.Options options = new MaxLeap.Options();
        options.applicationID = "appid";
        options.restAPIKey = "restapikey";
        options.serverRegion = MaxLeap.REGION_CN;        

        MaxLeap.initialize(this, options);

       //测试当前sdk的appid和restapikey配置是否正确。正式环境可移除。
       MaxLeap.checkSDKConnection();
    }
}

使用Options参数的方式初始化,灵活度更高。您可根据实际的项目需求进行配置。

FAQ

Q: 提示"应用访问凭证不正确,请检查“是什么原因?

A:我们提供了用于检测SDK是否正确的配置的方法:MaxLeap.checkSDKConnection()。该api会向后端查询一个名为testTable的表格,而此表格默认并未被创建。如果您的配置正确,后端会正确的返回该表格的状态,以此状态来判断配置是否正确。如果配置错误,则无法正常的获取该表的状态。

可以到如下页面查看应用的配置信息

android_start_faq1

需要正确的配置对应的Application ID和REST API key初始化之后,才能正常的访问。

Q: 提示"SDK 成功连接到你的云端应用"为什么日志中还会打印http 400异常?

A:该提示表示您的sdk配置正确,只需移除MaxLeap.checkSDKConnection()测试连接的代码即可。

Q: 已经正确配置sdk,但是还是无法正常使用?

A:可以从以下几点进行排除:

  • 检查您的应用是否配置了相关的权限。
  • 检查网络连接是否通畅。
  • 检查自定义的Application是否在AndroidManifest.xml中的<application>节点的name进行了修改。
  • 下载最新版本的sdk。
  • 您可以尝试参考我们的开源组件https://github.com/MaxLeap/Module-MaxLogin-Android

数据存储

简介

什么是 Cloud Data 服务

Cloud Data 是 MaxLeap 提供的数据存储服务,它建立在对象 MLObject 的基础上,每个 MLObject 包含若干键值对。所有MLObject 均存储在 MaxLeap 上,您可以通过 iOS/Android Core SDK 对其进行操作,也可在 Console 中管理所有的对象。此外 MaxLeap 还提供一些特殊的对象,如 MLUser(用户),MLFile(文件),MLGeoPoint(地理位置),他们都是基于 MLObject 的对象。

SDK 集成

请按第二章【SDK 集成】步骤完成 SDK 集成。

Cloud Object

存储在 Cloud Data 的对象称为 MLObject,而每个 MLObject 被规划至不同的 class 中(类似“表”的概念)。MLObject 包含若干键值对,且值为兼容 JSON 格式的数据。您无需预先指定每个 MLObject 包含哪些属性,也无需指定属性值的类型。您可以随时向 MLObject 增加新的属性及对应的值, Cloud Data 服务会将其存储至云端。

新建

假设我们要保存一条数据到 Comment 类,它包含以下属性:

属性名值类型
content“我很喜欢这条分享”字符
pubUserId1314520数字
isReadfalse布尔

我们建议您使用驼峰式命名法来命名类名和字段名(如:NameYourclassesLikeThis, nameYourKeysLikeThis),让您的代码看起来整齐美观。

首先,需要在云端数据仓库中添加 Comment 类,才能够往里面插入数据。 有关添加类等操作的说明,请查阅:控制台用户手册 - 云数据

添加属性的方法与 Java 中的 Map 类似:

MLObject myComment = new MLObject("Comment");
myComment.put("content", "我很喜欢这条分享");
myComment.put("pubUserId", 1314520);
myComment.put("isRead", false);
MLDataManager.saveInBackground(myComment);

该代码运行后,您可能想知道是否真的执行了相关操作。为确保数据正确保存,您可以在 MaxLeap 开发中心查看应用中的数据浏览器。您应该会看到类似于以下的内容:

objectId: "xWMyZ4YEGZ", content: "我很喜欢这条分享", pubUserId: 1314520, isRead: false,
createdAt:"2011-06-10T18:33:42Z", updatedAt:"2011-06-10T18:33:42Z"

注意:

  • Comment表合何时创建: 出于数据安全考虑,MaxLeap 禁止客户端创建表,所以在使用前必须先登录 MaxLeap 的控制台并手动创建 Comment 表。这样在运行代码后这条数据才会被成功插入。
  • 表中同一属性值类型一致: 如果云端的这个应用中已经存在名为 Comment 的数据表,而且也包括 content、pubUserId、isRead 等属性,那么,新建 comment 对象时,对应属性的值的数据类型要和创建该属性时一致,否则保存数据将失败。
  • 数据结构: 为了数据的安全性,您必须事先在控制台指定好所需要的所有属性及其类型,然后 MLObject 才能被正常保存。
  • 内建的属性: 每个 MLObject 对象有以下几个保存元数据的属性是不需要开发者指定的。这些属性的创建和更新是由系统自动完成的,请不要在代码里使用这些属性来保存数据。

    属性名
    objectId对象的唯一标识符
    createdAt对象的创建时间
    updatedAt对象的最后修改时间
  • 大小限制: MLObject的大小被限制在 128K 以内。

  • 键的名称可以包含英文字母,数字和下划线,但是必须以字母开头。值的类型可为字符, 数字, 布尔, 数组或是MLObject,为支持JSON编码的类型即可.

  • 您可以在调用 MLDataManager.saveInBackground()时,传入第二个参数 - SaveCallback 实例,用以检查新建是否成功。

MLDataManager.saveInBackground(myComment, new SaveCallback() {
  @Override
  public void done(MLException e) {
    if(e==null){
      // 新建成功
    } else{
      // 新建失败
    }
  }
});

检索

获取 MLObject

获取单条数据

您可以通过某条数据的ObjectId,获取完整的MLObject。调用MLQueryManager.getInBackground()方法需要提供三个参数:第一个为查询对象所属的 class 名,第二个参数为 ObjectId,第三个参数为回调函数,将在 getInBackground() 方法完成后调用。

String className = "Comment";
String objId = "OBJECT_ID";
MLQueryManager.getInBackground(className, objId, new GetCallback<MLObject>() {

  @Override
  public void done(MLObject object, MLException e) {
    // Object 即为所查询的对象
  }
});

也可以通过 “属性值 + MLQuery” 方式获取 MLObject:

String className = "Comment";
String objId = "OBJECT_ID";
MLQuery query = MLQuery.getQuery(className);

MLQueryManager.getInBackground(query, objId, new GetCallback() {
    @Override
    public void done(MLObject mlObject, MLException e) {

    }
});
获取多条数据

您可以通过MLQueryManager.findAllInBackground()的方式获取多条数据:

MLQuery<MLObject> query = MLQuery.getQuery("Comment");
query.whereEqualTo("isRead",false);

MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {
  @Override
  public void done(List<MLObject> list, MLException e) {
    // list即为所查询的对象集合
  }
});
  • 当获取的数据量较大的时候,您可以通过配置MLQuerysetLimit()参数来设置每次请求的条数,进行分页:
query.setLimit(20);//每次最多返回的条数

query.setSkip(page*20);//page 为已请求的页数,此处表示跳过之前已经获取的总条数

提示:MLQuery默认每次返回的最大条数为100。若您自行设置了返回的条数,最大条数不能超过1000。如果数据较多,建议您以分页的方式多次获取。

  • 当自定义的表中包含的字段比较多时,您可以通过配置MLQueryselectKeys()来获取关注的字段:
List<String> keyList = new ArrayList<>();
keyList.add("name");
keyList.add("age");

query.selectKeys(keyList);
  • 根据指定的字段对查询的结果进行升序或降序进行排列:
query.orderByAscending(MLObject.KEY_UPDATED_AT);

query.orderByDescending(MLObject.KEY_UPDATED_AT);
获取第一条数据

如果您只需获取 Query 结果的第一条,您可以使用 MLQueryManager.getFirstInBackground()方法:

MLQuery<MLObject> query = MLQuery.getQuery("Comment");
query.whereMatches("pubId","USER_ID");

MLQueryManager.getFirstInBackground(query, new GetCallback<MLObject>() {
  @Override
  public void done(MLObject object, MLException e){
    // MLObject即为所查询的对象
  }
});
获取 MLObject 属性值

要从检索到的 MLObject 实例中获取值,可以使用相应的数据类型的 getType 方法:

MLQueryManager.getInBackground(query, objId, new GetCallback() {
    @Override
    public void done(MLObject mlObject, MLException e) {
        if (e != null) {
            //获取结果失败
            return;
        }

        int age = mlObject.getInt("age");
        String name = mlObject.getString("name");
        boolean isRead = mlObject.getBoolean("isRead");

    }
});

更多条件查询,您可以查询api进行了解。

更新

更新 MLObject 需要两步:首先获取需要更新的 MLObject,然后修改并保存。

// 根据objectId获取MLObject
MLQuery query = MLQuery.getQuery(className);
String objId="OBJECT_ID";
MLQueryManager.getInBackground(query, objId, new GetCallback<MLObject>() {

  @Override
  public void done(MLObject comment, MLException e) {
    if (e == null) {
      // 将该评论修改为“已读”
      comment.put("isRead", true);
      MLDataManager.saveInBackground(comment);
    }
  }
});

客户端会自动找出被修改的数据,所以只有 “isRead” 字段会被发送到服务器。您不需要担心其中会包含您不想更新的数据。

若需要刷新已有对象,可以调用 MLDataManager.fetchInBackground() 方法:

MLDataManager.fetchInBackground(object, new GetCallback<MLObject>() {
  @Override
  public void done(MLObject object, MLException e){
    // object即为所更新后的对象
  }
});

删除

删除MLObject

您可以使用MLDataManager.deleteInBackground() 方法删除MLObjcet。确认删除是否成功,您可以使用 DeleteCallback 回调来处理删除操作的结果。

MLDataManager.deleteInBackground(mlObject);
批量删除

您可以使用MLDataManager.deleteAllInBackground() 方法删除多个MLObjcet。

List<MLObject> objects = ...
MLDataManager.deleteAllInBackground(objects);
删除MLObject实例的某一属性

除了完整删除一个对象实例外,您还可以只删除实例中的某些指定的值。请注意只有调用 saveInBackground() 之后,修改才会同步到云端。

// 移除该实例的isRead属性
comment.remove("isRead");
// 保存
MLDataManager.saveInBackground(comment.remove);

计数器

计数器是应用常见的功能需求之一。当某一数值类型的字段会被频繁更新,且每次更新操作都是将原有的值增加某一数值,此时,我们可以借助计数器功能,更高效的完成数据操作。并且避免短时间内大量数据修改请求引发冲突和覆盖。

比如记录某用户游戏分数的字段"score",我们便会频繁地修改,并且当有几个客户端同时请求数据修改时,如果我们每次都在客户端请求获取该数据,并且修改后保存至云端,便很容易造成冲突和覆盖。

递增计数器

此时,我们可以利用increment()方法(默认增量为1),高效并且更安全地更新计数器类型的字段。如,为了更新记录用户游戏分数的字段"score",我们可以使用如下方式:

gameScore.increment("score");
MLDataManager.saveInBackground(gameScore);
指定增量
gameScore.increment("score",1000);
MLDataManager.saveInBackground(gameScore);

注意,增量无需为整数,您还可以指定增量为浮点类型的数值。

递减计数器
gameScore.increment("score",-1000);
MLDataManager.saveInBackground(gameScore);

数组

您可以通过以下方式,将数组类型的值保存至MLObject的某字段(如下例中的skills字段)下:

增加至数组尾部

您可以使用add()addAll()skills属性的值的尾部,增加一个或多个值。

gameScore.add("skills", "driving");
gameScore.addAll("skills", Arrays.asList("flying", "kungfu"));
MLDataManager.saveInBackground(gameScore);

同时,您还可以通过addUnique()addAllUnique()方法,仅增加与已有数组中所有item都不同的值。插入位置是不确定的。

使用新数组覆盖

调用put()函数,skills字段下原有的数组将被覆盖:

gameScore.put("skills", Arrays.asList("flying", "kungfu"));
MLDataManager.saveInBackground(gameScore);
删除某数组字段的值

调用removeAll()函数,skills字段下原有的数组将被清空:

gameScore.removeAll("skills");
MLDataManager.saveInBackground(gameScore);

注意:

  • Remove和Add/Put必需分开调用保存函数,否则数据不能正常上传和保存。

关联数据

对象可以与其他对象相联系。如前面所述,我们可以把一个 MLObject 的实例 a,当成另一个 MLObject 实例 b 的属性值保存起来。这可以解决数据之间一对一或者一对多的关系映射,就像数据库中的主外键关系一样。

注:MaxLeap Services是通过 Pointer 类型来解决这种数据引用的,并不会将数据 a 在数据 b 的表中再额外存储一份,这也可以保证数据的一致性。

使用 Pointer 实现

Pointer类型可以实现一对一关联。

添加关联数据

例如:针对_User表,除了账号默认的基本信息外,还可以关联一个名为UserExtraInfo的表格,以扩展用户的基本信息。

第一步,在控制台创建UserExtraInfo表,其中可以自定义一些扩展的字段,比如性别、年龄等;

第二步,在_User表中添加列,列名称指定为extraInfo,列类型指定为Pointer,目标类指定为UserExtraInfo表即可。

第三步,代码中使用:

MLUser currentUser = MLUser.getCurrentUser();

MLObject userExtraInfo = new MLObject("UserExtraInfo");
userExtraInfo.put("nickName", "hehe");
currentUser.put("extraInfo", userExtraInfo);

MLDataManager.saveInBackground(currentUser, new SaveCallback() {
    @Override
    public void done(MLException e) {
        if (e == null) {
            showToast("关联成功");
        }
    }
});
获取关联数据

默认情况下,当您获取一个对象的时候,关联的 MLObject 不会被获取。这些对象除了 objectId 之外,其他属性值都是空的,要得到关联对象的全部属性数据,需要再次调用 fetch 方法(下面的例子对应为上一步的添加之后):

MLUser currentUser = MLUser.getCurrentUser();
MLObject usrExtraInfo = currentUser.getMLObject("extraInfo");

MLDataManager.fetchInBackground(usrExtraInfo, new GetCallback<MLObject>() {
    @Override
    public void done(MLObject mlObject, MLException e) {
     //mlObject即关联的表格
    }
});

使用 Relation 实现

Relation类型可以实现一对多关联。

添加关联数据

例如:一条微博信息可能会对应多条评论。创建一条微博信息并对应多条评论信息,您可以这样实现:

第一步,在控制台创建Comment表,用来存储多条评论内容;

第二步,创建Post表,用来存储发出的微博。在该表中添加列,列名称指定为comment,列类型指定为Relation,目标类指定为Comment表即可。

第三步,代码中使用:

将两条评论分别关联至一条微博中:

// 创建微博信息
MLObject myPost = new MLObject("Post");
myPost.put("content", "这是我的第一条微博信息,请大家多多关照。");

// 创建评论信息
MLObject myComment = new MLObject("Comment");
myComment.put("content", "期待您更多的微博信息。");

// 创建另一条评论信息
MLObject anotherComment = new MLObject("Comment");
anotherComment.put("content", "期待您更多的微博信息。");

// 将两条评论信息放至同一个List中
List<MLObject> listComment = new ArrayList<>();
listComment.add(myComment);
listComment.add(anotherComment);

// 在微博中关联这两条评论
myPost.put("comment", listComment);

// 这将保存两条数据,分别为微博信息和评论信息
MLDataManager.saveInBackground(myPost, new SaveCallback() {
    @Override
    public void done(MLException e) {
        if (e == null) {
            showToast("多条评论成功");
        }
    }
});

注意:

  • Java 6及更低版本请使用List<MLObject> listComment = new ArrayList<MLObject>()创建listComment.
  • 您也可以选择使用add()方法,逐个添加MLObject至属性中:
myPost.add("comment", myComment);
myPost.add("comment", anotherComment);
使用 MLRelation 实现关联

您可以使用 MLRelation 来建模多对多关系。这有点像 List 链表,但是区别之处在于,在获取附加属性的时候,MLRelation 不需要同步获取关联的所有 MLRelation 实例。这使得 MLRelation 比链表的方式可以支持更多实例,读取方式也更加灵活。

例如,一个 User 可以赞很多 Post。这种情况下,就可以用getRelation()方法保存一个用户喜欢的所有 Post 集合。为了新增一个喜欢的 Post,您可以这样做:

MLUser user = MLUser.getCurrentUser();
//在user实例中,创建MLRelation实例 - likes
MLRelation<MLObject> relation = user.getRelation("likes");
//在likes中添加关联 - post
relation.add(myPost);
MLUserManager.saveInBackground(user);

您可以从 MLRelation 中移除一个 Post:

relation.remove(post);
获取关联数据

默认情况下,处于关系中的对象集合不会被同步获取到。您可以通过 getQuery 方法返回的 MLQuery 对象,使用它的 findInBackground() 方法来获取 Post 链表,像这样:

MLRelation<MLObject> relation = myPost.getRelation("comment");
relation.setTargetClass("Comment");

MLQueryManager.findAllInBackground(relation.getQuery(), new FindCallback<MLObject>() {

    @Override
    public void done(List<MLObject> results, MLException e) {
        if (e != null) {
        } else {
            // results包含relation中所有的关联对象
        }
    }
});

如果您只想获取链表的一个子集合,您可以添加更多的约束条件到 getQuery 返回 MLQuery 对象上(这一点是直接使用 List 作为属性值做不到的),例如:

MLQuery<MLObject> query = relation.getQuery();
// 在 query 对象上可以添加更多查询约束
query.skip(10);
query.limit(10);

更多关于 MLQuery 的信息,请查看的查询部分。查询的时候,一个 MLRelation 对象运作起来像一个对象链表,因此任何您作用在链表上的查询(除了 include),都可以作用在 MLRelation上。

数据类型

目前为止,我们支持的数据类型有 String、Int、Boolean 以及 MLObject 对象类型。同时 MaxLeap 也支持 java.util.Date、byte[]数组、JSONObject、JSONArray 数据类型。 您可以在 JSONArray 对象中嵌套 JSONObject 对象存储在一个 MLObject 中。 以下是一些例子:

int myNumber = 42;
String myString = "the number is " + myNumber;
Date myDate = new Date();

JSONArray myArray = new JSONArray();
myArray.put(myString);
myArray.put(myNumber);

JSONObject myObject = new JSONObject();
myObject.put("number", myNumber);
myObject.put("string", myString);

byte[] myData = { 4, 8, 16, 32 };

MLObject bigObject = new MLObject("BigObject");
bigObject.put("myNumber", myNumber);
bigObject.put("myString", myString);
bigObject.put("myDate", myDate);
bigObject.put("myData", myData);
bigObject.put("myArray", myArray);
bigObject.put("myObject", myObject);
bigObject.put("myNull", JSONObject.NULL);
MLDataManager.saveInBackground(bigObject);

我们不建议存储较大的二进制数据,如图像或文件不应使用 MLObject 的 byte[] 字段类型。MLObject 的大小不应超过 128 KB。如果需要存储较大的文件类型如图像、文件、音乐,可以使用 MLFile 对象来存储,具体使用方法可见文件部分。 关于处理数据的更多信息,可查看数据安全

文件

MLFile 的创建和上传

MLFile 可以让您的应用程序将文件存储到服务器中,以应对文件太大或太多,不适宜放入普通 MLObject 的情况。比如常见的文件类型图像文件、影像文件、音乐文件和任何其他二进制数据(大小不超过 100 MB)都可以使用。

目前有两种方式可以实现文件的上传:

1.使用MLFileManagersaveInBackground()方法,将MLFile对象上传至服务器。

直接上传File文件:

private void upLoadFile(String filePath) {
    File file = new File(filePath);
    if (!file.exists()) {
        return;
    }

    final MLFile mlFile = new MLFile(file.getName(), file);

    MLFileManager.saveInBackground(mlFile, new SaveCallback() {
        @Override
        public void done(MLException e) {
            if(e ==null){
                String url = mlFile.getUrl();//上传完成后,得到该文件的下载地址
                System.out.println(url);
            }
        }
    }, new ProgressCallback() {
        @Override
        public void done(int percentDone) {
            System.out.println("percentDone:" + percentDone);
        }
    });
}

您也可以上传byte数组:

public void uploadFile(Bitmap img){
  // 将Bitmap转换为二进制数据byte[]
  Bitmap bitmap = img;
  ByteArrayOutputStream stream = new ByteArrayOutputStream();
  bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
  byte[] image = stream.toByteArray();

  // 创建MLFile对象
  MLFile myFile = new MLFile("myPic.png", image);

  // 上传
  MLFileManager.saveInBackground(myFile, new SaveCallback() {
    @Override
    public void done(MLException e) {

    }
  });
}

注意:以上MLFileManager的方式会将文件直接将上传至存储服务器。当上传成功后,需要自己将下载的路径做记录或者存储。否则,您将无法在控制台查看到该文件。

2.可以将 MLFile 直接存储到其他对象的某个属性里,后续可以取出来继续使用。

如下示例中,在 NewsImages 表中,存在image和image2两个FILE类型的字段和一个STRING类型的name,将MLFile直接存储到MLObject中:

private void upLoadFileByMLObject(String path1, String path2) {

    File file = new File(path1);
    File file2 = new File(path2);

    if (!file.exists() || !file2.exists()) {
        return;
    }

    final MLFile mlFile = new MLFile(file.getName(), file);
    final MLFile mlFile2 = new MLFile(file2.getName(), file2);

    MLObject obj = new MLObject("NewsImages");
    obj.put("image", mlFile);
    obj.put("image2", mlFile2);
    obj.put("name", file.getName());

    MLDataManager.saveInBackground(obj, new SaveCallback() {
        @Override
        public void done(MLException e) {
            if (e == null) {
                String url = mlFile.getUrl() + "   mlFile2:" + mlFile2.getUrl();
                System.out.println(url);
            }
        }
    }, new FileProgressCallback() {
        @Override
        public void done(int filePosition, int percentDone) {
            System.out.println("filePosition:" + filePosition + "  percentDone:" + percentDone);
        }
    });
}

注意:以上方式上传成功后,会在NewsImages表中生成一条上传记录,其中会包含文件的下载路径。

上传进度

MLFile 的 saveInBackground() 方法除了可以传入一个 SaveCallback 回调来通知上传成功或者失败之外,还可以传入第二个参数 ProgressCallback 回调对象,通知上传进度:

MLFileManager.saveInBackground(file, new SaveCallback() {
    @Override
    public void done(MLException e) {

    },new ProgressCallback() {
    @Override
    public void done(int i) {
      // 打印进度
      System.out.println("uploading: " + i);
    }
});

下载文件

直接下载文件
  1. 通过MLObject,指定MLFile
  2. 调用 MLFileManager.getDataInBackground() 下载:
MLFile myFile=imgupload.getMLFile("testpic");
MLFileManager.getDataInBackground(myFile, new GetDataCallback() {
    @Override
    public void done(byte[] bytes, MLException e) {

    }
});
获取文件的 url 自行处理下载:
String url = myFile.getUrl();

删除文件

到目前为止,文件的删除权限暂不开放。

查询

基本查询

使用MLQuery查询MLObject分三步:

  1. 创建一个 MLQuery 对象,并指定对应的"MLObject class";
  2. 为MLQuery添加过滤条件;
  3. 执行MLQuery:使用 MLQueryManager.findAllInBackground() 方法结合FindCallback 回调类来查询与条件匹配的 MLObject 数据。

例如,查询指定人员的信息,使用 whereEqualTo 方法来添加条件:

MLQuery<MLObject> query = MLQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan Stemkoski");
MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {
    public void done(List<MLObject> scoreList, MLException e) {
        if (e == null) {
            Log.d("score", "Retrieved " + scoreList.size() + " scores");
        } else {
            Log.d("score", "Error: " + e.getMessage());
        }
    }
});

查询条件

设置过滤条件

如果要过滤掉特定键的值时可以使用 whereNotEqualTo 方法。比如需要查询 isRead 不等于true的数据时可以这样写:

query.whereNotEqualTo("isRead", true);

当然,您可以在您的查询操作中添加多个约束条件(这些条件是 “and” 的关系),来查询符合您要求的数据。

query.whereNotEqualTo("isRead", true);
query.whereGreaterThan("userAge", 18);
设置结果数量

您可以使用 setLimit 方法来限制查询结果的数据条数。默认情况下 Limit 的值为 100,最大 1000,在 0 到 1000 范围之外的都强制转成默认的 100。

query.setLimit(10); // 设置query结果不超过10条

您也可以使用MLQueryManager.getFirstInBackground()来执行Query,以获取查询的第一条结果。

MLQuery<MLObject> query = MLQuery.getQuery("GameScore");
query.whereEqualTo("playerEmail", "dstemkoski@example.com");
MLQueryManager.getFirstInBackground(query, new GetCallback<MLObject>() {
  public void done(MLObject object, MLException e) {
    if (object == null) {
      Log.d("score", "The getFirst request failed.");
    } else {
      Log.d("score", "Retrieved the object.");
    }
  }
});
对结果排序

对于类型为数字或字符串的属性,您可以使用升序或降序的方式来控制查询数据的结果顺序:

// Sorts the results in ascending order by the score field
query.orderByAscending("score");

// Sorts the results in descending order by the score field
query.orderByDescending("score");
设置数值范围

对于类型为数字的属性,您可以对其值的大小进行筛选:

// Restricts to wins < 50
query.whereLessThan("wins", 50);

// Restricts to wins <= 50
query.whereLessThanOrEqualTo("wins", 50);

// Restricts to wins > 50
query.whereGreaterThan("wins", 50);

// Restricts to wins >= 50
query.whereGreaterThanOrEqualTo("wins", 50);
设置返回数据包含的属性

您可以通过selectKeys设置返回的数据包含哪些属性(自动包含内建属性,如objectId, createdAt 及 updatedAt):

MLQuery<MLObject> query = MLQuery.getQuery("GameScore");
query.selectKeys(Arrays.asList("playerName", "score"));
MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {

    @Override
    public void done(List<MLObject> objects, MLException exception) {
         // results has the list of objects
    }
});

随后对于返回的MLObject,您可以可通过MLDataManager.fetchInBackground()获取该数据其他属性。

MLObject object = results.get(0);
MLDataManager.fetchInBackground(object, new GetCallback<MLObject>() {

    @Override
    public void done(MLObject object, MLException exception) {
        // all fields of the object will now be available here.
    }
});
设置更多约束

在数据较多的情况下,分页显示数据是比较合理的解决办法,setSkip 方法可以做到跳过首次查询的多少条数据来实现分页的功能。

query.setSkip(10); // skip the first 10 results

如果您想查询匹配几个不同值的数据,如:要查询 “Jonathan Walsh”, “Dario Wunsch”, “Shawn Simon” 三个账号的信息时,您可以使用whereContainedIn(类似SQL中的in查询)方法来实现。

String[] names = {"Jonathan Walsh", "Dario Wunsch", "Shawn Simon"};
query.whereContainedIn("playerName", Arrays.asList(names));

相反,您想查询“Jonathan Walsh”, “Dario Wunsch”, “Shawn Simon"这三个账号以外的其他人的信息(类似 SQL 中的 not in 查询),您可以使用 whereNotContainedIn 方法来实现。

String[] names = {"Jonathan Walsh", "Dario Wunsch", "Shawn Simon"};
query.whereNotContainedIn("playerName", Arrays.asList(names));

您可以通过whereExists查询存在指定属性的数据。相应的,您可以通过whereDoesNotExist,查询不存在指定属性的数据。

// 查询包含"score"属性的对象
query.whereExists("score");

// 查询不包含"score"属性的对象
query.whereDoesNotExist("score");

您可以使用whereMatchesKeyInQuery方法查询一个query中的某属性的值与另一个query中某属性的值相同的数据。

如:现有一个名为"Team"的class存储篮球队的数据,有一个名为"User"的class存储用户数据。Team中使用"city"存储篮球队所在地,User中使用"hometown"存储其家乡。则您可以通过以下Query,查找家乡与特定篮球队所在地相同的用户。

MLQuery<MLObject> teamQuery = MLQuery.getQuery("Team");
//筛选篮球队:选择胜率超过50%的篮球队
teamQuery.whereGreaterThan("winPct", 0.5);
MLQuery<MLUser> userQuery = MLUser.getQuery();
userQuery.whereMatchesKeyInQuery("hometown", "city", teamQuery);
MLQueryManager.findAllInBackground(userQuery, new FindCallback<MLUser>() {

  @Override
  public void done(List<MLUser> results, MLException e) {
    // results中包含胜率超过50%的篮球队所在地的用户
  }
});

相应的,您可以通过whereDoesNotMatchKeyInQuery方法,获取家乡不在指定篮球队所在地的用户。

MLQuery<MLUser> anotherUserQuery = MLUser.getQuery();
losingUserQuery.whereDoesNotMatchKeyInQuery("hometown", "city", teamQuery);
MLQueryManager.findAllInBackground(anotherUserQuery, new FindCallback<MLUser>() {

  @Override
  public void done(List<MLUser> results, MLException e) {
    // results中包含家乡不在指定篮球队所在地的用户
  }
});

不同属性值类型的查询

值类型为数组的查询

如果一个 Key 对应的值是一个数组,您可以查询 Key 的数组包含了数字 2 的所有对象,通过:

// Find objects where the array in arrayKey contains the number 2.
query.whereEqualTo("arrayKey", 2);

同样,您可以查询出 Key 的数组同时包含了 2,3 和 4 的所有对象:

// Find objects where the array in arrayKey contains all of the numbers 2, 3, and 4.
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(2);
numbers.add(3);
numbers.add(4);
query.whereContainsAll("arrayKey", numbers);

值类型为字符串的查询

使用 whereStartsWith 方法来限制字符串的值以另一个字符串开头。非常类似 MySQL 的 LIKE 查询,这样的查询会走索引,因此对于大数据集也一样高效:

// Finds barbecue sauces that start with "Big Daddy's".
MLQuery<MLObject> query = MLQuery.getQuery("BarbecueSauce");
query.whereStartsWith("name", "Big Daddy's");

值类型为MLObject查询

MLObject类型字段匹配MLObject

如果您想获取某个字段匹配特定 MLObject 的数据,您可以像查询其他数据类型那样使用 whereEqualTo 来查询。例如,如果每个 Comment 对象都包含一个 Post 对象(在 post 字段上),您可以获取特定 Post 的所有 Comment 列表:

// 假设 MLObject myPost 已经在前面创建
MLQuery<MLObject> query = MLQuery.getQuery("Comment");
query.whereEqualTo("post", myPost);

MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {
public void done(List<MLObject> commentList, MLException e) {
 // commentList now has the comments for myPost
}
});
MLObject 类型字段匹配 Query

如果您想查询的对象的某个字段包含了一个 MLObject,并且这个 MLObject 匹配一个不同的查询,您可以使用 whereMatchesQuery 嵌套查询方法。请注意,默认的 limit 限制 100 也同样作用在内部查询上。因此如果是大规模的数据查询,您可能需要仔细构造您的查询对象来获取想要的行为。例如,为了查询有图片附件的 Post 的评论列表:

MLQuery<MLObject> innerQuery = MLQuery.getQuery("Post");
innerQuery.whereExists("image");
MLQuery<MLObject> query = MLQuery.getQuery("Comment");
query.whereMatchesQuery("post", innerQuery);
MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {
  public void done(List<MLObject> commentList, MLException e) {
    // comments now contains the comments for posts with images.
  }
});

反之,不想匹配某个子查询,您可以使用 whereDoesNotMatchQuery 方法。 比如为了查询没有图片的 Post 的评论列表:

MLQuery<MLObject> innerQuery = MLQuery.getQuery("Post");
innerQuery.whereExists("image");
MLQuery<MLObject> query = MLQuery.getQuery("Comment");
query.whereDoesNotMatchQuery("post", innerQuery);
MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {
  public void done(List<MLObject> commentList, MLException e) {
    // comments now contains the comments for posts without images.
  }
});
返回指定 MLObject 类型的字段

默认情况下,当您获取一个对象的时候,关联的 MLObject 不会被获取,但您可以使用 include 方法将其返回。例如。您想获取最近的 10 条评论,同时包括它们关联的 post:

MLQuery<MLObject> query = MLQuery.getQuery("Comment");

//Retrieve the most recent ones
query.orderByDescending("createdAt");

//Only retrieve the MLt ten
query.setLimit(10);

//Include the post data with each comment
query.include("post");

MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() {
public void done(List<MLObject> commentList, MLException e) {
 // commentList now contains the MLt ten comments, and the "post"
 // field has been populated. For example:
 for (MLObject comment : commentList) {
   // This does not require a network access.
   MLObject post = comment.getMLObject("post");
   Log.d("post", "retrieved a related post");
 }
}
});

您可以使用 dot(英语句号)操作符来多层 include 内嵌的对象。比如,您同时想 include 一个 Comment 的 post 里的 author(作者)对象(假设 author 对应的值是 MLUser 实例),您可以这样做:

query.include("post.author");

个数查询

如果您只是想统计有多少个对象满足查询,您并不需要获取所有匹配的对象,可以直接使用 count 替代 find。例如,查询一个账户发了多少微博:

MLQuery<MLObject> query = MLQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Sean Plott");
MLQueryManager.countInBackground(query, new CountCallback() {
  public void done(int count, MLException e) {
    if (e == null) {
      // The count request succeeded. Log the count
      Log.d("score", "Sean has played " + count + " games");
    } else {
      // The request failed
    }
  }
});

复合查询

您可以通过 MLQuery.or 方法查询匹配多个 Query 中一个的数据。如,您可以通过以下方式,获取胜场超过90场或低于10场的玩家名单:

MLQuery<MLObject> lotsOfWins = MLQuery.getQuery("Player");
lotsOfWins.whereGreaterThan("score", 90);

MLQuery<MLObject> fewWins = MLQuery.getQuery("Player");
fewWins.whereLessThan("score", 10);

List<MLQuery<MLObject>> queries = new ArrayList<MLQuery<MLObject>>();
queries.add(lotsOfWins);
queries.add(fewWins);

MLQuery<MLObject> mainQuery = MLQuery.or(queries);
MLQueryManager.findAllInBackground(mainQuery, new FindCallback<MLObject>() {
  public void done(List<MLObject> results, MLException e) {
    // results包含胜场超过90场或低于10场的玩家。
  }
});

MLObject子类

MaxLeap 希望设计成能让人尽快上手并使用。您可以通过 MLDataManager.fetchInBackground() 方法访问所有的数据。但是在很多现有成熟的代码中,子类化能带来更多优点,诸如简洁、可扩展性以及 IDE 提供的代码自动完成的支持等等。子类化不是必须的,您可以将下列代码转化:

MLObject shield = new MLObject("Armor");
shield.put("displayName", "Wooden Shield");
shield.put("fireproof", false);
shield.put("rupees", 50);

成这样:

Armor shield = new Armor();
shield.setDisplayName("Wooden Shield");
shield.setFireproof(false);
shield.setRupees(50);

创建 MLObject 子类

创建一个 MLObject 的子类很简单:

  1. 首先声明一个子类继承自 MLObject。
  2. 添加 @MLClassName 注解。它的值必须是一个字符串,也就是您过去传入 MLObject 构造函数的类名,该值与你在控制台创建的表名相同。这样一来,后续就不需要再在代码中出现这个字符串类名。
  3. 确保您的子类有一个 public 的默认(参数个数为 0)的构造函数。切记不要在构造函数里修改任何 MLObject 的字段。
  4. 在调用 MaxLeap.initialize() 注册应用之前,注册子类 MLObject.registerSubclass(Yourclass.class).

下列代码成功实现并注册了 MLObject 的子类 Armor:

// Armor.java
import com.maxleap.MLObject;
import com.maxleap.MLClassName;

@MLclassName("t_armor")
public class Armor extends MLObject {
}

// App.java
import com.maxleap.MaxLeap;
import android.app.Application;

public class App extends Application {
  @Override
  public void onCreate() {
    super.onCreate();

    MLObject.registerSubclass(Armor.class);
    MaxLeap.initialize(this, ML_APPLICATION_ID, ML_CLIENT_KEY);
  }
}

属性的访问/修改

添加方法到 MLObject 的子类有助于封装类的逻辑。您可以将所有跟子类有关的逻辑放到一个地方,而不是分成多个类来分别处理商业逻辑和存储/转换逻辑。

您可以很容易地添加访问器和修改器到您的 MLObject 子类。像平常那样声明字段的 getter 和 setter 方法,但是通过 MLObject 的 get 和 put 方法来实现它们。下面是这个例子为 Post 类创建了一个 content 的字段:

// Armor.java
@MLClassName("t_armor")
public class Armor extends MLObject {
  public String getDisplayName() {
    return getString("displayName");
  }
  public void setDisplayName(String value) {
    put("displayName", value);
  }
}

现在您就可以使用 armor.getDisplayName()方法来访问 displayName 字段,并通过 armor.setDisplayName() 来修改它。这样就允许您的 IDE 提供代码自动完成功能,并且可以在编译时发现到类型错误。

各种数据类型的访问器和修改器都可以这样被定义,使用各种 get()方法的变种,例如 getInt(),getMLFile()或getMap().

定义函数

如果您不仅需要一个简单的访问器,而是有更复杂的逻辑,您可以实现自己的方法,例如:

public void takeDamage(int amount) {
  // Decrease the armor's durability and determine whether it has broken
  increment("durability", -amount);
  if (getDurability() < 0) {
    setBroken(true);
  }
}

创建子类的实例

您可以使用您自定义的构造函数来创建您的子类对象。您的子类必须定义一个公开的默认构造函数,并且不修改任何父类 MLObject 中的字段,这个默认构造函数将会被 SDK 使用来创建子类的强类型的对象。

要创建一个到现有对象的引用,可以使用 MLObject.createWithoutData():

Armor armorReference = MLObject.createWithoutData(Armor.class, armor.getObjectId());

子类的查询

您可以通过静态方法 MLQuery.getQuery() 获取特定的子类的查询对象。下面的例子用以查询用户可购买的所有防具:

MLQuery<Armor> query = MLQuery.getQuery(Armor.class);
query.whereLessThanOrEqualTo("rupees", MLUser.getCurrentUser().get("rupees"));
MLQueryManager.findAllInBackground(query, new FindCallback<Armor>() {
  @Override
  public void done(List<Armor> results, MLException e) {
    for (Armor a : results) {
      // ...
    }
  }MLUser
});

地理位置

MaxLeap 提供 MLGeoPoint对象,帮助用户根据地球的经度和纬度坐标进行基于地理位置的信息查询。

MLGeoPoint 字段说明

创建 MLGeoPoint

MLGeoPoint需要提供两个参数:第一个为纬度(正数表示北纬),第二个参数为经度(正数表示东经)。

//创建北纬40度,西经30度的MLGeoPoint
MLGeoPoint point = new MLGeoPoint(40.0, -30.0);

该MLGeoPoint对象可被存储在MLObject中:

myShop.put("location", point);

地理位置查询

查询距离某地理位置最近的对象

您可以通过 whereNear 方法获取A点附近的对象,该方法需要提供两个参数:第一个为目标对象存储地理位置的字段名,第二个参数为A点的地理位置。通过下面的例子,我们可以找到离某用户最近的十家店铺。

MLGeoPoint userLocation = (MLGeoPoint) userObject.get("location");
MLQuery<MLObject> shopQuery = MLQuery.getQuery("Shop");
shopQuery.whereNear("location", userLocation);
query.setLimit(10);
MLQueryManager.findAllInBackground(query, new FindCallback<MLObject>() { ... });
查询某地理位置一定距离内的对象

您可以使用 whereWithinKilometers, whereWithinMiles 方法查找某地理位置一定距离内的对象。其用法与上述例子类似。

查询一定地理位置范围内对象

您可以通过 whereWithinGeoBox 方法获取一定地理位置范围内的对象,该方法需要提供三个参数:第一个为目标对象存储地理位置的字段名,后两个参数为 MLGeoPoint 对象,以这两个点连成的线段为直径的圆,便是whereWithinGeoBox 将查询的范围。通过下面的例子,我们可以找到一定地理位置范围内所有店铺。

MLGeoPoint southwestOfSF = new MLGeoPoint(37.708813, -122.526398);
MLGeoPoint northeastOfSF = new MLGeoPoint(37.822802, -122.373962);
MLQuery<MLObject> query = MLQuery.getQuery("PizzaPlaceObject");
query.whereWithinGeoBox("location", southwestOfSF, northeastOfSF);
MLQueryManager.findAllInBackground(new FindCallback<MLObject>() { ... });

请注意:

  1. 每个 MLObject 类仅可能有一个带 MLGeoPoint 对象的键。
  2. GeoPoint 的纬度必须在 -90.090.0 之间。经度必须在 -180.0180.0 之间。若纬度或经度设置超出边界,会引起错误。

数据安全

每个到达 MaxLeap 云服务的请求是由移动端 SDK,管理后台,云代码或其他客户端发出,每个请求都附带一个 security token。MaxLeap 后台可以根据请求的 security token 确定请求发送者的身份和授权,并在处理数据请求的时候,根据发送者的授权过滤掉没有权限的数据。

具体的介绍及操作方法,请参考 数据存储-使用指南

账号服务

准备

安装 SDK

  1. 请按第二章 【SDK 集成】步骤,配置项目

  2. 将 SDK 添加至项目

    将解压后的 maxleap-sdk-core-<version>.jar 文件添加到项目的 libs 目录中。

用户管理

MLUserMLObject 的子类,它继承了 MLObject 所有的方法,具有 MLObject 相同的功能。不同的是,MLUser 增加了一些特定的关于用户账户相关的功能。

如果当前应用没有用户时,SDK 会尝试在应用打开时会自动尝试创建一个匿名用户。有关匿名用户的概念请看匿名用户章节的介绍。

字段说明

MLUser 除了从 MLObject 继承的属性外,还有几个特定的属性:

属性名类型介绍是否必需或唯一
usernameString用户的用户名必需
passwordString用户的密码必需
emailString用户的电子邮件地址可选
emailVerifiedBoolean电子邮件是否验证可选
mobilePhoneString手机号可选
mobilePhoneVerifiedString手机号码是否验证可选
installationIdsString用户完成的所有安装的 InstallationId可选

注意:

  • 请确保用户名、手机号和电子邮件地址是独一无二的。

  • 这些属性和其它 MLObject 的属性不同,在设置时不是使用的 put() 方法,而是使用专门的 setXXX() 方法。

匿名用户

匿名用户是指提供用户名和密码,系统为您创建的一类特殊用户,它享有其他用户具备的相同功能。不过,一旦注销,匿名用户的所有数据都将无法访问。如果您的应用需要使用一个相对弱化的用户系统时,您可以考虑 MaxLeap 提供的匿名用户系统来实现您的功能。

默认情况下,SDK会通过MLUser.getCurrentUser()的方式来检测本地是否缓存注册用户信息。若不存在,会默认的在后端的_User表中创建一个匿名用户。您可以通过修改options.enableAnonymousUser参数,来取消匿名用户的默认创建:

//初始化MaxLeap的时候,将Options中的enableAnonymousUser参数改为false
MaxLeap.Options options = new MaxLeap.Options();
        options.applicationID = "appid";
        options.restAPIKey = "restapikey";
        options.serverRegion = MaxLeap.REGION_CN; 

        options.enableAnonymousUser = false;

您也可以通过 MLAnonymousUtils 获取一个匿名的用户账号:

MLAnonymousUtils.loginInBackground(new LogInCallback<MLUser>() {
      @Override
      public void done(MLUser user, MLException e) {
        if (e != null) {
          Log.d("MyApp", "Anonymous login failed.");
        } else {
          Log.d("MyApp", "Anonymous user logged in.");
        }
  }
});

注意:

  • 假如当前用户是一个匿名用户,这个时候直接调用注册接口,sdk 会把这个匿名用户更新成为一个普通用户,而不会创建一个新用户。

  • 匿名用户采用异步的方式创建,有一定的延迟,如果应用在某个时刻需要匿名登录,却发现当前用户为空,就需要手动创建匿名用户。

注册

用户名密码注册

  1. 创建 MLUser 对象,并提供必需的 usernamepassword
  2. 利用 MLUserManager.signUpInBackground() 保存至云端。
MLUser user = new MLUser();
user.setUserName("userName");
user.setPassword("passWord");

MLUserManager.signUpInBackground(user, new SignUpCallback() {
    public void done(MLException e) {
        if (e == null) {
            // 注册成功
        } else {
            // 注册失败
        }
    }
});

注册成功可以进行绑定手机号或者绑定邮箱等操作,以便忘记密码后找回密码。

注意:

  • 在注册过程中,服务器会进行注册用户信息的检查,以确保注册的用户名和电子邮件地址是独一无二的。此外,服务端还会对用户密码进行不可逆的加密处理,不会明文保存任何密码,应用切勿再次在客户端加密密码,这会导致重置密码等功能不可用。

  • 注册使用的是 signUpInBackground() 方法,而不是 saveInBackground() 方法。另外还有各种不同的 signUp 方法。像往常一样,我们建议在可能的情况下尽量使用异步版本的 signUp 方法,这样就不会影响到应用程序主 UI 线程的响应。您可以阅读 API 中更多的有关这些具体方法的使用。

  • 如果注册不成功,您可以查看返回的错误对象。最有可能的情况是,用户名或电子邮件已经被另一个用户注册。这种情况您可以提示用户,要求他们尝试使用不同的用户名进行注册。

  • 您也可以要求用户使用 Email 做为用户名注册,这样做的好处是,您在提交信息的时候可以将输入的“用户名“默认设置为用户的 Email 地址,以后在用户忘记密码的情况下可以使用 MaxLeap 提供的重置密码功能。

手机号验证码注册

MaxLeap 支持直接使用手机号码进行注册,注册成功后手机号码将作为用户的用户名。同时,该手机号处于已认证的状态,您可以通过手机号重置密码的方式为该账号设置密码。再次登录的时候,可以使用手机号和密码进行登录。或者也可以通过手机号验证码登录

获得注册短信验证码

MLUserManager.requestLoginSmsCodeInBackground("手机号码", new RequestSmsCodeCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

使用短信验证码和手机号进行注册

MLUserManager.loginWithSmsCodeInBackground("手机号码", "验证码", new LogInCallback<MLUser>() {
    @Override
    public void done(final MLUser user, final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

登录

用户名密码登录

您可以通过 MLUserManager.logInInBackground() 方法登录。字段说明:第一个参数为用户名,第二个参数为密码,第三个参数为回调方法 LogInCallback().

MLUserManager.logInInBackground("userName", "passWord", new LogInCallback<MLUser>() {
  public void done(MLUser user, MLException e) {
    if (user != null) {
      // 登录成功
    } else {
      // 登录失败
    }
  }
});

手机号验证码登录

此种登录方式同手机号验证码注册。当用户使用手机号和验证码注册成功之后,如果该用户不存在则会创建一个新的用户,如果存在则直接登录到该用户。

获得登录短信验证码

MLUserManager.requestLoginSmsCodeInBackground("手机号码", new RequestSmsCodeCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

使用短信验证码和手机号进行登录

MLUserManager.loginWithSmsCodeInBackground("手机号码", "验证码", new LogInCallback<MLUser>() {
    @Override
    public void done(final MLUser user, final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

当前用户

如果用户在每次打开您的应用程序时都要登录,这将会直接影响到您应用的用户体验。为了避免这种情况,您可以使用缓存的 currentUser 对象。

每当您注册成功或是登录成功后,当前用户都会被保留在本地存储设备上。您可以使用以下方法来获取这个缓存的用户对象以判断当前应用是否曾经注册过:

MLUser currentUser = MLUser.getCurrentUser();
if (currentUser != null) {
  if (MLAnonymousUtils.isLink(currentUser)) {
    //  匿名用户
  } else {
    //  普通用户
  }
} else {
  // 未登录
}

注销登录

当然,您也可以使用如下方法清除缓存的用户:

MLUser.logOut();
MLUser currentUser = MLUser.getCurrentUser(); //此时,crrentUser 将 为null

修改密码

如果使用用户名/密码的方式进行登录,或者使用手机号/验证码登录后重置过密码,可以采用以下方式修改当前用户的密码:

MLUserManager.changePasswordInBackground(oldPwd, newPwd, new ChangePasswordCallback() {
    @Override
    public void done(MLException e) {
        if (e == null) {
            showToast("修改成功");
        } else {
            showToast(e.getMessage());
        }
    }
});

注:

  • 修改密码前请确保MLUser.getCurrentUser()不为空,即处于登录状态。

  • 该接口会先验证旧密码是否正确,如果正确才会执行修改密码操作。

重置密码

手机号重置密码

如果用户使用手机号码注册或者已绑定手机号,你也可以通过手机短信来重置密码。

申请获得重置密码的短信验证码

MLUserManager.requestPasswordResetByPhoneNumberInBackground("手机号码", new RequestPasswordResetCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

重置密码

MLUserManager.requestResetPasswordInBackground("手机号码", "验证码", "新密码",
        new ResetPasswordCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

此方法也可用于忘记密码后,通过手机号重置密码。

邮箱重置密码

如果使用邮箱注册或者已绑定邮箱,则可以通过电子邮件的方式进行重置密码:

MLUserManager.requestPasswordResetInBackground(
        "myemail@example.com", new RequestPasswordResetCallback() {
    public void done(MLException e) {
        if (e == null) {
            // 重置密码的邮件已发出
        } else {
        }
    }
});

如果邮箱与用户注册时提供的邮箱匹配,系统将发出密码重置邮件。密码重置流程如下:

  • 用户输入他们的电子邮件,请求重置自己的密码。
  • MaxLeap 向用户提供的邮箱发送一封电子邮件,该邮件提供密码重置链接。
  • 用户根据向导点击重置密码链接,打开一个重置页面,输入一个新的密码。
  • MaxLeap 将用户的密码重置为新输入的密码。

此方法也可用于忘记密码后,通过邮箱重置密码。

刷新当前用户信息

如果用户已经登录,可以通过以下方式来获取服务器上当前用户的最新数据。

MLUser currentUser = MLUser.getCurrentUser();
MLUserManager.fetchInBackground(currentUser, new GetCallback() {
    @Override
    public void done(MLObject mlObject, MLException e) {
        if (null == e) {
            //获取成功
        } else {
            //获取失败
        }
    }
});

获取成功后,currentUser会自动的被更新。

查询用户

出于安全性考虑,MaxLeap 目前不允许对用户进行查询操作。

绑定手机号

在使用用户名和密码登录后手机号码默认是没有验证过的,您可以使用以下方法对手机号码进行绑定。

1.设置手机号

MLUser currentUser = MLUser.getCurrentUser();
currentUser.setMobilePhone(phone);
MLUserManager.saveInBackground(currentUser, new SaveCallback() {
    @Override
    public void done(MLException e) {
        if (e == null) {
            sendSms();
        } else {
            showToast(e.getMessage());
        }
    }
});

注:

  • 只有当手机号码设置成功以后,才能发送短信验证码。因为可能存在手机号被占用。

  • 如果之前已经成功绑定过手机号,此操作执行成功后将会替换为新的手机号,并处于未验证的状态。

2.发送验证码

MLUserManager.requestPhoneVerifyInBackground("手机号码", new RequestPhoneVerifyCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

3.进行验证

MLUserManager.verifyPhoneInBackground("手机号码", "验证码", new VerifyPhoneCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

验证通过后控制台的用户记录的 mobilePhoneVerified 属性会变为 true

绑定邮箱

在使用用户名和密码登录后邮箱默认是没有验证过的,您可以使用以下方法对电子邮箱进行绑定。

1.设置邮箱

MLUser currentUser = MLUser.getCurrentUser();
currentUser.setEmail("邮箱地址");
MLUserManager.saveInBackground(currentUser, new SaveCallback() {
    @Override
    public void done(MLException e) {
        if (e != null) {
            showToast(e.getMessage());
        } else {
             //邮箱设置成功后,请求发送认证邮件
        }
    }
});

2.请求发送认证邮件

MLUserManager.requestEmailVerifyInBackground("邮箱地址", new RequestEmailVerifyCallback() {
    @Override
    public void done(MLException e) {
        if (e != null) {
            showToast(e.getMessage());
        } else {
            showToast("发送成功,请进入邮箱查看");
        }
    }
});

当未收到邮件,可重复调用该接口来发送邮件。

MaxLeap 提供强大的邮箱验证服务,您只需在 控制台 -> 应用设置 -> 邮件设置 -> 打开 验证用户的邮箱。系统便会自动在 MLUser 中添加 emailVerified 字段。并且,当 MLUser 的 email 字段被赋值或者修改, 且emailVerified 字段的值为 false。 MaxLeap 便会自动向用户发送一个链接,用户点击链接后便会将 emailVerified设置为 true

emailVerified字段状态:

  • true - 用户通过点击系统发送的链接验证邮箱成功

  • false - 用户还未验证邮箱,或者验证失败

短信验证服务

只有在登录成功的情况下,Maxleap提供了短信验证服务,执行一些敏感操作(比如支付)时,可以使用短信来验证是否是本人操作。

1.用户点击支付按钮

2.调用接口发送短信验证码,并等待用户输入验证码

MLUserManager.requestSmsCodeInBackground("手机号码", new RequestSmsCodeCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

3.用户收到短信,输入验证码

4.调用接口验证用户输入的验证码是否正确

MLUserManager.verifySmsCodeInBackground("手机号码", "验证码", new VerifySmsCodeCallback() {
    @Override
    public void done(final MLException e) {
        if (e != null) {
            //  发生错误
        } else {
            //  成功请求
        }
    }
});

在控制台中管理用户

_User 表是一个特殊的表,专门存储 MLUser 对象。在控制台 -> 开发中心 -> 云数据中,您会看到一个 _User 表。

第三方登录

为简化用户的注册及登录流程,并且集成 MaxLeap 应用与 Facebook, Twitter 等应用。MaxLeap 提供了第三方登录应用的服务,通过该服务可以将 MLUser 与第三方平台的用户联系起来。。为了减少应用的安装包大小,MaxLeap SDK 将尽可能使用 Web OAuth 认证的方式来实现第三方认证,但是您也可以使用第三方应用的 SDK。

Facebook 登录

为了尽可能减少您的应用的大小,MaxLeap SDK 目前使用 Web 认证的方式登陆 Facebook 账号。Facebook 账号登录后,如果该 Facebook 用户Id并未与任何 MLUser 绑定,MaxLeap 将自动为该用户创建一个账号,并与其绑定。

准备工作

  1. Facebook开发者中心 创建 Facebook应用。点击 My Apps -> Add a New App

  2. 选择 Settings -> Advanced -> Client OAuth Settings -> Valid OAuth redirect URIs 一栏填入您的回调地址,MaxLeap SDK 默认使用 https://www.facebook.com/connect/login_success.html 作为回调地址。

  3. 打开 MaxLeap Console -> App Settings -> User Authentication。勾选 Allow Facebook Authentication. 并将步骤一中获取的 Facebook Application ID 和 App Secret 填写至相应位置。

  4. 在项目的 Application.onCreate() 函数中,于 MaxLeap.initialize(this, APP_ID, API_KEY) 之后,添加如下代码:

    MLFacebookUtils.initialize("YOUR FACEBOOK APP ID", "YOUR FACEBOOK SECRET");
    

修改回调地址

如果您在填写回调地址时没有使用 SDK 提供的默认地址的话,则需要在调用注册之前先修改回调地址。

MLFacebookUtils.setRedirectUrl(redirectUrl);

登录并注册新 MLUser

使用 Facebook 账号登录后,如果该 Facebook 用户Id 并未与任何 MLUser 绑定,MaxLeap将自动为该用户创建一个账号,并与其绑定。如:

MLFacebookUtils.logInInBackground(this, new LogInCallback<MLUser>() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用Facebook账号登录
    } else if (user.isNew()) {
      //用户第一次使用Facebook账号登录,成功注册并绑定user用户
    } else {
      //用户使用Facebook账号登录成功。
    }
  }
});

您也可以在注册时指定所需要申请的 Facebook 权限。有关权限的说明可以参考 Facebook 开发人员指南的 Permission 章节

List<String> permissions = Arrays.asList(
            FacebookProvider.Permissions.User.ABOUT_ME,
            FacebookProvider.Permissions.User.RELATIONSHIPS,
            FacebookProvider.Permissions.User.BIRTHDAY,
            FacebookProvider.Permissions.User.LOCATION);
MLFacebookUtils.logInInBackground(permissions, this, new LogInCallback<MLUser>() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用Facebook账号登录
    } else if (user.isNew()) {
      //用户第一次使用Facebook账号登录,成功注册并绑定user用户
    } else {
      //用户使用Facebook账号登录成功。
    }
  }
});

绑定 MLUser 与 Facebook 账号

您可以通过以下方式,绑定已有的 MLUser 和 Facebook 账号:

if (!MLFacebookUtils.isLinked(user)) {
    MLFacebookUtils.linkInBackground(user, this, new SaveCallback() {
        @Override
        public void done(MLException ex) {
          if (MLFacebookUtils.isLinked(user)) {
            //绑定成功
      }
    }
  });
}

绑定成功后,MaxLeap 将会把该 Facebook 账号的信息更新至该 MLUser中。下次再使用该Facebook 账号登录应用时,MaxLeap 将检测到其已绑定 MLUser,便不会为该 Facebook 账号添加新的 MLUser.

解除绑定

MLFacebookUtils.unlinkInBackground(user, new SaveCallback() {
  @Override
  public void done(MLException ex) {
    if (ex == null) {
      Log.d("MyApp", "The user is no longer associated with their Facebook account.");
    }
  }
});

解除绑定成功后,MaxLeap 将会把该 Facebook 账号的信息从该 MLUser 中移除。下次再使用该 Facebook 账号登录应用时,MaxLeap 将检测到其未绑定 MLUser,便会为该Facebook 账号添加新的 MLUser.

Twitter 登录

为了尽可能减少您的应用的大小,MaxLeap SDK 目前使用 Web 认证的方式登陆 Twitter 账号。使用 Twitter 账号登录后,如果该 Twitter 用户Id 并未与任何 MLUser 绑定,MaxLeap 将自动为该用户创建一个账号,并与其绑定。

准备工作

  1. Twitter开发者中心 创建 Twitter 应用,其中 Callback URL 一项请填写有效地址,MaxLeap SDK 默认使用 http://localhost 作为回调地址。
  2. 打开 MaxLeap Console -> App Settings -> User Authentication.勾选 Allow Twitter Authentication,并将步骤一中获取的 Twitter consumer Key 填写至相应位置。
  3. 在项目的 Application.onCreate() 函数中,于 MaxLeap.initialize(this, APP_ID, API_KEY) 之后,添加如下代码:
MLTwitterUtils.initialize("YOUR Twitter CONSUMER KEY", "YOUR Twitter CONSUMER SECRET");

修改回调地址

如果您在填写回调地址时没有使用 SDK 提供的默认地址的话,则需要在调用注册之前先修改回调地址。

MLTwitterUtils.setRedirectUrl(redirectUrl);

登录并注册新 MLUser

使用 Twitter 账号登录后,如果该 Twitter 用户Id 并未与任何 MLUser 绑定,MaxLeap 将自动为该用户创建一个账号,并与其绑定。如:

MLTwitterUtils.logInInBackground(this, new LogInCallback<MLUser>() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用Twitter账号登录
    } else if (user.isNew()) {
      //用户第一次使用Twitter账号登录,成功注册并绑定user用户
    } else {
      //用户使用Twitter账号登录成功。
    }
  }
});

绑定 MLUser 与 Twitter 账号

您可以通过以下方式,绑定已有的 MLUser 账号和 Twitter 账号:

if (!MLTwitterUtils.isLinked(user)) {
    MLTwitterUtils.linkInBackground(user, this, new SaveCallback() {
        @Override
        public void done(MLException ex) {
          if (MLTwitterUtils.isLinked(user)) {
            //绑定成功
      }
    }
  });
}

绑定成功后,MaxLeap 将会把该 Twitter 账号的信息更新至该 MLUser 中。下次再使用该Twitter 账号登录应用时,MaxLeap 将检测到其已绑定 MLUser,便不会为该 Twitter账号添加新的 MLUser.

解除绑定

MLTwitterUtils.unlinkInBackground(user, new SaveCallback() {
  @Override
  public void done(MLException ex) {
    if (ex == null) {
      Log.d("MyApp", "The user is no longer associated with their Twitter account.");
    }
  }
});

解除绑定成功后,MaxLeap 将会把该 Twitter 账号的信息从该 MLUser 中移除。下次再使用该 Twitter 账号登录应用时,MaxLeap 将检测到其未绑定 MLUser,便会为该 Twitter 账号添加新的 MLUser。

微信登录

由于微信开发平台只支持 SSO 登录,所以在使用 MaxLeap SDK 登录微信时必须先导入微信的 SDK 才能正常使用. 微信账号登录后,如果该 微信 用户Id并未与任何 MLUser 绑定,MaxLeap 将自动为该用户创建一个账号,并与其绑定。

准备工作

  1. 微信开放平台 创建微信应用.注意按照微信官方的指导务必填写正确的应用签名并且通过微信的开发者认证.

  2. 打开 MaxLeap Console -> 应用设置 -> 用户验证。勾选 “允许使用微信登录"。

  3. 在项目的 Application.onCreate() 函数中,于 MaxLeap.initialize(this, APP_ID, API_KEY) 之后,添加如下代码:

    MLWechatUtils.initialize("YOUR WECHAT APP ID", "YOUR WECHAT SECRET");
    

登录并注册新 MLUser

使用 微信 账号登录后,如果该 微信 用户Id 并未与任何 MLUser 绑定,MaxLeap将自动为该用户创建一个账号,并与其绑定。如:

MLWechatUtils.logInInBackground(this, new LogInCallback<MLUser>() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用微信账号登录
    } else if (user.isNew()) {
      //用户第一次使用微信账号登录,成功注册并绑定user用户
    } else {
      //用户使用微信账号登录成功。
    }
  }
});

绑定 MLUser 与微信 账号

您可以通过以下方式,绑定已有的 MLUser 和 微信 账号:

if (!MLWechatUtils.isLinked(user)) {
    MLWechatUtils.linkInBackground(user, this, new SaveCallback() {
        @Override
        public void done(MLException ex) {
          if (MLWechatUtils.isLinked(user)) {
            //绑定成功
      }
    }
  });
}

绑定成功后,MaxLeap 将会把该 微信 账号的信息更新至该 MLUser中。下次再使用该微信 账号登录应用时,MaxLeap 将检测到其已绑定 MLUser,便不会为该 微博 账号添加新的 MLUser.

解除绑定

MLWechatUtils.unlinkInBackground(user, new SaveCallback() {
  @Override
  public void done(MLException ex) {
    if (ex == null) {
      Log.d("MyApp", "The user is no longer associated with their Wechat account.");
    }
  }
});

解除绑定成功后,MaxLeap 将会把该 微信 账号的信息从该 MLUser 中移除。下次再使用该 微信 账号登录应用时,MaxLeap 将检测到其未绑定 MLUser,便会为该微信账号添加新的 MLUser.

QQ 登录

使用 MaxLeap SDK 登录QQ时必须先导入QQ的 SDK 才能正常使用. QQ账号登录后,如果该 QQ 用户Id并未与任何 MLUser 绑定,MaxLeap 将自动为该用户创建一个账号,并与其绑定。

准备工作

  1. 腾讯开放平台 创建移动应用。

  2. 打开 MaxLeap Console -> 应用设置 -> 用户验证。勾选 "允许使用QQ登录"。

  3. 在项目的 Application.onCreate() 函数中,于 MaxLeap.initialize(this, APP_ID, API_KEY) 之后,添加如下代码:

    MLQQUtils.initialize("YOUR QQ APP ID");
    

登录并注册新 MLUser

使用 QQ 账号登录后,如果该 QQ 用户Id 并未与任何 MLUser 绑定,MaxLeap将自动为该用户创建一个账号,并与其绑定。如:

MLQQUtils.logInInBackground(this, new LogInCallback() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用QQ账号登录
    } else if (user.isNew()) {
      //用户第一次使用QQ账号登录,成功注册并绑定user用户
    } else {
      //用户使用QQ账号登录成功
    }
  }
});

绑定 MLUser 与QQ 账号

您可以通过以下方式,绑定已有的 MLUser 和 QQ 账号:

if (!MLQQUtils.isLinked(user)) {
    MLQQUtils.linkInBackground(user, this, new SaveCallback() {
        @Override
        public void done(MLException ex) {
          if (MLQQUtils.isLinked(user)) {
            //绑定成功
      }
    }
  });
}

绑定成功后,MaxLeap 将会把该 QQ 账号的信息更新至该 MLUser中。下次再使用该QQ 账号登录应用时,MaxLeap 将检测到其已绑定 MLUser,便不会为该 QQ 账号添加新的 MLUser.

解除绑定

MLQQUtils.unlinkInBackground(user, new SaveCallback() {
  @Override
  public void done(MLException ex) {
    if (ex == null) {
      Log.d("MyApp", "The user is no longer associated with their QQ account.");
    }
  }
});

解除绑定成功后,MaxLeap 将会把该 QQ 账号的信息从该 MLUser 中移除。下次再使用该 QQ 账号登录应用时,MaxLeap 将检测到其未绑定 MLUser,便会为该微博 账号添加新的 MLUser.

新浪微博登录

为了尽可能减少您的应用的大小,MaxLeap SDK 目前 默认使用 Web 认证的方式登陆微博账号,但是如果你在工程中引入了微博 SDK后.MaxLeap SDK 会自动调用微博的接口来完成认证. 微博账号登录后,如果该 微博 用户Id并未与任何 MLUser 绑定,MaxLeap 将自动为该用户创建一个账号,并与其绑定。

准备工作

  1. 微博开放平台 创建微博应用。

  2. 选择你的应用,点击 应用信息 -> 高级信息,填写授权回调页. MaxLeap SDK 默认使用 https://api.weibo.com/oauth2/default.html 作为回调地址。

  3. 打开 MaxLeap Console -> 应用设置 -> 用户验证。勾选 "允许使用新浪微博登录"。

  4. 在项目的 Application.onCreate() 函数中,于 MaxLeap.initialize(this, APP_ID, API_KEY) 之后,添加如下代码:

    MLWeiboUtils.initialize("YOUR WEIBO APP ID", "YOUR WEIBO SECRET");
    

修改回调地址

如果您在填写回调地址时没有使用 SDK 提供的默认地址的话,则需要在调用注册之前先修改回调地址。

MLWeiboUtils.setRedirectUrl(redirectUrl);

登录并注册新 MLUser

使用 微博 账号登录后,如果该 微博 用户Id 并未与任何 MLUser 绑定,MaxLeap将自动为该用户创建一个账号,并与其绑定。如:

MLWeiboUtils.logInInBackground(this, new LogInCallback<MLUser>() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用微博账号登录
    } else if (user.isNew()) {
      //用户第一次使用微博账号登录,成功注册并绑定user用户
    } else {
      //用户使用微博账号登录成功。
    }
  }
});

您也可以在注册时指定所需要申请的 scope 权限。有关权限的说明可以参考 scope 说明

List<String> scopes = Arrays.asList(
            WeiboProvider.Scope.EMAIL, 
            WeiboProvider.Scope.DIRECT_MESSAGES_READ);
MLWeiboUtils.logInInBackground(scopes, this, new LogInCallback<MLUser>() {
  @Override
  public void done(MLUser user, MLException err) {
    if (user == null) {
      //用户取消了使用微博账号登录
    } else if (user.isNew()) {
      //用户第一次使用微博账号登录,成功注册并绑定user用户
    } else {
      //用户使用微博账号登录成功。
    }
  }
});

绑定 MLUser 与微博 账号

您可以通过以下方式,绑定已有的 MLUser 和 微博 账号:

if (!MLWeiboUtils.isLinked(user)) {
    MLWeiboUtils.linkInBackground(user, this, new SaveCallback() {
        @Override
        public void done(MLException ex) {
          if (MLWeiboUtils.isLinked(user)) {
            //绑定成功
      }
    }
  });
}

绑定成功后,MaxLeap 将会把该 微博 账号的信息更新至该 MLUser中。下次再使用该微博 账号登录应用时,MaxLeap 将检测到其已绑定 MLUser,便不会为该 微博 账号添加新的 MLUser.

解除绑定

MLWeiboUtils.unlinkInBackground(user, new SaveCallback() {
  @Override
  public void done(MLException ex) {
    if (ex == null) {
      Log.d("MyApp", "The user is no longer associated with their Weibo account.");
    }
  }
});

解除绑定成功后,MaxLeap 将会把该 微博 账号的信息从该 MLUser 中移除。下次再使用该 微博 账号登录应用时,MaxLeap 将检测到其未绑定 MLUser,便会为该微博 账号添加新的 MLUser.

FAQ

Q: 用户每次都请求短信验证码来登录的话,成本太高,有什么解决办法吗?

A: 让用户设置密码,之后用户就可以使用 手机号/密码 方式登录了。详见手机号重置密码

Q:为什么无法请求手机验证码?

A:除了手机号验证码注册手机号验证码登录以及手机号重置密码不需要在登录状态使用。其余的绑定手机号短信验证服务均需要用户处于登录状态。

A:账户相关的其他问题?

Q:可以参考我们账户相关的开源组件https://github.com/MaxLeap/Module-MaxLogin-Android

云代码

简介

什么是云代码服务

云代码是部署运行在 MaxLeap 云引擎上的代码,您可以用它来实现较复杂的,需要运行在云端的业务逻辑。它类似于传统的运行在 Web server上的 Web Service或 RESTful API。它对外提供的接口也是 RESTful API,也正是以这种方式被移动应用调用。

目前服务器端支持语言包括 Java,其他语言尽请期待。

准备

如果您尚未安装 SDK,请按第二章步骤,集成 SDK。

需要开发云代码,实现所需的接口和 HOOK,开发以及发布过程请根据您的需求选择对应服务端语言

Java 开发指南

云代码调用

只需要使用 MLCloudManager.callFunctionInBackground() 方法就可以调用部署在云端的代码,该方法接收两个参数,第一个参数为方法名,第二个参数为方法的参数列表(参数名:参数值)。

MLCloudManager.callFunctionInBackground("hello", new HashMap<String, Object>(),
    new FunctionCallback<JSONObject>() {
        @Override
        public void done(JSONObject result,
                LASException exception) {
            if (e == null) {
                // when success
            } else {
                // something went wrong
            }
        }
    });

在线参数

简介

什么是在线参数

每个应用在云端都有一个对应的MLCloudConfig对象,用以存储该应用的参数。Cloud Config 服务帮助您访问和操作云端参数。您可以通过 Console 在 MaxLeap 中配置应用参数,并且使用iOS/Android SDK读取云端的参数。

SDK 集成

请按第二章【SDK 集成】步骤完成 SDK 集成。

在线参数中添加参数

您可以通过 Console 向 Cloud Config 中增添应用参数。新建云端参数时,您需要指定该参数的以下属性:

属性名
Parameter参数名
Type参数类型
Value参数的值

您还可以为不同的 Segment 设置不同的参数值。

获取 MLCloudConfig 对象

您可以通过MLCloudConfig.getCurrentCloudConfig()方法获取最新的 Cloud Config.

MLCloudConfig currentConfig = MLCloudConfig.getCurrentCloudConfig();

获取 MLCloudConfig 中的参数值

在获取一个云端参数的值时,您需要知道该参数的值类型,然后通过对 MLCloudConfig 实例调用getType()方法获取对应参数的值。该方法需要传入两个参数:第一个为云端参数名,第二个为默认值。当云端不存在响应的参数时,系统将会把默认值赋值给该参数。

currentConfig.getString(key, defaultValue)

类似地:

currentConfig.getInt(key, defaultValue)
currentConfig.getList(key, defaultValue)
currentConfig.getBoolean(key, defaultValue)
currentConfig.getDate(key, defaultValue)

更新在线参数

在每次 App 进入前台时,SDK 会自动更新上述方法获取的 currentConfig. 您也可以调用以下代码手动刷新所有云参数:

您可以通过MLCloudConfigManager.getInBackground()获取MLCloudConfig对象,然后调用currentConfig.getInt()更新参数的值。该方法包含两个参数:第一个为云端参数名,第二个为默认值。

MLCloudConfigManager.getInBackground(new ConfigCallback() {
    @Override
    public void done(MLCloudConfig cloudConfig, MLException exception) {
        if (exception == null) {
            int y = currentConfig.getInt("y", 100);
        } else{}
    }
});

监听

为参数添加跟踪后,系统将在 Activity 开始或继续时,遍历所有被跟踪的云端参数是否有更新,若存在更新,则会执行相应的逻辑。添加跟踪之前,您需要在 Activity 的onResume()函数中添加如下代码,以确保参数与云端同步:

@Override
protected void onResume() {
    super.onResume();
    MLCloudConfigManager.refereshKeysInBackground();
}

添加监听

您可以通过MLCloudConfigManager.observeKeyInBackground()跟踪云端参数的变化,并且及时获取新的参数值。该函数包含两个参数:第一个为云端参数名,第二个为ValueChangedCallback回调类实例。

MLCloudConfigManager.observeKeyInBackground("keyX", new ValueChangedCallback() {
    @Override
    public void done(MLCloudConfig cloudConfig) {
       int newKeyX = cloudConfig.get("keyX", null);
    }
});

注意,一个云端参数支持多个跟踪。

移除监听

移除的办法很简单,您只需添加如下代码:

MLCloudConfigManager.removeObserver("keyX",  previousValueChangedCallback);

移除一个云端参数的所有跟踪:

MLCloudConfigManager.removeObserver("x");

移除所有参数的所有跟踪:

MLCloudConfigManager.removeAllObservers();

在线参数值类型

MaxLeap 支持绝大多数 MLObject支持的值类型:

  • String
  • Number
  • Date
  • Array
  • Boolean
  • Object

移动支付

简介

目前支持支付宝、微信、银联支付等渠道,支持支付及查询订单功能。我们将持续更新,支持更多支付平台和更多功能,敬请期待。

使用

配置支付管理信息

  1. 在各支付平台创建进行支付的应用

  2. 打开 MaxLeap 控制台 -> 支付管理 -> 渠道配置, 配置上一步获得的各平台的相关支付信息

添加项目依赖

请按第二章【SDK 集成】步骤完成 SDK 下载,

解压后将以下 Jar 包导入工程的 libs 目录下:

  • 核心 Jar 包: maxleap-sdk-core-xxx.jar
  • 支付 Jar 包: maxleap-sdk-pay-xxx.jar

支付宝和微信平台的 SDK

银联 SDK 1. 下载 手机控件支付开发包安卓版 2. 解压后依次进行以下目录 “手机控件支付产品入网材料” -> “手机控件支付产品技术开发包” -> “开发包” -> “app开发包” -> “控件开发包” -> “upmp_android” -> “sdkPro” 3. 将 UPPayAssistEx.jarjar/UPPayPluginExPro.jar 放入 libs 目录下。 4. 将 data.bin 放入 assets 目录下。 5. 将 arm64-v8a,mips,x86 等含有 *.so 文件的目录放入 jniLibs 目录下。

配置应用权限

AndroidManifest.xml 中添加 permission

    <!--共通-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <!--银联-->
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.NFC" />

AndroidManifest.xml 中注册 activity

    <!--支付宝-->
    <activity
            android:name="com.alipay.sdk.app.H5PayActivity"
            android:configChanges="orientation|keyboardHidden|navigation|screenSize"
            android:exported="false"
            android:screenOrientation="behind"
            android:windowSoftInputMode="adjustResize|stateHidden">
    </activity>

    <!--银联-->
    <activity android:name="com.maxleap.MLUnionPaymentActivity"
                      android:configChanges="orientation|keyboardHidden"
                      android:excludeFromRecents="true"
                      android:launchMode="singleTop"
                      android:screenOrientation="portrait"/>
    <activity
            android:name="com.unionpay.uppay.PayActivity"
            android:configChanges="orientation|keyboardHidden"
            android:excludeFromRecents="true"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustResize" />

初始化平台

  • 支付宝,银联不需要初始化

  • 微信

    MLPayManager.initializeWechatPay(context, "your wechat appId");

处理回调

  • 支付宝和银联不需要

  • 微信

    第一种

    由于微信的回调机制,在支付完成后会调用:包名+.wxapi.WXPayEntryActivity,所以需要自己在工程中实现WXPayEntryActivity类 在 onCreate() 方法中调用以下方法:

    MLPayManager.onCreate(getIntent());

    onNewIntent 方法中调用以下方法:

    setIntent(intent); MLPayManager.onNewIntent(intent);

    示例如下:

    public class WXPayEntryActivity extends Activity {
        @Override
        protected void onCreate(final Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            MLPayManager.onCreate(getIntent());
            finish();
        }

        @Override
        protected void onNewIntent(Intent intent) {
            super.onNewIntent(intent);
            setIntent(intent);
            MLPayManager.onNewIntent(intent);
        }
    }

并在AndroidManifest.xml中相应的进行配置:

  • 如果在Gradle.build中您修改过applicationId,即applicationIdAndroidManifest.xmlpackage不一致,您需要如下设置
    <activity
        android:name=".wxapi.WXPayEntryActivity"
        android:launchMode="singleTop" />

    <activity-alias
        android:name="${applicationId}.wxapi.WXPayEntryActivity"
        android:enabled="true"
        android:exported="true"
        android:targetActivity=".wxapi.WXPayEntryActivity" />
  • 反之,如果一致。您需要如下配置
    <activity
        android:name=".wxapi.WXPayEntryActivity"
        android:enabled="true"
        android:launchMode="singleTop" />

第二种

由于在maxleap-sdk-pay-xxx.jar中已经对WXPayEntryActivity回调做了处理,您只需要在 AndroidManifest.xml 中加入以下内容

    <activity android:name="com.maxleap.MLWechatPayEntryActivity"
              android:launchMode="singleTop"/>
    <activity-alias android:name="${applicationId}.wxapi.WXPayEntryActivity"
                    android:targetActivity="com.maxleap.MLWechatPayEntryActivity"
                    android:enabled="true"
                    android:exported="true"/>

进行支付

    MLPayParam payParam = new MLPayParam();
    payParam.setChannel(MLPayParam.Channel.ALIPAY_APP);
    payParam.setSubject("a toy");
    payParam.setBillNum("" + System.currentTimeMillis());
    payParam.setTotalFee(1);
    MLPayManager.doPayInBackground(MainActivity.this, payParam,
        new PayCallback() {
                @Override
                public void done(String id, MLException e) {
                    if (e != null) {
                        Log.e(TAG, "支付失败,错误信息为 " + e.getMessage());
                        return;
                    }
                    Log.i(TAG, "完成支付,订单号为 " + id);
                }
            });

参数依次为

  • activity : Activity 调用支付的 Android Activity
  • payParam : MLPayParam 调用支付的相关参数
  • payCallback : PayCallback 支付完成后的回调

MLPayParam.Channel 表示支付渠道,目前支持两种

  • ALIPAY_APP 支付宝
  • WECHAT_APP 微信

如果应用只集成了一个第三方平台的话可以省略渠道参数,SDK 会自动根据应用当前集成的第三方平台的情况自动调用对应的支付请求。

注意

使用银联支付时控制台会打印 java.lang.ClassNotFoundException:org.simalliance.openmobileapi.SEService 之类的异常信息,此时无视即可。

订单查询

SDK 仅支持单笔账单的查询

    MLPayManager.queryOrderInBackground(billNum, new QueryOrderCallback() {
        @Override
        public void done(List<MLOrder> orders, MLException e) {
            if (e != null) {
                Log.e(TAG, "查询失败,错误信息为 " + e.getMessage());
                return;
            }
            Log.i(TAG, "完成查询, 订单数量为 " + orders.size());
            Log.i(TAG, "订单信息为 " + Arrays.toString(orders.toArray()));
        }
    });

参数依次为

  • billNum : String 订单流水号
  • queryOrderCallback : QueryOrderCallback 查询完成后的回调

获得订单对象 MLOrder 的实例后可以调用 getTotalFee()getBillNum() 等方法获得对应的订单信息。

需要注意的是,以上方法由于没有传入渠道参数,所以如果没有保证所有平台上的订单流水号的唯一性的话可能会获得多条记录。 除了以上方法外还存在可以传入渠道的版本 queryOrderInBackground(channel, billNum, queryOrderCallback)

即时通讯

简介

即时通讯服务是 MaxLeap 研发组件中的重要基础组件,可以为开发者提供实时聊天、群聊、好友关系管理等功能,支持语音、图片等附件信息。

MaxIMLib 是不含界面的基础 IM 通讯能力库,封装了通信能力和会话、消息等对象。引用到 App 工程中后,需要开发者自己实现 UI 界面,相对较轻量,适用于对 UI 有较高订制需求的开发者。

安装

  1. 安装 SDK

    请按第二章【SDK 集成】步骤,完成集成。

    下载解压后将 maxleap-sdk-im-xxx.jar 包导入工程的 libs 目录下。

  2. 配置权限

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
  3. 添加依赖

    compile('io.socket:socket.io-client:0.6.3') {
        exclude group: 'org.json', module: 'json'
    }
    compile "com.squareup.okhttp3:okhttp:3.1.2"
    

MLParrot

获得 MLParrot 实例

MLParrot 是 IMLib 的管理类,所有的相关方法都可以通过 MLParrot 的实例进行调用。MLParrot 有以下两种方式可以用于获得实例:

第一种方式,使用这种方式所有回调都会发生在 UI 线程上。通常情况下应该总是使用这种方式。

MLParrot parrot = MLParrot.getInstance();

第二种方式,使用这种方式所有回调都会发生在自定义的 Handler 所对应的线程上。

MLParrot parrot = MLParrot.getInstance(handler);

MLParrot 的方法

MLParrot 提供的方法主要有两种:

  1. onXXX()offXXX() 开头的基于 Socket 的方法以及其它基于 Http 请求的方法。在使用基于 Socket 的方法时请注意在调用 onXXX() 开启功能后及时调用 offXXX() 进行关闭。(例: 在 Activity 的 onResume() 时调用 onMessage() 以便在前台时获得实时消息,在 onPause() 时调用 offMessage() 在后台时关闭实时消息的获取。)

  2. MLParrot 同时支持 Fluent API,所以你可以使用这样的方法进行链式调用:parront.methodA().methodB().methodC()

登录

IMLib 支持两种方式进行登录,用户自定的账户系统或者基于 MaxLeap 系统的 MLUser。

在调用具体的登录方法前需要先指定使用哪种方式进行登录。

使用用户已有账号系统

1、作为普通用户登录

parrot.initWithCustomAccount("your application id", "your api key", clientId, installId);

其中 installId 主要用于推送,如果不需要推送的话可以直接传 null

2、作为游客登录

parrot.initWithGuest("your application id", "your api key", clientId, jsonExtras);

其中 clientId 可以为空,此时系统会自动分配 ID,如果指定的话则强制使用指定的 ID 作为游客的身份识别。最后一个参数为 JSON 形式的对象,代表登录游客的自定义属性,可以为空。

使用 MaxLeap 账号系统

1、使用用户名和密码验证登录

parrot.initWithMLUser("your application id", "your api key",  username, password);

以上传入的参数为已经注册过的 MLUser 的用户名和密码。

2、使用手机号和短信验证码登录

先使用 MaxLeap 核心库获得手机验证码

MLUserManager.requestLoginSmsCodeInBackground("your phone number", new RequestSmsCodeCallback() {
    @Override
    public void done(MLException e) {
    }
});

然后再进行初始化操作

parrot.initWithPhoneNumber("your application id", "your api key",  phoneNumber, verifyCode);

3、使用第三方平台认证信息登录

parrotAndroid.initWithAuth(App.APPLICATION_ID, App.API_KEY, authDataJSON);

以上 authDataJSON 是 MaxLeap 系统的 MLUser 对应的 authData

创建连接

指定好登录的方式后就可以通过以下方式建立连接,如果使用自定义的账户系统则登录成功返回的就是传入的 clientId,如果使用 MLUser 则返回的是该 MLUserobjectId

parrot.login(new DataHandler<String>() {
    @Override
    public void onSuccess(String id) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

登录成功后就可以进行后续的操作了。

消息

IMLibMessage 代表消息。一个 Message 通常有两部分组成: 代表消息来源或目标的 MessageSource,代表消息内容的 MessageContent

Message 可以通过构造方法创建,但更好的方式是使用 MessageBuilder 来创建。 MessageSource 可以用于判断消息来自好友还是群组还是聊天室。 MessageContent 用于构建消息的内容和类型,目前共有四种类型的消息:text, image, audio, video

创建一个消息对象

Message msg = MessageBuilder.newBuilder()
                .to(targetFriend)
                .text(message);

单聊

单聊即单个用户之间的点对点聊天系统。单聊的前提是需要先将对方添加为好友,可以查找好友关系管理一节。

发送消息

Message msg = MessageBuilder.newBuilder()
                .to(targetFriend)   // 目标对象的 ClientId
                .text(message);
parrot.sendMessage(msg, new SimpleDataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }
});

接收消息

parrot.onMessage(new SimpleDataHandler<Message>() {
    @Override
    public void onSuccess(Message message) {
        if (message.getFrom().fromFriend()) {
            //  do something
        }
    }
});

接收到消息后可以通过 message.getType() 获得消息的类型以判断消息来自哪里。消息一共有以下三种类型:MessageSource.TYPE_FRIEND, MessageSource.TYPE_GROUP, MessageSource.TYPE_ROOM。也可以通过 fromFriend(), fromGroup(), fromRoom() 进行判断。

群组

发送消息

与单聊基本一样,只是创建 Message 时使用的是 toGroup()

Message msg = MessageBuilder.newBuilder()
                .toGroup(targetGroup)   // 目标的 Group 的 Id
                .text(message);
parrot.sendMessage(msg, new SimpleDataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }
});

接收消息

与单聊基本一样,只是接收时的判断条件变成了 fromGroup()

parrot.onMessage(new SimpleDataHandler<Message>() {
    @Override
    public void onSuccess(Message message) {
        if (message.getFrom().fromGroup()) {
            //  do something
        }
    }
});

聊天室

聊天室很像群组,但是聊天室没有离线消息也没有历史记录。

发送消息

与单聊基本一样,只是创建 Message 时使用的是 toRoom()

Message msg = MessageBuilder.newBuilder()
                .toRoom(targetRoom) // 目标的 Room 的 Id
                .text(message);
parrot.sendMessage(msg, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        toastP("sendMessage() success");
    }

    @Override
    public void onError(ParrotException e) {
        toastE("sendMessage()", e);
    }
});

接收消息

与单聊基本一样,只是接收时的判断条件变成了 fromRoom()

parrot.onMessage(new SimpleDataHandler<Message>() {
    @Override
    public void onSuccess(Message message) {
        if (message.getFrom().fromRoom()) {
            //  do something
        }
    }
});

系统消息

发送系统消息

parrot.sendSystemMessage(targetFriend, message, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

接收系统消息

parrot.onSystemMessage(new SimpleDataHandler<Message>() {
    @Override
    public void onSuccess(Message message) {
    }
});

在线状态管理

监控上线事件

parrot.onFriendOnline(new SimpleDataHandler<String>() {
    @Override
    public void onSuccess(String friend) {
    }
})

监控下线事件

parrot.onFriendOffline(new SimpleDataHandler<String>() {
    @Override
    public void onSuccess(String friend) {
    }
})

好友关系管理

添加好友

在发送消息前你需要先添加对方为朋友,此时可以调用以下方法:

parrot.addFriend("friend's client id", new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

删除好友

parrot.removeFriend("friend's client id", new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获得指定好友的信息

可以用于查询指定好友现在是否在线

parrot.getFriendInfo("friend's client id", new DataHandler<FriendInfo>() {
    @Override
    public void onSuccess(FriendInfo friendInfo) {
        tvStatus.setText(friendInfo.isOnline() ? "Online" : "Offline");
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获取所有好友

可以用于查询所有好友的在线状态

parrot.listFriends(new DataListHandler<Friend>() {
    @Override
    public void onSuccess(List<Friend> friends) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

以上方法返回的是 Friend 对象的列表。Friend 有两个属性:id 代表着该好友的 client idonline 代表着该好友当前是否在线。

群组关系管理

获得所有群组

parrot.listGroups(new DataListHandler<Group>() {
    @Override
    public void onSuccess(List<Group> t) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获得指定群组的信息

parrot.getGroupInfo("group id", new DataHandler<Group>() {
    @Override
    public void onSuccess(Group group) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

创建群组

parrot.createGroup("group name",
    Arrays.asList("member client id 1", "member client id 2"),
    new DataHandler<Group>() {
        @Override
        public void onSuccess(Group group) {
        }

        @Override
        public void onError(ParrotException e) {
        }

    });

删除群组

parrot.deleteGroup("group id", new DataHandler<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
        }

        @Override
        public void onError(ParrotException e) {
        }
    });

更新群组

Group group = new Group();
group.setId("group id");
group.setOwner("owner");
group.setName("group name");
group.setMembers(Arrays.asList("member client id 1", "member client id 2"));
parrot.updateGroup(group, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

添加成员

parrot.addGroupMembers("group id", newMembers, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

移除成员

parrot.removeGroupMembers("group id", removedMembers, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

聊天室关系管理

获得所有聊天室

parrot.listRooms(new DataListHandler<Room>() {
    @Override
    public void onSuccess(List<Room> room) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获得指定聊天室的信息

parrot.getRoomInfo("room id", new DataHandler<Room>() {
    @Override
    public void onSuccess(Room room) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

创建聊天室

parrot.createRoom("room name",
    Arrays.asList("member client id 1", "member client id 2"),
    new DataHandler<Room>() {
        @Override
        public void onSuccess(Room room) {
        }

        @Override
        public void onError(ParrotException e) {
        }

    });

删除聊天室

parrot.deleteRoom("room id", new DataHandler<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
        }

        @Override
        public void onError(ParrotException e) {
        }
    });

更新聊天室

parrot.updateRoom(room, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

添加成员

parrot.addRoomMembers("room id", newMembers, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

移除成员

parrot.removeRoomMembers("group id", removedMembers, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

自定义属性

用户,群组和聊天室在使用时可以任意设置自定义属性。自定义的属性本身是一个普通的 JSONObject 对象。

用户

创建或更新自定义属性

parrot.saveOrUpdateUserExtras(jsonExtras, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

创建自定义属性(会覆盖原来设置的所有自定义属性)

parrot.saveUserExtras(jsonExtras, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获取自定义属性

parrot.getUserExtras(new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

删除自定义属性

parrot.deleteUserExtras(new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

群组

创建或更新自定义属性

parrot.saveOrUpdateGroupExtras(groupId, jsonExtras, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

创建自定义属性(会覆盖原来设置的所有自定义属性)

parrot.saveGroupExtras(groupId, jsonExtras, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获取自定义属性

parrot.getGroupExtras(groupId, new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

删除自定义属性

parrot.deleteGroupExtras(groupId, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

聊天室

创建或更新自定义属性

parrot.saveOrUpdateRoomExtras(roomId, jsonExtras, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

创建自定义属性(会覆盖原来设置的所有自定义属性)

parrot.saveRoomExtras(roomId, jsonExtras, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

获取自定义属性

parrot.getRoomExtras(roomId, new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

删除自定义属性

parrot.deleteRoomExtras(roomId, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

游客管理

创建游客

游客在创建时可用直接指定 ID 和自定义属性,两者都是可选项。如果指定了游客 ID 则系统会以 ID 为准创建或更新游客,如果没有指定 ID 则系统会自行分配 ID 并返回。

parrot.createOrUpdateGuest("guest id", jsonExtras, new DataHandler<String>() {
    @Override
    public void onSuccess(String id) {

    }

    @Override
    public void onError(ParrotException e) {

    }
});

获得游客信息

parrot.getGuestInfo("guest id", new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {

    }

    @Override
    public void onError(ParrotException e) {

    }
});

获取游客历史消息

parrot.recentGuestMessages("guest id", ts, limit, new DataListHandler<MessageHistory>() {
    @Override
    public void onSuccess(List<MessageHistory> t) {

    }

    @Override
    public void onError(ParrotException e) {

    }
});

搜索支持

搜索功能主要有 4 个参数,query, sort, skip 和 limit。 其中 skip 和 limit 为 int 类型数值,主要用于分页。 sort 用于进行排序,值为需要使用的属性的字符串序列。单独指定属性名表示顺序,在属性名前加上 - 表示逆序,多个条件之间使用 , 分隔(如:-size,-ts 表示先按 size 逆序再按 ts 逆序)。 query 表示查询条件,主要用于查询自定义属性,值为普通的 Map 类型(如 foo:bar 表示查询拥有自定义属性为 foo 且 foo 值为 bar 的对象)。

搜索用户

Map<String, String> query = new HashMap<String, String>();
query.put("foo", "bar");
String sort = "-size,-ts";
int skip = 0;
int limit = 10;
parrot.searchUsers(query, sort, skip, limit, new DataListHandler<User>() {
    @Override
    public void onSuccess(List<User> users) {

    }

    @Override
    public void onError(ParrotException e) {

    }
});

搜索群组

Map<String, String> query = new HashMap<String, String>();
query.put("foo", "bar");
String sort = "-size,-ts";
int skip = 0;
int limit = 10;
parrot.searchGroups(query, sort, skip, limit, new DataListHandler<Group>() {
    @Override
    public void onSuccess(List<Group> groups) {

    }

    @Override
    public void onError(ParrotException e) {

    }
});

搜索聊天室

Map<String, String> query = new HashMap<String, String>();
query.put("foo", "bar");
String sort = "-size,-ts";
int skip = 0;
int limit = 10;
parrot.searchRooms(query, sort, skip, limit, new DataListHandler<Room>() {
    @Override
    public void onSuccess(List<Room> rooms) {

    }

    @Override
    public void onError(ParrotException e) {

    }
});

多媒体支持

除了基本的文字聊天,IMLib 也支持多媒体聊天。在使用多媒体聊天时你需要先调用上传接口将多媒体文件上传到服务器上,为了聊天的实时性,请先在上传前严格控制文件的大小。

上传文件

parrot.uploadFile(sd.getFile(), new DataHandler<String>() {
    @Override
    public void onSuccess(String url) {
        toastP("success");
    }

    @Override
    public void onError(ParrotException e) {
        toastE("Upload File", e);
    }
});

以上方法返回的是上传的文件的路径。

上传完成后可以调用以下方法构建多媒体消息,之后可以像之前一样调用 sendMessage() 方法发送消息。

Message msg = MessageBuilder.newBuilder()
            .to(targetFriend)
            .image(url);    // 上传后返回的路径

除了图片,消息也支持音频,视频文件,只需要将 image() 替换成 audio()video() 即可。

多设备同步

IMLib 支持多设备同步,即你在 A 设备发送的消息在 B 设备也能同样受到。为了实现此功能,你需要调用以下方法:

parrot.onSelfMessage(new DataHandler<Message>() {
    @Override
    public void onSuccess(Message message) {
        toastP("onSelfMessage() success");
    }

    @Override
    public void onError(ParrotException e) {
        toastE("onSelfMessage()", e);
    }
});

聊天记录

获得与指定好友的聊天记录

获得的聊天记录按时间顺序从小到大排列

parrot.recentMessages("friend's client id",
    timestamp,
    limit,
    new DataListHandler<MessageHistory>() {
        @Override
        public void onSuccess(List<MessageHistory> messageHistory) {
        }

        @Override
        public void onError(ParrotException e) {
        }
    });

获得指定群组的聊天记录

获得的聊天记录按时间顺序从小到大排列

parrot.recent GroupMessages("group id",
    timestamp,
    limit,
    new DataListHandler<MessageHistory>() {
        @Override
        public void onSuccess(List<MessageHistory> messageHistory) {
        }

        @Override
        public void onError(ParrotException e) {
        }
    });

离线推送

离线推送需要依赖于 maxleap-core-xxx.jar 包,具体配置步骤可以参见 Marketing 的 LPNS 章节。

登出与释放资源

登出

如果继承了离线推送功能后,应用退出后就能够接受到推送的离线消息。如果不希望接受到推送的消息则需要使用登出功能。

parrot.logOut(installId, new DataHandler<Void>() {
   @Override
    public void onSuccess(List<Void> aVoid) {
    }

    @Override
    public void onError(ParrotException e) {
    }
});

释放资源

当用户退出应用后,应确保调用以下方法释放资源。

@Override
protected void onDestroy() {
    super.onDestroy();
    MLParrot.getInstance().destroy();
}

以上方法会关闭 Socket 连接并停止内部线程池,在调用后如果试图再次发送请求会导致线程池的错误。 所以以上方法应该只在退出应用时调用,而不是在每个 Activity 的 onDestroy() 中调用。

应用内社交

简介

应用内社交,在应用开发中出现的场景非常多,包括用户间关注(好友)、朋友圈(时间线)、状态广场、互动(评论、点赞)等常用功能,应用内社交可以认为是一个应用基础通用功能。

安装

安装 SDK

请按第二章【SDK 集成】步骤完成 SDK 安装。

maxleap-sdk-social-xxx.jar 包导入工程的 libs 目录下。

配置权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

添加依赖

compile "com.squareup.okhttp3:okhttp:3.1.2"

使用

MLHerms

MLHerms 是 SDK 的工具类,是一切功能的入口。在使用 MHerms 前需要先进行初始化操作。

MLHermes.initialize(this, APP_ID, API_KEY);
MLHermes.setSessionToken(USER_SESSION_TOKEN);

Constraint

Constraint 用于在某些查询 API 中指定排序,分页相关的操作,可以通过以下实例获得一个 Constraint 对象。

Constraint constraint = new Constraint.Builder()
    .page(0)
    .asc()
    .desc()
    .orderByTime()
    .orderByUserId()
    .build();

所有可以接受 Constraint 的 API 中都可以省略 Constraint 对象,此时将按照第 0 页,按时间倒序的默认条件进行查询。

关系操作

SDK 中使用 RelationManager 来进行关系相关的操作,可以通过以下方法获得 RelationManager 的实例。

RelationManager relationManager = MLHermes.getRelationManager();

创建或更新关系

relationManager.createOrUpdateRelation(userId, followerId, reverse, black,new DataHandler<JSONArray>() {
    @Override
    public void onSuccess(JSONArray relation) {
        String objectId1 = relation.getJSONObject(0).getString("objectId");
        if(reverse){
            String objectId2 = relation.getJSONObject(1).getString("objectId");
        }
    }

    @Override
    public void onError(HermsException e) {

    }
});
  • reverse 表示是否互相关注,当互相关注时返回两个创建的 Relation 的 ObjectId,否则只返回一个 Relation。
  • black 表示是否允许对方查看关系。

获得关系

relationManager.getRelation(relationObjectId, new DataHandler<Relation>() {
    @Override
    public void onSuccess(Relation relation) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

其中 Relation 表示关系的实体。

删除关系

relationManager.delete(relationObjectId, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得状态

状态用于描述关系的形式,有以下三种

  • Status.FOLLOW_EACH_OTHER 互相关注
  • Status.FOLLOWING 关注
  • Status.UNFOLLOW 没有关注
relationManager.getStatus(userId, followerId, new DataHandler<Integer>() {
    @Override
    public void onSuccess(Integer status) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得 Followers

relationManager.getFollowers(userId, constraint, new DataListHandler<Relation>() {
    @Override
    public void onSuccess(List<Relation> relations) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得 Follows

relationManager.getFollows(followerId, constraint, new DataListHandler<Relation>() {
    @Override
    public void onSuccess(List<Relation> relations) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

说说

SDK 中使用 ShuoShuoManager 来进行说说相关的操作,可以通过以下方法获得 ShuoShuoManager 的实例。

ShuoShuoManager shuoShuoManager = MLHermes.getShuoShuoManager();

创建或更新说说

在进行网络请求之前首先需要先创建 ShuoShuo 的实例对象,目前可以创建以下三种形式的说说:

分享文本

ShuoShuo shuoShuo = new ShuoShuo();
shuoShuo.setUserId(userId);
shuoShuo.setContent("hello world");

分享链接

ShuoShuo shuoShuo = new ShuoShuo();
shuoShuo.setUserId(userId);
shuoShuo.setContent("hello world");
shuoShuo.setLink("http://www.github.com");

分享图片

ShuoShuo shuoShuo = new ShuoShuo();
shuoShuo.setUserId(userId);
shuoShuo.setContent("hello world");
shuoShuo.setFileName(new File("/Users/SidneyXu/downloads", "foobar.jpg").getAbsolutePath());

以上三种形式都可以在创建时传入地理位置信息

shuoShuo.setLongitude(50);
shuoShuo.setLatitude(50);

默认说说发送到广场,可以通过以下方式发送到朋友圈

shuoShuo.setFriendCycle(true);

创建完后就可以调用以下方法发送请求

shuoShuoManager.createOrUpdateShuoShuo(shuoShuo, new DataHandler<String>() {
    @Override
    public void onSuccess(String objectId) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得说说

shuoShuoManager.getShuoShuo(shushuoObjectId, new DataHandler<ShuoShuo>() {
    @Override
    public void onSuccess(ShuoShuo shuoShuo) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

删除说说

删除的同时也会把说说对应的文件也给删除掉

shuoShuoManager.deleteShuoShuo(userId, shushuoObjectId, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得指定用户的说说列表

shuoShuoManager.getShuoShuoList(userId, constraint, black, zan, new DataListHandler<ShuoShuo>() {
    @Override
    public void onSuccess(List<ShuoShuo> shuoshuos) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

其中 blackzan 都是查询条件。

获得最近更新的说说列表

shuoShuoManager.getLatestShuoShuos(constraint, new DataListHandler<ShuoShuo>() {
    @Override
    public void onSuccess(List<ShuoShuo> shuoshuos) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得指定范围内的说说列表

shuoShuoManager.getNearbyShuoShuos(longitude, latitude, distance, new DataListHandler<ShuoShuo>() {
    @Override
    public void onSuccess(List<ShuoShuo> shuoshuos) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得朋友圈的说说列表

shuoShuoManager.getFriendCircleShuoShuos(userId, new DataHandler<Triple<List<ShuoShuo>, List<Comment>, List<Comment>>>() {
    @Override
    public void onSuccess(Triple<List<ShuoShuo>, List<Comment>, List<Comment>> listListListTriple) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

返回值为说说列表,评论列表,赞列表。

获得说说包含的图片列表

shuoShuoManager.getPhotoList(userId, shuoshuoObjectId, new DataListHandler<String>() {
    @Override
    public void onSuccess(List<String> photoPaths) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

以上操作返回的是图片路径的列表。

下载说说中的图片

shuoShuoManager.downloadPhoto(userId, shuoshuoObjectId, filePath, targetPath, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

其中 filePath 是说说中指定的图片的路径,targetPath 是指定的需要下载到的路径。

评论

SDK 中使用 CommentManager 来进行评论相关的操作,可以通过以下方法获得 CommentManager 的实例。

CommentManager commentManager = MLHermes.getCommentManager();

创建评论

commentManager.createComment(userId, shuoId, content, new DataHandler<String>() {
    @Override
    public void onSuccess(String objectId) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

更新评论

更新评论只能够更新该评论是否已读。

commentManager.updateComment(commentObjectId, read, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void void) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得评论

commentManager.getComment(commentObjectId, new DataHandler<Comment>() {
    @Override
    public void onSuccess(Comment comment) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

其中 Comment 表示评论的实体。

删除评论

commentManager.deleteComment(commentObjectId, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得评论列表

commentManager.getComments(shuoId, constraint, new DataListHandler<Comment>() {
    @Override
    public void onSuccess(List<Comment> comments) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得未读评论列表

commentManager.getUnreadComments(userId, new DataListHandler<Comment>() {
    @Override
    public void onSuccess(List<Comment> comments) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

为评论 + 1

commentManager.favoriteComment(userId, shuoId, new DataHandler<String>() {
    @Override
    public void onSuccess(String objectId) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

地理位置

SDK 中使用 LocationManager 来进行地理位置相关的操作,可以通过以下方法获得 LocationManager 的实例。

LocationManager locationManager = MLHermes.getLocationManager();

创建或更新地理位置

locationManager.createOrUpdateLocation(userId, longitude, latitude, new DataHandler<String>() {
    @Override
    public void onSuccess(String objectId) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得地理位置

locationManager.getLocation(locationObjectId, new DataHandler<Location>() {
    @Override
    public void onSuccess(Location location) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

其中 Location 表示地理位置的实体。

删除地理位置

locationManager.deleteLocation(locationObjectId, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得用户的地理位置

locationManager.getLocationByUser(userId, new DataListHandler<Location>() {
    @Override
    public void onSuccess(List<Location> locations) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

获得附近地理位置

locationManager.getNearbyLocation(userId, longitude, latitude, distance, new DataListHandler<Location>() {
    @Override
    public void onSuccess(List<Location> locations) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

账号系统

SDK 中使用 UserManager 来进行账号相关的操作,可以通过以下方法获得 UserManager 的实例。

UserManager userManager = MLHermes.getUserManager();

注册用户

userManager.registerInBackground(username, password, new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {
        String objectId = jsonObject.optString("objectId");
    }

    @Override
    public void onError(HermsException e) {

    }
});

登录用户

userManager.loginInBackground(username, password, new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {
        String objectId = jsonObject.optString("objectId");
    }

    @Override
    public void onError(HermsException e) {

    }
});

获得验证码

userManager.getSmsCodeInBackground(phoneNumber, new DataHandler<Void>() {
    @Override
    public void onSuccess(Void aVoid) {

    }

    @Override
    public void onError(HermsException e) {

    }
});

使用验证码登录

userManager.loginByMobileNumberInBackground(phoneNumber, smsCode, new DataHandler<JSONObject>() {
    @Override
    public void onSuccess(JSONObject jsonObject) {
        String objectId = jsonObject.optString("objectId");
    }

    @Override
    public void onError(HermsException e) {

    }
});

社交分享

社交分享目前支持五种平台:微信朋友圈,微信好友,QQ 好友,QQ 空间和新浪微博。

安装

安装 SDK

请按第二章【SDK 集成】完成 SDK 下载,

解压后将 maxleap-sdk-social-xxx.jar 包导入工程的 libs 目录下。

第三方平台 SDK

  • 微信 SDK:下载解压后将 libammsdk.jar 放入 libs 目录下。
  • QQ SDK:下载解压后将 open_sdk_r5509_lite.jar 放入 libs 目录下。
  • 微博 SDK:下载解压后将 weiboSDKCore_xxx.jar 放入 libs 目录下,将各平台的 *.so 文件放入 jniLibs 目录下。

申请AppID和配置签名

下载完第三方平台的 SDK 后请按照各平台的规定申请应用,注意第三方平台上填写的应用包名签名必须确保正确,否则您将无法跳转到分享界面。

配置权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

添加依赖

compile "com.squareup.okhttp3:okhttp:3.1.2"
compile 'com.android.support:support-v4:24.2.0'

配置appID到应用

将申请的appid等信息,在res->values->string.xml中配置如下:

<string name="ml_sina_weibo_app_id">your weibo appid</string>
<string name="qq_app_id">tencent+your qq appid</string>
<string name="ml_qq_app_id">your qq appid</string>
<string name="ml_wechat_app_id">your wechat appid</string>
<string name="ml_wechat_app_secret">your wechat secret</string>

配置AndroidManifest.xml

将以下平台对应的组件配置到您项目的AndroidManifest.xml

微信

<activity
    android:name="com.maxleap.social.thirdparty.WXEntryActivity"
    android:launchMode="singleTop"/>
<activity-alias
    android:name="${applicationId}.wxapi.WXEntryActivity"
    android:targetActivity="com.maxleap.social.thirdparty.WXEntryActivity"
    android:enabled="true"
    android:exported="true"/>

QQ

<activity
        android:name="com.tencent.tauth.AuthActivity"
        android:launchMode="singleTask"
        android:noHistory="true"
        android:screenOrientation="portrait">
    <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/qq_app_id"/>
    </intent-filter>
</activity>
<activity
        android:name="com.tencent.connect.common.AssistActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:screenOrientation="portrait"
        android:theme="@android:style/Theme.Translucent.NoTitleBar"/>

weibo

<activity
        android:name="com.sina.weibo.sdk.component.WeiboSdkBrowser"
        android:configChanges="keyboardHidden|orientation"
        android:windowSoftInputMode="adjustResize"
        android:exported="false">
</activity>

使用

MLHermes

首先在Application中对MLHermes进行初始化

//maxleap上申请的appid和apikey
private String APP_ID = "your appid";
private String API_KEY = "your apikey";

@Override
public void onCreate() {
    super.onCreate();

    MLHermes.initialize(this, APP_ID, API_KEY);

}

Platform

Platform 在 SDK 中是一个代表第三方平台的抽象类。目前有三个直接子类 QQPlatformWechatPlatformWeiboPlatform,分别表示 QQ 平台,微信平台和新浪微博平台。

Platform 负责存储第三方平台的相关数据,如应用 ID,应用 Secret,Access Token 等。

在 SDK 中可以任意创建 Platform 的实例,或者调用以下实例中的 MLHermes 的静态方法获得缓存在 SDK 中的实例。

Platform platform = MLHermes.getPlatform(Platform.Type.WEIBO);

其中参数为 Platform.Type类型,共有以下三种: - Type.WEIBO - Type.QQ - Type.WECHAT

ShareItem

ShareItem 在 SDK 中代表需要分享的内容。你可以直接创建 ShareItem 的实例也可以使用 ShareItemBuilder 的 Fluent API 来进行创建。

文本分享

ShareItem shareItem = ShareItem.newBuilder()
        .text(editText.getText().toString())
        .actionUrl("http://www.github.com")
        .createShareItem();

图文分享

ShareItem shareItem = ShareItem.newBuilder()
        .text("image message")
        .description("this is a simple image test message")
        .imageUrl("http://www.demo.com/avatar.jpg")
        .bitmap(bitmap)
        .actionUrl("http://www.github.com")
        .createShareItem();

以上通用分享可以用于各个平台,但是由于平台的差异性实际行为会有一些差别。

除了以上通用方法你也可以根据第三方平台的 API 直接创建对应的 ShareItem 来实现更强的自定义功能。

例:自定义微信 ShareItem

// 根据微信 API 创建 Req 对象
SendMessageToWX.Req req = new SendMessageToWX.Req();
WXTextObject textObject = new WXTextObject();
WXMediaMessage message = new WXMediaMessage();
message.title = "custom title";
message.mediaObject = textObject;
req.transaction = "" + System.currentTimeMillis();
req.message = message;

// 建立微信对应的 ShareItem 对象
WechatShareItem wechatShareItem = new WechatShareItem(req);

ShareProvider

ShareProvider 在 SDK 中代表分享的行为。一个 ShareProvider 由 Platform 提供对应的平台信息,有 ShareItem 提供需要分享的信息。

创建 ShareProvider

shareProvider = new WeiboShareProvider(activity, platform);

ShareProvider 目前共有以下四个子类:

  • WeiboShareProvider 微博分享
  • QQShareProvider QQ 好友分享
  • QZoneShareProvider QZone 分享
  • WechatShareProvider 微信分享,默认分享到微信好友,如果需要分享到朋友圈需要 mShareItem.isTimeLine = true

进行分享

shareProvider.shareItem(shareItem, new EventListener() {
    @Override
    public void onSuccess() {
        // 分享成功
    }

    @Override
    public void onError(HermsException e) {
        // 分享失败
    }

    @Override
    public void onCancel() {
        // 取消分享
    }
});

分享后回调监听

  • 微信

微信分享后监听已经在sdk中做了实现,您无需在做处理。

  • QQ

QQ分享或者QQ ZONE分享需要在onActivityResult()中加入回调方法。

示例如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(shareProvider != null){
        shareProvider.onActivityResult(requestCode, resultCode, data);
    }
}
  • 微博

微博分享需要在onNewIntent()中加入回调方法

示例如下:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (shareProvider != null) {
        shareProvider.onNewIntent(intent);
    }
}

您可参考开源组件 MaxShare 中的相关调用实现。

数据分析

SDK 集成

请按第二章【SDK 集成】步骤完成下载和配置。

启用服务

安装 SDK 完成后,MaxLeap 服务将自动帮助您追踪应用内的一些数据,自动收集终端信息。

MaxLeap 分析服务的默认状态为 开启,如果您希望 关闭 分析服务,您可以在 SDK 初始化中调用以下代码:

MaxLeap.Options options = new MaxLeap.Options();
options.appId = APP_ID;
options.clientKey = API_KEY;
//  关闭数据分析
options.analyticsEnable = false;
MaxLeap.initialize(this, options);

渠道

渠道代表该应用的来源,如 GooglePlay,App Store 或其他自定义渠道。如果分析服务是开启的,则必须先设置渠道。只需在 AndroidManifest.xml 中添加如下代码:

<application>
    <meta-data
        android:name="ml_channel"
        android:value="YOUR_CHANNEL_NAME">
    </meta-data>
</application>

会话

会话(session)代表在某一段时间内,用户与应用之间的交互。记录会话可获取新增用户、活跃用户、启动次数、使用时长等基本数据。

追踪会话

  • 用户在不同 Activity 中切换时,我们需要在所有 Activity 中的 onPause()onResume() 中添加如下代码,以实现会话的暂停和继续,并判断用户是否开始新的会话。
@Override
protected void onPause() {
  super.onPause();
  MLAnalytics.onPause(this);
}
@Override
protected void onResume() {
  super.onResume();
  MLAnalytics.onResume(this);
}
  • 离开应用后,在特定时间内,用户重返应用,系统将继续上一个会话。该时间长度可自定义(单位:秒),在 SDK 初始化时调用以下代码:
options.sessionContinueTime = 30 * 1000;
  • 当应用进入后台超过特定时间,会话结束。用户重返应用时,将开始新的会话。

自定义页面

页面(screen)代表被用户访问的应用界面。记录页面访问可获取页面访问量,访问路径,访问深度等数据。

字段说明

字段名类型描述
pageNameString应用页面的名称

追踪自定义页面

在页面开始处,添加:

@Override
protected void onResume() {
  super.onResume();
  MLAnalytics.onPageStart(pageName);
}

在页面结束处,添加:

@Override
protected void onPause() {
  super. onPause();
  MLAnalytics.onPageEnd(pageName);
}

注意:

  • 每个页面都必须同时指定onPageStart()onPageEnd(),并且不同页面之间须相互独立,没有交叉,即不能 start page 1-start page 2-end page 1-end page 2
  • 若页面通过 Activity + Fragment 实现,我们需要在 Fragment 中的onResume()onPause()中添加上述代码,以记录对该 Fragment 界面的访问。

自动统计会话和页面

自动统计可以省去手动调用的繁琐步骤,但是自动统计仅适用于 4.0 以上的设备,所以如果你的应用需要兼容 4.0 以下的设备时不应该使用自动统计,而应该按照前两节的所述进行手动统计。

自动统计默认关闭,可以通过以下代码开启自动统计

自动统计会话和页面

options.autoTrackStrategy = MaxLeap.AUTO_TRACK_SESSION_AND_PAGEVIEW;

仅自动统计会话

options.autoTrackStrategy = MaxLeap.AUTO_TRACK_SESSION;

自定义事件

自定义事件可以实现在应用程序中埋点,以记录用户的点击行为并且采集相应数据。

字段说明

字段名类型描述
eventIdString事件名
keyString事件参数
valueString事件参数的值

请注意不要定义过多的自定义事件名 (event_id) 否则可能出现数目庞大的自定义事件列表, 而无法达到了解与分析用户行为的目的.

统计自定义事件发生次数

MLAnalytics.logEvent(eventId);

统计事件及其属性

如果你希望统计事件时包含自定义的属性可以调用如下方法:

Map<String,String> dimensions = new HashMap<>;
dimensions.put("error", "404");
MLAnalytics.logEvent(eventId, eventCount, dimensions);

应用内支付

应用内支付用于统计用户的支付行为。

建立 Transaction

MLIapTransaction transaction = new MLIapTransaction("gas 360", MLIapTransaction.TYPE_IN_APP);
transaction.setTitle("buy gas");
transaction.setDescription("buy some gas to make the car continue running");
transaction.setPriceAmount(7990000);
transaction.setPriceCurrency("GBP");
transaction.setPaySource(MLIapTransaction.PAYSOURCE_GOOGLE_PLAY);

Transaction 用于表示一次完整的购买行为所相关的信息。其构造方法接收两个参数:productIdtypeproductId 用于区分物品,type 为物品的类型,通常为应用内付费或者订阅付费两种。

Transaction 的属性说明

属性名类型描述
titleString购买的物品的标题
descriptionString购买的物品的描述信息
orderIdString订单 ID
priceAmountlong购买的物品的价格,根据 Google Play 的定义,其值为实际价格的 100 万倍
priceCurrencyString购买的物品的货币单位
paySourceString表示购买的渠道的字符串,通常使用系统已定义的常量即可(PAYSOURCE_GOOGLE_PLAY, PAYSOURCE_AMAZON_STORE, PAYSOURCE_ALIPAY, PAYSOURCE_PAYPAL)
virtualCurrencyAmountString虚拟货币价格,仅用于游戏支付,应用内付费不需要设置该属性

发送统计信息

统计表示开始支付的事件

MLAnalytics.onChargeRequest(transaction);

统计表示支付成功的事件

MLAnalytics.onChargeSuccess(transaction);

统计表示支付失败的事件

MLAnalytics.onChargeFailed(transaction);

统计表示支付取消的事件

MLAnalytics.onChargeCancel(transaction);

正确统计支付信息前提

  1. 集成 Session 统计信息

  2. 调用成功,失败或取消事件前必须先调用开始支付才能保证统计的数据的正确性(即开始支付-支付失败-开始支付-支付成功,而不能开始支付-支付失败-支付成功)

推送营销

简介

什么是 MaxLeap 推送营销服务

推送营销服务是 MaxLeap 提供的营销和信息发布功能。目前提供两种消息模式:推送消息应用内消息

  • 您可以通过推送消息方式向指定人群推送消息,也可以通过应用内消息,在应用内向有某种行为的用户显示特定内容。
  • 您还可以在消息中设置用户点击后的目标 Activity。消息的创建,设置和发送均在Console中完成。

SDK 集成

请按第二章【SDK 集成】步骤完成 SDK 集成。

推送消息

推送消息帮助您迅速地将消息展示给大量的用户。发送推送消息后,无论用户是否打开应用,都将在状态栏看见它。您可以在 Console 中自定义发送消息的内容,并且传递若干参数(键值对)至客户端。用户点击推送消息后,应用会根据参数决定目标 Activity。

目前 MaxLeap 提供两种类型的推送服务:GCM 和 LPNS,GCM 依托于谷歌服务,LPNS 依托于长连接,开发者可以自行选择采用哪种类型。

GCM

配置

  1. 提供 Sender IDAPI key. 请在 Google开发者中心 获取这两个Key.
  2. AndroidManifest.xml 中进行必要的配置。

    <!-- your package -->
    <permission
        android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" />
    
    <!-- App receives GCM messages. -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <!-- GCM requires a Google account. -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <!-- Keeps the processor from sleeping when a message is received. -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    
    <application ...>
        <!--渠道-->
        <meta-data
            android:name="ml_channel"
            android:value="google_play" />
    
        <!--Push 类型-->
        <meta-data
            android:name="ml_push"
            android:value="gcm" />
    
        <!--senderId-->
        <meta-data
            android:name="com.maxleap.push.gcm_sender_id"
            android:value="id:yourSenderId" />
    
        <!--Notification 图标(非必需),默认为应用的图标-->
        <meta-data
            android:name="com.maxleap.push.notification_icon"
            android:resource="@android:drawable/ic_dialog_alert" />
    
        <!--Play Services-->
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    
        <!--Push 服务-->
        <service
            android:name="com.maxleap.MLPushService"
            android:enabled="true"
            android:exported="false" />
    
        <!--Receiver-->
        <receiver
            android:name="com.maxleap.push.GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
    
            <category android:name="YOUR_PACKAGE_NAME" />
        </intent-filter>
        </receiver>
    
        <receiver android:name="com.maxleap.MLPushBroadcastReceiver" android:exported="false">
        <intent-filter>
            <action android:name="com.maxleap.push.intent.RECEIVE"/>
            <action android:name="com.maxleap.push.intent.OPEN"/>
        </intent-filter>
        </receiver>
    </application>
    

    注意

    请将上述代码中的 YOUR_PACKAGE_NAME 换成你的应用的包名,将 yourSenderId 替换成你在 Google 开发者控制台上创建的 senderId,多个 senderId 可以用逗号进行分隔(即 “id:abc” 或 “id:abc,def,ghi” 都是合法的)。

LPNS

配置

AndroidManifest.xml 中添加权限和必要的项目

<manifest>
    <!--必需-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />

    <!--可选-->
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <application>

        <!--渠道-->
        <meta-data
            android:name="ml_channel"
            android:value="google_play" />

        <!--Push 类型-->
        <meta-data
            android:name="ml_push"
            android:value="lpns" />

        <!--心跳间隔(非必需),默认为5分钟-->
        <meta-data
            android:name="ml_push_heartbeat"
            android:value="600000" />

        <!--Notification 图标(非必需),默认为应用的图标-->
        <meta-data
            android:name="com.maxleap.push.notification_icon"
            android:resource="@android:drawable/ic_dialog_alert" />

        <!--用于接收 Push 信息-->
        <receiver
            android:name="com.maxleap.MLPushBroadcastReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="com.maxleap.push.intent.RECEIVE" />
                <action android:name="com.maxleap.push.intent.OPEN" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.maxleap.MLPushService"
            android:enabled="true"
            android:exported="false" />

        <!-- 用于进行心跳检测,开机重启服务 -->
        <receiver
            android:name="com.maxleap.MLBootReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>
        <receiver
            android:name="com.maxleap.MLHeartBeatReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="com.maxleap.push.intent.HEARTBEAT" />
                <action android:name="com.maxleap.push.intent.RECONNECT" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

自定义推送消息的处理

您可以通过以下步骤自定义推送消息的显示和处理。

  1. 新建任意继承自 MLPushBroadcastReceiver 的类
  2. 在以上步骤创建的类中完成通过方法的重写完成一系列自定义操作
  3. AndroidManifest.xml 中配置将 com.maxleap.MLPushBroadcastReceiver 一项替换成你自定义的类。
跳转到指定的 Activity

重写 getActivity() 方法可以指定当点击通知后所跳转的 Activity 画面。

@Override
protected Class<? extends Activity> getActivity(Intent intent) {
    return HelloWorld.class;
}

在跳转到的目标 Activity 中可以通过 getIntent() 得到该条 Push 所携带的信息

Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
    for (String key : intent.getExtras().keySet()) {
        Log.i(TAG, key + " = " + intent.getStringExtra(key));
    }
}
跳转到指定的 Uri

重写 getUri() 方法可以指定当点击通知后所跳转的 Uri。

@Override
protected Uri getUri(Intent intent) {
    return Uri.parse("http://www.github.com");
}

注意

getUri() 的优先级要高于 getActivity()。如果 getUri() 没有返回 null 的话,则 getActivity() 会被忽略。

自定义图标

定义 Notification 的 LargeIcon

@Override
protected Bitmap getLargeIcon(Context context) {
    return BitmapFactory.decodeStream(in);
}

自定义 Notification 的 SmallIcon

@Override
protected int getSmallIconId(Context context) {
   return R.drawable.small_icon;
}

或者如之前所述,在 AndroidManifest.xml 中配置

<meta-data
    android:name="com.maxleap.push.notification_icon"
    android:resource="@android:drawable/ic_dialog_alert" />
修改 Intent

如果你希望在 Activity 或 Uri 跳转前修改 Intent 的信息,,可以重写如下代码

@Override
protected void startIntent(Context context, Intent intent) {
    // 修改 Intent 的 Flag 信息
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    super.startIntent(context, intent);
}
完全自定义 Notification

如果希望自己来实现 Notification 对象,可以重写如下方法

@Override
protected Notification getNotification(Context context, Intent intent)

应用内消息

配置

为了使用应用内消息服务,您需要启用Marketing服务,在 MaxLeap.initialize()方法中修改默认配置信息:

MaxLeap.Options options = new MaxLeap.Options();
options.appId = APP_ID;
options.clientKey = API_KEY;
options.marketingEnable = true;
options.serverRegion=MaxLeap.REGION_CN;
MaxLeap.initialize(this, options);

如果你希望在某个 Activity 中显示应用内消息,则此 Activity 必须继承自 FragmentActivity 并且在 onResume()onPause() 中实现如下方法。

@Override
protected void onResume() {
    super.onResume();

    MLMarketing.setInAppMessageDisplayActivity(this);
    MLAnalytics.onResume(this);
}

@Override
protected void onPause() {
    super.onPause();

    MLMarketing.dismissCurrentInAppMessage();
    MLMarketing.clearInAppMessageDisplayActivity();
    MLAnalytics.onPause(this);
}

用户支持

简介

用户支持服务是 MaxLeap 为开发者提供的一套标准应用客服方案。在客户端,此方案提供完整的 FAQ 的显示页面及问题反馈对话页面。在 Console 端,用户支持服务提供 FAQ 的管理及用户反馈的处理界面。

准备工作

请按第二章【SDK 集成】完成环境的准备。

集成

maxleap-sdk-core-{版本}.jar,maxleap-sdk-support-{版本}.jar,maxleap-sdk-support-resources等。

进入 FAQ 页面

调用下面的方法可以进入 FAQ 界面

MLHelpCenter.openFaqs(context);

进入用户反馈页面

调用下面的方法可以进入 Issue 界面

MLHelpCenter.openConversation(context);

自定义 UI

全局样式

actionbar

  • values/ml_hc_styles.xml
    • MLHelpcenter.Theme
      • colorAccent - 用于设置获得焦点的控件的颜色

ActionBar 样式

通用样式

actionbar

  • values/ml_hc_styles.xml

    • MLHelpcenter.Theme
      • homeAsUpIndicator - 用于设置 UP 按钮图标
    • MLHelpcenter.Theme.ActionBar
      • background - 用于设置 ActionBar 背景色
    • MLHelpcenter.Theme.ActionBar.Title
      • android:textColor - 用于设置 title 字体颜色
      • android:textSize - 用于设置 title 字体大小

SearchView 样式

actionbar

  • values/ml_hc_styles.xml
    • MLHelpcenter.Theme
      • ml_hc_searchActionButtonIcon - 用于设置 Search 按钮图标

actionbar

  • values/ml_hc_styles.xml

    • MLHelpcenter.Theme
      • searchIcon - 用于设置 search hint 的图标
      • ml_hc_searchHintTextColor - 用于 设置 search hint 的文字颜色
  • values/ml_hc_strings.xml

    • ml_hc_search_hint - 用于设置 search hint 文字

其它 Action Button 样式

actionbar

  • values/ml_hc_styles.xml
    • MLHelpcenter.Theme 样式
      • ml_hc_chooseImageIcon - 用于设置选择图片按钮的图标
      • ml_hc_sendButtonIcon - 用于设置发送按钮的图标

actionbar

  • values/ml_hc_styles.xml
    • MLHelpcenter.Theme 样式
      • ml_hc_contactUsButtonIcon - 用于设置联系按钮的图标

actionbar

  • values/ml_hc_styles.xml
    • MLHelpcenter.Theme 样式
      • ml_hc_messageCountBackground - 用于设置新消息个数的背景图片
      • ml_hc_messageCountTextColor - 用于设置新消息个数的文字颜色
      • ml_hc_messageCountTextSize - 用于设置新消息个数的文字大小

各画面单独的样式

FAQ 画面

actionbar

  • values/ml_hc_strings.xml
    • ml_hc_faq - 用于设置 FAQ 标题文字

All Questions 画面

actionbar

  • values/ml_hc_strings.xml
    • ml_hc_allQuestions - 用于设置 ALL Questions 标题文字

Question 画面

actionbar

  • values/ml_hc_strings.xml
    • ml_hc_question - 用于设置 Question 标题文字

Conversions 画面

actionbar

  • values/ml_hc_strings.xml

    • ml_hc_conversation - 用于设置 Conversion 标题文字
  • values/ml_hc_styles.xml

    • ml_hc_leftChatBubbleBackground - 用于设置左边气泡的背景
    • ml_hc_leftChatBubbleTextColor - 用于设置左边气泡的字体颜色
    • ml_hc_leftChatBubbleTextSize - 用于设置左边气泡的字体大小
    • ml_hc_rightChatBubbleBackground - 用于设置右边气泡的背景
    • ml_hc_rightChatBubbleTextColor - 用于设置右边气泡的字体颜色
    • ml_hc_rightChatBubbleTextSize - 用于设置右边气泡的字体大小

Album 画面

actionbar

  • values/ml_hc_styles.xml
    • ml_hc_album - 用于设置 Album 标题文字

Drawing 画面

actionbar

  • values/ml_styles.xml
    • ml_hc_drawing - 用于设置 Drawing 标题文字