Android消息中心信息获取与发布
Charles Chan @ 2014-09-02 #Notification @Android
Contents:
本文讲述一下如何在Android手机中获取到消息中心的实时数据,并发送给其他设备。
因为本人对蓝牙部分不熟悉,所以这里采用了UDP的形式进行数据的发送。
实际上,此处主要的难点是icon图片的封装和解包。至于通讯是依赖于蓝牙还是UDP还是其他什么方式,就仅仅是发送命令不同罢了。
下面是消息中心数据的获取。实际上非常简单,只要实现了NotificationListenerService 里面的几个函数就可以了。
MyNotificationService.java
package com.zeerd.emneg.mynotificationcenter;
import android.annotation.TargetApi;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
public class MyNotificationService extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification arg0) {
// TODO Auto-generated method stub
SendNotification sn = new SendNotification(this);
sn.posted(arg0);
}
@Override
public void onNotificationRemoved(StatusBarNotification arg0) {
// TODO Auto-generated method stub
SendNotification sn = new SendNotification(this);
sn.removed(arg0);
}
}
需要注意的就是要在AndroidManifest.xml文件中配置service:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zeerd.emneg.mynotificationcenter"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.zeerd.emneg.mynotificationcenter.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyNotificationService"
android:label="@string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
</manifest>
下面的类用于消息的打包和发送。为了便于测试,这里将消息数据通过sendBroadcast发送到了MainActivity一份。实际的使用中是不需要的。
SendNotification.java
package com.zeerd.emneg.mynotificationcenter;
import java.lang.NullPointerException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import android.annotation.TargetApi;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
public class SendNotification {
final static private String TAG = "SendOfMyNotification";
private Context mContext = null;
private UdpClient udpClient = null;
public SendNotification(Context context) {
mContext = context;
udpClient = new UdpClient();
new Thread(udpClient).start();
SharedPreferences settings = mContext
.getSharedPreferences("Setting", 0);
String ip = settings.getString("server_ip", "127.0.0.1");
udpClient.setServerIp(ip, 5678);
Log.d(TAG, "Connect to " + ip + ":" + 5678);
}
public boolean posted(StatusBarNotification arg0) {
toActivity.posted(mContext, arg0.getNotification().extras);
udpClient.posted(mContext, arg0.getId(), arg0.isOngoing(),
arg0.getNotification().extras);
return true;
}
public boolean removed(StatusBarNotification arg0) {
toActivity.removed(mContext, arg0.getNotification().extras);
udpClient.removed(mContext, arg0.getId(), arg0.isOngoing(),
arg0.getNotification().extras);
return true;
}
private NotificationInfo getInfo(Context c, Bundle extras) {
NotificationInfo info = new NotificationInfo();
info.action = NotificationInfo.ACTION_UNDEF;
try {
info.title = extras.getString(Notification.EXTRA_TITLE);
} catch (NullPointerException e) {
// e.printStackTrace();
}
try {
info.icon = extras.getInt(Notification.EXTRA_SMALL_ICON);
} catch (NullPointerException e) {
// e.printStackTrace();
}
try {
info.largeIcon = ((Bitmap) extras
.getParcelable(Notification.EXTRA_LARGE_ICON));
} catch (NullPointerException e) {
// e.printStackTrace();
try {
info.largeIcon = ((Bitmap) extras
.getParcelable(Notification.EXTRA_LARGE_ICON_BIG));
} catch (NullPointerException e1) {
// e1.printStackTrace();
try {
info.largeIcon = ((Bitmap) extras
.getParcelable(Notification.EXTRA_PICTURE));
} catch (NullPointerException e2) {
// e2.printStackTrace();
}
}
}
try {
info.text = extras.getCharSequence(Notification.EXTRA_TEXT)
.toString();
} catch (NullPointerException e) {
// e.printStackTrace();
}
try {
info.subText = extras.getCharSequence(Notification.EXTRA_SUB_TEXT)
.toString();
} catch (NullPointerException e) {
// e.printStackTrace();
}
Log.d(TAG, "Title[" + info.title + "] Text[" + info.text + "] SubText["
+ info.subText + "] Icon[" + info.icon + "] LargeIcon["
+ ((info.largeIcon == null) ? "false]" : "true]"));
return info;
}
private static class NotificationInfo {
public static final int ACTION_UNDEF = 0;
public static final int ACTION_POSTED = 1;
public static final int ACTION_REMOVED = 2;
public String title = "";
public String text = "";
public String subText = "";
public int icon = -1;
public Bitmap largeIcon = null;
public int action = ACTION_UNDEF;
public int id = -1;
boolean isOngoing = false;
}
// for easy testing , we send the notification's infos to the MainActivity,
// too.
public static class toActivity {
public static boolean posted(Context c, Bundle extras) {
Intent intent = new Intent(MainActivity.INTENT_ACTION_NOTIFICATION);
intent.putExtras(extras);
c.sendBroadcast(intent);
return true;
}
public static boolean removed(Context c, Bundle extras) {
Intent intent = new Intent(MainActivity.INTENT_ACTION_NOTIFICATION);
intent.putExtras(extras);
c.sendBroadcast(intent);
return true;
}
}
public class UdpClient implements Runnable {
private InetAddress serverAddr = null;
DatagramSocket socket = null;
private int port = 0;
private NotificationInfo info = null;
public void setServerIp(String ip, int p) {
try {
serverAddr = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
}
port = p;
}
public boolean posted(Context c, int id, boolean isOngoing,
Bundle extras) {
info = getInfo(c, extras);
info.action = NotificationInfo.ACTION_POSTED;
info.id = id;
info.isOngoing = isOngoing;
return true;
}
public boolean removed(Context c, int id, boolean isOngoing,
Bundle extras) {
info = getInfo(c, extras);
info.action = NotificationInfo.ACTION_REMOVED;
info.id = id;
info.isOngoing = isOngoing;
return true;
}
private byte[] makeSendBuf() {
byte[] buf;
byte[] id = intToByteArray(info.id);
byte[] idLen = intToByteArray(id.length);
byte[] isOngoing = intToByteArray(info.isOngoing ? 1 : 0);
byte[] isOngoingLen = intToByteArray(isOngoing.length);
byte[] title = info.title.getBytes();
byte[] titleLen = intToByteArray(title.length);
byte[] text = info.text.getBytes();
byte[] textLen = intToByteArray(text.length);
byte[] sub = info.subText.getBytes();
byte[] subLen = intToByteArray(sub.length);
byte[] icon = intToByteArray(info.icon);
byte[] iconLen = intToByteArray(icon.length);
byte[] largeIcon = bitmapToByteArray(info.largeIcon);
byte[] largeIconWidth = null;
byte[] largeIconHeight = null;
if (info.largeIcon != null) {
largeIconWidth = intToByteArray(info.largeIcon.getWidth());
largeIconHeight = intToByteArray(info.largeIcon.getHeight());
}
byte[] largeIconLen = intToByteArray((largeIcon == null) ? 0
: info.largeIcon.getByteCount());
buf = concatBytes(idLen, id);
buf = concatBytes(buf, isOngoingLen);
buf = concatBytes(buf, isOngoing);
buf = concatBytes(buf, titleLen);
buf = concatBytes(buf, title);
buf = concatBytes(buf, textLen);
buf = concatBytes(buf, text);
buf = concatBytes(buf, subLen);
buf = concatBytes(buf, sub);
buf = concatBytes(buf, iconLen);
buf = concatBytes(buf, icon);
buf = concatBytes(buf, largeIconLen);
buf = concatBytes(buf, largeIconWidth);
buf = concatBytes(buf, largeIconHeight);
buf = concatBytes(buf, largeIcon);
return buf;
}
@Override
public void run() {
if (serverAddr == null) {
return;
}
Log.d(TAG, "Client: Start connecting\\n");
try {
socket = new DatagramSocket();
} catch (SocketException e2) {
e2.printStackTrace();
}
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
if (info != null) {
try {
byte[] buf = makeSendBuf();
// the max size of the send buffer is 4097. we made the
// send buffer to 4000 for prefix and reserve.
// the prefix protocol format is:
// byte1 : action
// byte2~5 : the start offset (unit by byte) in the
// whole sending buffer
// byte6~9 : the total size of the sending buffer
for (int i = 0; i < buf.length; i += 4000) {
byte[] s = new byte[4009], n, t;
int sLen = ((buf.length - i) < 4000) ? buf.length
- i : 4000;
n = intToByteArray(i);
t = intToByteArray(buf.length);
s[0] = (byte) info.action;
System.arraycopy(n, 0, s, 1, 4);
System.arraycopy(t, 0, s, 5, 4);
System.arraycopy(buf, i, s, 9, sLen);
DatagramPacket packet = new DatagramPacket(s,
sLen + 8, serverAddr, port);
socket.send(packet);
}
Log.d(TAG, "Client: Message sent\\n");
} catch (Exception e) {
Log.d(TAG, "Client: Error!\\n");
e.printStackTrace();
}
info = null;
}
}
}
}
private static byte[] intToByteArray(int a) {
byte[] ret = new byte[4];
ret[3] = (byte) (a & 0xFF);
ret[2] = (byte) ((a >> 8) & 0xFF);
ret[1] = (byte) ((a >> 16) & 0xFF);
ret[0] = (byte) ((a >> 24) & 0xFF);
return ret;
}
private static byte[] bitmapToByteArray(Bitmap b) {
if (b != null) {
int bytes = b.getByteCount();
ByteBuffer buffer = ByteBuffer.allocate(bytes);
b.copyPixelsToBuffer(buffer);
return buffer.array();
} else {
return null;
}
}
private static byte[] concatBytes(byte[] a, byte[] b) {
if (b == null) {
return a;
} else {
byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}
}
MainActivity.java
package com.zeerd.emneg.mynotificationcenter;
import java.util.Properties;
import android.os.Bundle;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
@TargetApi(19)
public class MainActivity extends Activity {
public static String INTENT_ACTION_NOTIFICATION = "com.zeerd.emneg.mynotificationcenter.notification";
NotificationManager mNotificationMgr = null;
Button mBtn = null;
Context mContext = this;
protected Properties properties = null;
protected TextView title;
protected TextView text;
protected TextView subtext;
protected ImageView largeIcon;
protected EditText etIp;
protected MyReceiver mReceiver = new MyReceiver();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
properties = new Properties();
mNotificationMgr = (NotificationManager) this
.getSystemService(Context.NOTIFICATION_SERVICE);
title = (TextView) findViewById(R.id.nt_title);
text = (TextView) findViewById(R.id.nt_text);
subtext = (TextView) findViewById(R.id.nt_subtext);
largeIcon = (ImageView) findViewById(R.id.nt_largeicon);
etIp = (EditText) findViewById(R.id.editText1);
mBtn = (Button) findViewById(R.id.button1);
mBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
writeProperties();
Bitmap aBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher);
Notification.Builder b = new Notification.Builder(mContext);
b.setContentTitle("Test");
b.setContentText("subject");
b.setSubText("content");
b.setSmallIcon(R.drawable.ic_launcher);
b.setLargeIcon(aBitmap);
Notification n = b.build();
mNotificationMgr.notify(null, 0, n);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onResume() {
super.onResume();
if (mReceiver == null)
mReceiver = new MyReceiver();
registerReceiver(mReceiver,
new IntentFilter(INTENT_ACTION_NOTIFICATION));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
@TargetApi(19)
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
Bundle extras = intent.getExtras();
String notificationTitle = extras
.getString(Notification.EXTRA_TITLE);
Bitmap notificationLargeIcon = ((Bitmap) extras
.getParcelable(Notification.EXTRA_LARGE_ICON));
CharSequence notificationText = extras
.getCharSequence(Notification.EXTRA_TEXT);
CharSequence notificationSubText = extras
.getCharSequence(Notification.EXTRA_SUB_TEXT);
title.setText(notificationTitle);
text.setText(notificationText);
subtext.setText(notificationSubText);
if (notificationLargeIcon != null) {
largeIcon.setImageBitmap(notificationLargeIcon);
}
}
}
}
private void writeProperties() {
SharedPreferences settings = mContext
.getSharedPreferences("Setting", 0);
Editor editor = settings.edit();
editor.putString("server_ip", etIp.getText().toString());
editor.commit();
}
}
接收端的代码如下:
package com.zeerd.emneg.mynotificationreceiver;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final String TAG = "ReceiverOfMyNotification";
public static final int SERVERPORT = 5678;
public Context mContext = this;
public ListView mListViewOngoing;
public ListView mListViewNonOngoing;
public List<NotificationInfo> mOngoingList;
public List<NotificationInfo> mNonOngoingList;
public Handler handler;
@SuppressLint("HandlerLeak")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mOngoingList = new ArrayList<NotificationInfo>();
mNonOngoingList = new ArrayList<NotificationInfo>();
mListViewOngoing = (ListView) findViewById(R.id.listView2);
mListViewNonOngoing = (ListView) findViewById(R.id.listView1);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
NotificationInfo info = (NotificationInfo) msg.obj;
if (info.isOngoing) {
mOngoingList.add(0, info);
ListAdapter list = new OngoingAdapter(mContext);
mListViewOngoing.setAdapter(list);
} else {
mNonOngoingList.add(0, info);
ListAdapter list1 = new NonOngoingAdapter(mContext);
mListViewNonOngoing.setAdapter(list1);
}
}
};
new Thread(new Server()).start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public class Server implements Runnable {
byte[] fullBuf = new byte[100 * 1024];
@Override
public void run() {
try {
Log.d(TAG, "Server: Start connecting");
@SuppressWarnings("resource")
DatagramSocket socket = new DatagramSocket(SERVERPORT);
byte[] buf = new byte[4097];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while (true) {
socket.receive(packet);
int index = byteArrayToInt(buf, 1);
int total = byteArrayToInt(buf, 5);
if ((total - index) < 4000) {
Log.d(TAG, "Server: Receiving" + index + "/" + total);
System.arraycopy(buf, 9, fullBuf, index, total - index);
String debug_log = "";
for (int i = 0; i < 100; i++) {
debug_log += " "
+ Integer.toString((int) fullBuf[i], 16);
}
Log.d(TAG, "Server: Receiving[0..100] " + debug_log);
NotificationInfo info = getInfo(fullBuf);
info.action = (int) buf[0];
Log.d(TAG, "Title["
+ info.title
+ "] Text["
+ info.text
+ "] SubText["
+ info.subText
+ "] Icon["
+ info.icon
+ "] LargeIcon["
+ ((info.largeIcon == null) ? "false]"
: "true]"));
Message msg = handler.obtainMessage(0, info);
handler.sendMessage(msg);
} else {
Log.d(TAG, "Server: Receiving" + index + "/" + total);
System.arraycopy(buf, 9, fullBuf, index, 4000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static class NotificationInfo {
public static final int ACTION_UNDEF = 0;
public static final int ACTION_POSTED = 1;
@SuppressWarnings("unused")
public static final int ACTION_REMOVED = 2;
public String title = "";
public String text = "";
public String subText = "";
public int icon = -1;
public Bitmap largeIcon = null;
public int action = ACTION_UNDEF;
public int id = -1;
boolean isOngoing = false;
}
private NotificationInfo getInfo(byte[] buf) {
int idx = 0;
int len = 0;
byte[] text = null;
NotificationInfo info = new NotificationInfo();
try {
len = byteArrayToInt(buf, idx);
text = new byte[len];
System.arraycopy(buf, idx + 4, text, 0, len);
idx += (4 + len);
info.id = byteArrayToInt(text, 0);
len = byteArrayToInt(buf, idx);
text = new byte[len];
System.arraycopy(buf, idx + 4, text, 0, len);
idx += (4 + len);
info.isOngoing = (byteArrayToInt(text, 0) == 0) ? false : true;
len = byteArrayToInt(buf, idx);
text = new byte[len];
System.arraycopy(buf, idx + 4, text, 0, len);
idx += (4 + len);
info.title = new String(text);
len = byteArrayToInt(buf, idx);
text = new byte[len];
System.arraycopy(buf, idx + 4, text, 0, len);
idx += (4 + len);
info.text = new String(text);
len = byteArrayToInt(buf, idx);
text = new byte[len];
System.arraycopy(buf, idx + 4, text, 0, len);
idx += (4 + len);
info.subText = new String(text);
len = byteArrayToInt(buf, idx);
Log.d(TAG, "len of icon=" + len + " idx=" + idx);
if (len > 0) {
text = new byte[len];
System.arraycopy(buf, idx + 4, text, 0, len);
info.icon = byteArrayToInt(text, 0);
}
idx += (4 + len);
len = byteArrayToInt(buf, idx);
Log.d(TAG, "len of large icon=" + len + " idx=" + idx);
if (len > 0) {
byte[] num = new byte[4];
System.arraycopy(buf, idx + 4, num, 0, 4);
int w = byteArrayToInt(num, 0);
System.arraycopy(buf, idx + 8, num, 0, 4);
int h = byteArrayToInt(num, 0);
text = new byte[len];
System.arraycopy(buf, idx + 12, text, 0, len);
ByteBuffer buffer = ByteBuffer.allocate(len);
buffer.put(text);
buffer.rewind();
info.largeIcon = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_8888);
info.largeIcon.copyPixelsFromBuffer(buffer);
}
} catch (Exception e) {
e.printStackTrace();
}
return info;
}
public static int byteArrayToInt(byte[] b, int idx) {
return b[3 + idx] & 0xFF | (b[2 + idx] & 0xFF) << 8
| (b[1 + idx] & 0xFF) << 16 | (b[0 + idx] & 0xFF) << 24;
}
public class OngoingAdapter extends BaseAdapter {
LayoutInflater mInflator;
private Context mContext;
public OngoingAdapter(Context c) {
mContext = c;
mInflator = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return mOngoingList.size();
}
@Override
public Object getItem(int arg0) {
if (mOngoingList != null) {
return mOngoingList.get(arg0);
} else {
return null;
}
}
@Override
public long getItemId(int arg0) {
return arg0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = mInflator.inflate(R.layout.listitem, parent, false);
if ((convertView != null) && (mOngoingList != null)) {
NotificationInfo info = (NotificationInfo) mOngoingList
.get(position);
getListVIew(convertView, info);
}
return convertView;
}
}
public class NonOngoingAdapter extends BaseAdapter {
LayoutInflater mInflator;
private Context mContext;
public NonOngoingAdapter(Context c) {
mContext = c;
mInflator = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
return mNonOngoingList.size();
}
@Override
public Object getItem(int arg0) {
if (mNonOngoingList != null) {
return mNonOngoingList.get(arg0);
} else {
return null;
}
}
@Override
public long getItemId(int arg0) {
return arg0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = mInflator.inflate(R.layout.listitem, parent, false);
if ((convertView != null) && (mNonOngoingList != null)) {
NotificationInfo info = (NotificationInfo) mNonOngoingList
.get(position);
getListVIew(convertView, info);
}
return convertView;
}
}
private void getListVIew(View convertView, NotificationInfo info) {
TextView tvTitle = (TextView) convertView.findViewById(R.id.text1);
TextView tvText = (TextView) convertView.findViewById(R.id.text2);
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
Log.d(TAG, "Set Title[" + info.title + "] Text[" + info.text
+ "] SubText[" + info.subText + "] Icon[" + info.icon
+ "] LargeIcon["
+ ((info.largeIcon == null) ? "false]" : "true]"));
tvTitle.setText(((info.action == NotificationInfo.ACTION_POSTED) ? "[+]"
: "[-]") + info.title);
tvText.setText("(" + info.id + ")" + info.text + "[" + info.subText
+ "]");
iconView.setImageBitmap(info.largeIcon);
}
}
listitem.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<TextView
android:id="@+id/text1"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginTop="6dp"
android:layout_toRightOf="@id/icon" />
<TextView
android:id="@+id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="@id/text1"
android:layout_below="@id/text1"
android:layout_toRightOf="@id/icon"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
效果如下: