众所周知,微信本来并没有提供直接收发微信的功能,但是利用一些方法,可以实现直接在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 | private void back(int 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 | else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { |
利用微信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。没有出现异常的话,录音就发送成功了~