Android-App内部实现收发微信

众所周知,微信本来并没有提供直接收发微信的功能,但是利用一些方法,可以实现直接在App里面变向地收发微信。

流程如下:

利用AccesibilityService接收微信

首先,利用AccesibilityService监听微信的推送通知,如果是文字类型,则直接取出,如果是语音类型,则跳到微信进行播放。
if (eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
            Notification notification = (Notification) event.getParcelableData();
            if (null != notification) {
                Bundle extras = notification.extras;
                if (null != extras) {
                    String user = extras.getString("android.title");
                    String content = extras.getString("android.text");
                    if (!TextUtils.isEmpty(content) && content.contains("[语音]")) {
                        needPlayVoice = true;
                        Toast.makeText(this, user + "发来语音", Toast.LENGTH_LONG).show();
                        MainActivity.playTTS(user + "发来语音");
                            final PendingIntent pendingIntent = notification.contentIntent;
                            new Handler().postDelayed(new Runnable(){  
                              public void run() { 
                                  try {
                                    pendingIntent.send();
                                } catch (CanceledException e) {
                                    e.printStackTrace();
                                }
                              }
                      }, 2000);  
                      }else{
                          if(content.indexOf(':') != -1)
                              content = content.substring(content.indexOf(':') + 1);
                          Toast.makeText(this, user + "发来消息:" + content, Toast.LENGTH_LONG).show();
                      }
                }
            }
    }
接着,利用AccesibilityService监听微信的窗口变化,获取到最后一条语音消息以及其长度,进行播放,播放完成后,自动转回App。
else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
        String clazzName = event.getClassName().toString();
        AccessibilityNodeInfo nodeInfo = event.getSource();
        if (clazzName.equals("com.tencent.mm.ui.LauncherUI") && needPlayVoice){
            needPlayVoice = false;
            int ms = playAudioMsg(nodeInfo);
            back(ms * 1000); 
        }
}

其中,播放最后一条语音方法如下:

private int playAudioMsg(AccessibilityNodeInfo nodeInfo){
    if (null == nodeInfo) return 0;
    List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("语音");
    if (null != list && list.size() > 0) {
        AccessibilityNodeInfo node = list.get(list.size() - 1);
        node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
        String content = node.getContentDescription().toString();
        String number = content.substring(2, content.length() - 2);
        return Integer.parseInt(number);
    }
    return 0;
}

自动跳回方法如下:

1
2
3
4
5
6
7
8
9
10
private void back(int time){
new Handler().postDelayed(new Runnable(){
public void run() {
Intent intent = getPackageManager().getLaunchIntentForPackage("com.yi.test");
if (intent != null) {
startActivity(intent);
}
}
}, time);
}

利用Receiver获取用户的OpenID

在AndroidManifest文件中,添加权限:
 <uses-permission android:name="com.tencent.mm.plugin.permission.READ" />  
<uses-permission android:name="com.tencent.mm.plugin.permission.WRITE" />  
<uses-permission android:name="com.tencent.mm.plugin.permission.SEND" /> 
<uses-permission android:name="com.tencent.mm.permission.MM_MESSAGE" /> 
定义接收微信新消息通知的Receiver
<receiver android:enabled="true" android:exported="true" android:name=".WXNotifyReceiver" >
        <intent-filter>
            <action android:name="com.tencent.mm.plugin.openapi.Intent.ACTION_NOTIFY_MSG"/>
            <category android:name="com.tencent.mm.category.com.jinglingtec.ijiazu"/>
        </intent-filter>
</receiver>
获取微信新消息中用户的OpenID
public class WXNotifyReceiver extends BroadcastReceiver{
    public void onReceive(Context paramContext, Intent paramIntent){
        Log.e("WXNotifyReceiver", "new Message" + paramContext.toString() + "\n" + paramIntent.toString());
        if(paramContext == null || paramIntent == null){
            Log.e("[wechat_debug]", "param null");
            return;
        }

        String notifyType = paramIntent.getStringExtra("EXTRA_EXT_OPEN_NOTIFY_TYPE");
        if(notifyType == null || notifyType.length() < 0){
            Log.e("[wechat_debug]", "wrong intent extra notifyType");
            return;
        }

        if(notifyType.equalsIgnoreCase("NEW_MESSAGE")){
            ArrayList<String> userData = paramIntent.getStringArrayListExtra("EXTRA_EXT_OPEN_USER_DATA");

            if ((userData == null) || (userData.size() <= 0))
            {
              Log.e("[wechat_debug]", "wrong intent extra userDatas");
              return;
            }

            Log.e("[wechat_debug]", "notifyType = " + notifyType);
            Iterator<String> it = userData.iterator();

            while (it.hasNext())
            {
              String paramString = (String)it.next();
              Log.e("[wechat_debug]", "userData = " + paramString);
              String[] params = paramString.split(",");
              if ((params == null) || (params.length < 3))
              {
                Log.e("[wechat_debug]", "wrong userData");
                return;
              }

              Log.e("[wechat_debug]", "receive wechat data ... ");
              Log.e("[wechat_debug]", "openID:" + params[0]));
            }
        }
      }
  }

利用微信OpenAPI实现发送微信文字

发送文字信息,可以直接使用微信SDK发送信息到微信,参考Android-接入微信分享
再利用AccesibilityService自动点击分享和返回按钮即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
String clazzName = event.getClassName().toString();
AccessibilityNodeInfo nodeInfo = event.getSource();
if(clazzName.equals("com.tencent.mm.ui.transmit.SelectConversationUI"))
sendToUser(nodeInfo, 好友昵称);
else if(clazzName.equals("com.tencent.mm.ui.base.aa"))
clickByText(nodeInfo, "分享");
else if(clazzName.equals("android.widget.LinearLayout")){
if(needSendMsg){
needSendMsg = false;
back(0);
}
}
}

private void sendToUser(AccessibilityNodeInfo nodeInfo, String user){
if (null == nodeInfo) return;
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(user);
if (null != list && list.size() > 0) {
AccessibilityNodeInfo node = list.get(list.size() - 1);
while(!node.isClickable()){
node = node.getParent();
if(node.isClickable()){
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
}
}
}

private void clickByText(AccessibilityNodeInfo nodeInfo, String text){
if (null == nodeInfo) return;
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(text);
if (null != list && list.size() > 0) {
AccessibilityNodeInfo node = list.get(list.size() - 1);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}

利用微信OpenAPI实现发送微信语音

首先,需要通过微信的鉴权,拿到AccessToken,流程参考移动应用微信登录开发指南
拿到AccessToken后,开始录音,注意必须是amr格式:
if(mediaRecorder == null){
                myButton.setText("停止录音");
                mediaRecorder = new MediaRecorder();   
                mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);    
                mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);   
                mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);  
                mediaRecorder.setAudioSamplingRate(8000);
                mediaRecorder.setAudioEncodingBitRate(16);
                File audioFile;
                try {
                    audioFile = File.createTempFile("record_", ".amr"); 
                    filePath = audioFile.getAbsolutePath();
                    mediaRecorder.setOutputFile(audioFile.getAbsolutePath());  
                    mediaRecorder.prepare();  
                    mediaRecorder.start();  
                    Log.e("Recorder", "Record Begin");
                } catch (IOException e) {
                    Log.e("Recorder", e.getMessage());
                }
            }else{
                myButton.setText("开始录音");
                mediaRecorder.stop();
                Log.e("Recorder", "Record End");
            }
 }
录音完成后,开始利用微信的OpenAPI获取MediaAccessToken,用于后面的上传录音文件:
new GetMediaAccessTokenThread().start();

public class GetMediaAccessTokenThread extends Thread{

final static String TAG = "GetMediaAccessTokenThread";

@Override
public void run() {

    StringBuffer stringBuffer;
    do
    {
      try
      {
        HttpsURLConnection con = (HttpsURLConnection)new URL(String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", new Object[] { APP_KEY, SECRET_KEY })).openConnection();
        con.setDoOutput(true);
        con.setDoInput(true);
        con.connect();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(con.getInputStream()));
        stringBuffer = new StringBuffer();
        while (true)
        {
          String line = bufferedReader.readLine();
          if (line == null)
            break;
          stringBuffer.append(line);
        }
        String accessToken = ((JSONObject)new JSONTokener(stringBuffer.toString()).nextValue()).getString("access_token");
        Log.e(TAG, "Media Access Token : " + accessToken);
      }
      catch (Exception exception)
      {
        Log.e(TAG, "Exception in GetHttps : " + exception.getMessage());
        return;
      }
    }while (stringBuffer.toString() == null);

}

}

获取MediaAccessToken后,上传录音文件,获取MediaID:
public class UploadMediaThread extends Thread{

String accessToken = null;
final static String TAG = "UploadMediaThread";

public UploadMediaThread(String accessToken){
    this.accessToken = accessToken;
}

@Override
public void run(){
    String str = "--" + "---part---" + "\r\n";
    String url = String.format("http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=voice", new Object[] { accessToken });
    Log.e(TAG, "uploadVoice http = " + url);
    int length;
    try
    {
        HttpURLConnection con = (HttpURLConnection)new URL(url).openConnection();
        con.setReadTimeout(10000);
        con.setRequestMethod("POST");
        con.setDoOutput(true);
        con.setDoInput(true);
        con.setRequestProperty("Connection", "keep-alive");
        con.setRequestProperty("Charsert", "UTF-8");
        con.setRequestProperty("Content-type", "multipart/form-data;boundary=" + "---part---");
        File voiceFile = new File(录音文件路径);
        if ((voiceFile == null) || (!((File)voiceFile).exists()) || (!((File)voiceFile).isFile()))
        {
            Log.e(TAG, "voiceFile error");
            return;
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("--" + str + "\r\n");
        stringBuilder.append("Content-Disposition: form-data;name=\"voice\";filename=\"" + voiceFile.getName() + "\";filelength=\"" + voiceFile.length() + "\"\r\n");
        stringBuilder.append("Content-Type: application/octet-stream; \r\n\r\n");
        OutputStream outputStream = con.getOutputStream();
        outputStream.write(stringBuilder.toString().getBytes());
        FileInputStream inputStream = new FileInputStream(voiceFile);
        byte bytes[] = new byte[1024];
        while (true)
        {
            length = inputStream.read(bytes);
            if (length == -1)
                break;
            outputStream.write(bytes, 0, length);
        }

        inputStream.close();
        outputStream.write("\r\n".getBytes());
        outputStream.write(("--" + str + "--\r\n").getBytes());
        outputStream.flush();
        outputStream.close();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(con.getInputStream()));
        StringBuffer stringBuffer = new StringBuffer();
        while (true)
        {
            String line= bufferedReader.readLine();
            if (line == null)
                break;
            stringBuffer.append(line);
        }
        bufferedReader.close();
        Log.e(TAG, "uploadVoice : " + stringBuffer.toString());
        JSONObject resultJSON = (JSONObject)new JSONTokener(stringBuffer.toString()).nextValue();
        if (resultJSON.isNull("media_id") && !resultJSON.isNull("errcode"))
        {
            Log.e(TAG, "uploadVoice Error: " + resultJSON.toString());
            return;
        }
        String mediaID = resultJSON.getString("media_id");
        Log.e(TAG, "uploadVoice MediaID: " + mediaID);

    }catch (Exception exception){
        Log.e(TAG, "Exception in GetHttps : " + exception.getMessage());
        return;
    }
}

}
获取MediaID后,直接发送给用户的OpenID:
public class SendVoiceThread extends Thread{

String accessToken = null;
String toUserID = null;
String mediaID = null;
final static String TAG = "SendVoiceThread";

public SendVoiceThread(String accessToken, String toUserID, String mediaID){
    this.accessToken = accessToken;
    this.toUserID = toUserID;
    this.mediaID = mediaID;
}

@Override
public void run() {
     String url = String.format("https://api.weixin.qq.com/sns/authorize/message?access_token=%s&openid=%s", new Object[] { accessToken, toUserID});
     Log.e("SendVoiceThread", "sendVoiceMsg https = " + (String)url);
     String toSendJSON;
     try{
         HttpsURLConnection con = (HttpsURLConnection)new URL((String)url).openConnection();
         con.setReadTimeout(8000);
         con.setRequestMethod("POST");
         con.setDoOutput(true);
         con.setDoInput(true);
         toSendJSON = "{\"touser\":\"" + toUserID + "\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"" + mediaID + "\"}}";
         Log.e(TAG, "sendVoiceMsg json = " + toSendJSON);
         OutputStream outputStream = con.getOutputStream();
         outputStream.write(toSendJSON.getBytes());
         outputStream.flush();
         outputStream.close();
         BufferedReader inputStream = new BufferedReader(new InputStreamReader(con.getInputStream()));
         StringBuffer stringBuffer = new StringBuffer();
          while (true)
          {
            String line = inputStream.readLine();
            if (line == null)
              break;
            stringBuffer.append(line);
          }
          Log.e(TAG, "sendVoiceMsg return : " + stringBuffer.toString());
          JSONObject resultJSON = (JSONObject)new JSONTokener(stringBuffer.toString()).nextValue();
          if (!resultJSON.isNull("errcode"))
          {
              int errCode = resultJSON.getInt("errcode");
          }else{
              Log.e(TAG, " >>> sendVoiceMsg Msg success. ");
          }
        }
        catch (Exception localException)
        {
            Log.e(TAG, "Exception in GetHttps : " + localException.getMessage());
            return;
        }
}

}

这里,注意accseeToken是步骤(1)获取的,不是MediaAccessToken。没有出现异常的话,录音就发送成功了~