2013年9月16日 星期一

Unity3D研究院之在Android中调用新浪微博接口(三十七)

http://www.xuanyusong.com/archives/1800

         最近工作比较忙 加上感觉自己好像有点上了年纪 整个人感觉非常的疲惫啊, 希望国庆期间可以好好休息一下,调养一下自己。这两天研究了一下如何在Unity中调用新浪微博接口, 上一篇已经IOS平台分享新浪微博,那么本篇文章将写在结合Unity 在 Android平台分享新浪微博。 使用起来我还是觉得IOS平台的接口比较好用,开始基本傻瓜化,可Android平台的接口新手的话估计导入后都无法运行。借此,雨松MOMO希望新浪微博尽快让Android平台下的接口也能好用,废话就不多说了,我们进入正题。
如下图所示,这是MOMO在Android手机中Unity3D下调用新浪微博分享接口。

在继续学习之前请朋友确认你所需要的微薄密钥是否准备完毕。
1.没有密钥的朋友
请在这里注册一个移动应用,http://open.weibo.com/ 。注册成功后在应用信息-》基本信息中即可获得APP KEY 和 APP SECRET,没有这两个KEY你是无法发送微薄的。 仅仅这些还是不够,因为是刚刚注册的新密钥所以是不能被公众所使用的,你应当继续在 应用信息-> 测试帐号 中添加测试账号,只有添加过的测试帐号才能使用新注册的密钥发送微薄。
2.有密钥的朋友
可以直接使用你的密钥来进行开发。

接着我们下载新浪微博Andoid下API接口, 下载地址:http://code.google.com/p/android-weibo-sdk/downloads/list     我个人在选择API的时候习惯选择最新的版本,页面中最上面的下载地址就是最新的API
解压后使用Eclipse将整个工程引入,一定要从解压后的顶层的文件夹将下面的两个工程同时的引入Eclipse,不然你会出现错误 ”@style/ContentOverlay” 无法找到。这样的话就会将两个工程都引入至elipse,com_weibo_android 是API包, com_weibo_android_exampe 就是例子程序,直接运行它即可。

然后我们需要在Unity3D中制作Android的插件来调用新浪微博接口,如果你现在对Android的插件还是不是很了解,请阅读Unity3D研究院之打开Activity与调用JAVA代码传递参数(十八)文章。

开始MOMO使用com_weibo_android_exampe 来制作Android插件的时候发现最终Unity导出项目的时候无法运行,并且在手机中也无法找到刚刚编译的项目。非常奇怪,后来我就重新把API的代码整理了一遍,问题就解决了。
SinaMessageActivity.java  它是我们写的Activity插件, 当Unity 中准备好 文字与图片数据的时候调用它即可发送信息。

/*
 * Copyright 2011 Sina.
 *
 * Licensed under the Apache License and Weibo License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.open.weibo.com
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.yusong.momo;

import java.io.IOException;
import java.net.MalformedURLException;

import com.unity3d.player.UnityPlayerActivity;
import com.yusong.momo.AsyncWeiboRunner.RequestListener;

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Toast;

public class SinaMessageActivity extends UnityPlayerActivity implements RequestListener {

//public class SinaMessageActivity extends Activity implements RequestListener {
 //这个是你的密钥
 private static final String CONSUMER_KEY = "1626952933";
 private static final String CONSUMER_SECRET = "d92012d3d2ffa5630554723e1825b11c";

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

 }

 //在Unity中调用此方法进行登录、发送微薄
 public void pressed(String name)
 {
  Weibo weibo = Weibo.getInstance();
  weibo.setupConsumerConfig(CONSUMER_KEY, CONSUMER_SECRET);
  weibo.setRedirectUrl("http://xuanyusong.com");

  SharedPreferences settings = getSharedPreferences("MOMO",
    Activity.MODE_PRIVATE);

  //token 和 expires_in 是登录一次成功后服务器返回给你的。
  //因为我们不需要用户每次发送微薄时都登录
  //所以应当记录用户第一次登录时返回的token 和 expires_in
  //发送微博时直接使用之前保存的token和expires_in即可

  String token = settings.getString("token", "");
  String expires_in = settings.getString("expires_in", "");

  //SharedPreferences 中如果有数据就直接发微薄,否则让用户重新登录
  if (!TextUtils.isEmpty(token)
    && !TextUtils.isEmpty(expires_in)) {
   AccessToken accessToken = new AccessToken(token,
     CONSUMER_SECRET);
   accessToken.setExpiresIn(expires_in);
   weibo.setAccessToken(accessToken);
   accessToken = new AccessToken(weibo.getAccessToken()
     .getToken(), weibo.getAccessToken().getSecret());
   accessToken = new AccessToken(token, CONSUMER_SECRET);

   weibo.setAccessToken(accessToken);

   //这里代码一定要加, 不然即时你保存了之前的 token 和 expires_in也 无法发送微薄
   Utility.setAuthorization(new Oauth2AccessTokenHeader());
   try {
    //发送一条文字信息
    update(weibo, Weibo.getAppKey(), "test1", "",
      "");
   } catch (MalformedURLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (WeiboException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  } else {

   //开始让用户登录
   weibo.authorize(SinaMessageActivity.this,
     new AuthDialogListener());

  }

 }

 public void onResume() {
  super.onResume();
 }

 class AuthDialogListener implements WeiboDialogListener {

  @Override
  public void onComplete(Bundle values) {

   //用户登录成功后直接发送微薄
   String token = values.getString("access_token");
   String expires_in = values.getString("expires_in");

   SharedPreferences settings = getSharedPreferences("MOMO",
     Activity.MODE_PRIVATE);
   SharedPreferences.Editor editor = settings.edit();
   editor.putString("token", token);
   editor.putString("expires_in", expires_in);
   editor.commit();

   AccessToken accessToken = new AccessToken(token, CONSUMER_SECRET);
   accessToken.setExpiresIn(expires_in);
   Weibo weibo = Weibo.getInstance();
   weibo.setAccessToken(accessToken);
   accessToken = new AccessToken(weibo.getAccessToken().getToken(),
     weibo.getAccessToken().getSecret());
   accessToken = new AccessToken(token, CONSUMER_SECRET);

   weibo.setAccessToken(accessToken);
   //这里代码一定要加, 不然即时你保存了之前的 token 和 expires_in也 无法发送微薄
   Utility.setAuthorization(new Oauth2AccessTokenHeader());

    try {
     //在这里发送一条 文字+ 图片的微薄
     //路径比较重要,在Unity中将图片保存在这个路径下
     //在Android插件中直接通过这个路径即可拿到图片对象
     upload(weibo, Weibo.getAppKey(), "/data/data/com.yusong.momo/files/Screenshot.png",
       "Unity3D 中调用新浪微博接口 分享自 @雨松MOMO  ", "", "") ;

    } catch (WeiboException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }

  }

  @Override
  public void onError(DialogError e) {
   Toast.makeText(getApplicationContext(),
     "Auth error : " + e.getMessage(), Toast.LENGTH_LONG).show();
  }

  @Override
  public void onCancel() {
   Toast.makeText(getApplicationContext(), "Auth cancel",
     Toast.LENGTH_LONG).show();
  }

  @Override
  public void onWeiboException(WeiboException e) {
   Toast.makeText(getApplicationContext(),
     "Auth exception : " + e.getMessage(), Toast.LENGTH_LONG)
     .show();
  }

 }

 // 发送文字
 private String upload(Weibo weibo, String source, String file,
   String status, String lon, String lat) throws WeiboException {
  WeiboParameters bundle = new WeiboParameters();
  bundle.add("source", source);
  bundle.add("pic", file);
  bundle.add("status", status);
  if (!TextUtils.isEmpty(lon)) {
   bundle.add("lon", lon);
  }
  if (!TextUtils.isEmpty(lat)) {
   bundle.add("lat", lat);
  }
  String rlt = "";
  String url = Weibo.SERVER + "statuses/upload.json";
  AsyncWeiboRunner weiboRunner = new AsyncWeiboRunner(weibo);
  weiboRunner.request(this, url, bundle, Utility.HTTPMETHOD_POST, this);

  return rlt;
 }

 // 发送文字 + 图片
 private String update(Weibo weibo, String source, String status,
   String lon, String lat) throws MalformedURLException, IOException,
   WeiboException {
  WeiboParameters bundle = new WeiboParameters();
  bundle.add("source", source);
  bundle.add("status", status);
  if (!TextUtils.isEmpty(lon)) {
   bundle.add("lon", lon);
  }
  if (!TextUtils.isEmpty(lat)) {
   bundle.add("lat", lat);
  }
  String rlt = "";
  String url = Weibo.SERVER + "statuses/update.json";
  AsyncWeiboRunner weiboRunner = new AsyncWeiboRunner(weibo);
  weiboRunner.request(this, url, bundle, Utility.HTTPMETHOD_POST, this);
  return rlt;
 }

 @Override
 public void onComplete(String response) {
  runOnUiThread(new Runnable() {

   @Override
   public void run() {
    Toast.makeText(SinaMessageActivity.this, "微薄分享成功",
      Toast.LENGTH_LONG).show();
   }
  });

 }

 @Override
 public void onIOException(IOException e) {
  runOnUiThread(new Runnable() {

   @Override
   public void run() {
    Toast.makeText(SinaMessageActivity.this, "微薄分享错误",
      Toast.LENGTH_LONG).show();
   }
  });

 }

 @Override
 public void onError(WeiboException e) {
  runOnUiThread(new Runnable() {
   @Override
   public void run() {
    Toast.makeText(SinaMessageActivity.this, "微薄分享失败",
      Toast.LENGTH_LONG).show();
   }
  });

 }

}


当插件准备完毕后,将它导入至Unity3D中,如果不会的朋友请看我之前的文章。Unity3D研究院之打开Activity与调用JAVA代码传递参数(十八)
我们在看看Unity中是如何调用插件中的分享接口的。
Test.cs
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System;

public class Test : MonoBehaviour {

 void Start () 
 {
  //在Unity截取一张图片
                //该图片的路径保存在"/data/data/com.yusong.momo/files/Screenshot.png"
                //在Android插件中根据这个路径即可拿到该图片
  Application.CaptureScreenshot("Screenshot.png");

 }

 void Update () 
 {

 }

 void OnGUI()
 {
  if(GUILayout.Button("sendSinaMessage",GUILayout.Width(200),GUILayout.Height(100)))
  {

   //开始登录新浪微博
   using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            {
                using( AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
                {
                      //在这里直接调用pressed方法进行登录与发送微薄
                      jo.Call("pressed","");
                }
            }

  }

 }
}


调用接口后会调用新浪登录界面,继续发送新浪微薄。


最后我把文本的Android插件代码 以及 Unity工程放出来, 下载完毕后解压即可看到工程。雨松MOMO希望大家学习愉快。

2013年3月23日补充

在调用新浪微薄Android接口的时候我还出现了一个 Can’t create handler inside thread that has not called Looper.prepare() 错误。
这个错误前段时间一直因为别的事情没有能即时的解决。 这几天MOMO在做Android for QQzone的插件中也遇到了同样的错误。出现这个错误的额原因就是unity调用插件中的方法中又开启了新的线程。
先改动一下上面的unity C#脚本,方法名我就不换了。。。主要说原理。。
  if(GUILayout.Button(" Send QQ zone", GUILayout.Height(50)))
  {

                         AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");

    using (AndroidJavaObject jo = jc.GetStatic("currentActivity"))
      {
    jo.Call("LoginAndSend",jo);

        }

  }

 这段代码的意思就是在unity中把当前的Activity对象传递给Android插件中。在插件中这样来写。

public void LoginAndSend(final Activity currentActivity) 
{
 currentActivity.runOnUiThread(new Runnable() {

  @Override
  public void run() {
   //doyourcode

                        });
 }

}

在插件中通过调用UI主线程来执行原先的子线程。。