智能短信群发机多少钱一台好处在哪里?

黑马智能短信管理第一天
功能描述:
----会话列表 通过异步查询获取会话数据,不会导致ANR(Application Not Response)异常,对于会话可以进行单条和多条的删除,查看会话详情,我们在对ListView进行了优化。
----文件夹视图对信息进行了分类管理:收件箱 发件箱 已发送 草稿箱这四类,并且我们对信息进行了日期分隔显示。
创建了群组数据库,里面有两张表:groups和thread_group,groups是用来存放群组的,thread_group。
----新建信息
号码的输入控件采用的是AutoCompelteTextView.透析filter的过滤机制。
----短息搜索机制
应用能在应用的内部任何一个Activity进行搜索,并且支持全局搜索。
1.对整个页面的布局-----使用TabActivity + TabHost
①布局设置
&RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" &
android:id="@android:id/tabhost"//使用android自带id
android:layout_width="match_parent"
android:layout_height="match_parent" &
&LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" &
&TabWidget
android:id="@android:id/tabs"//使用android自带id
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" &
&/TabWidget&
&!-- 自定义导航 --&
&RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:paddingBottom="10dp"
android:paddingTop="10dp" &
&ImageView
android:id="@+id/iv_slide_backgrounp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/slide_background" /&
&LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" &
&!-- 第一个三分之一 --&
&LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center_horizontal"
android:orientation="horizontal" &
&!-- 会话标签 --&
&LinearLayout
android:id="@+id/ll_conversation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp" &
&ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tab_conversation" /&
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="会话" /&
&/LinearLayout&
&/LinearLayout&
&!-- 第二个三分之一 --&
&LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center_horizontal"
android:orientation="horizontal" &
&!-- 文件夹标签 --&
&LinearLayout
android:id="@+id/ll_folder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical" &
&ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tab_folder" /&
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文件夹" /&
&/LinearLayout&
&/LinearLayout&
&!-- 第三个三分之一 --&
&LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center_horizontal"
android:orientation="horizontal" &
&!-- 群组标签 --&
&LinearLayout
android:id="@+id/ll_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp" &
&ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:background="@drawable/tab_group" /&
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="群组" /&
&/LinearLayout&
&/LinearLayout&
&/LinearLayout&
&/RelativeLayout&
&FrameLayout
android:id="@android:id/tabcontent"//使用android 自带id
android:layout_width="match_parent"
android:layout_height="match_parent" &
&/FrameLayout&
&/LinearLayout&
&/TabHost&
&/RelativeLayout&
②.TabHost的设置以及切换页面
public class MainActivity extends TabActivity implements OnClickListener {
private TabHost tabH
private LinearLayout llC
private LinearLayout llF
private LinearLayout llG
private ImageView slideBackG
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabHost = getTabHost();//可以直接获取到tabhost的控件,id是android自带
addTab("conversation","会话",R.drawable.tab_conversation,ConversationUI.class);
addTab("folder","文件夹",R.drawable.tab_folder,FolderUI.class);
addTab("group","群组",R.drawable.tab_group,GroupUI.class);
llConversation = (LinearLayout) findViewById(R.id.ll_conversation);
llFolder = (LinearLayout) findViewById(R.id.ll_folder);
llGroup = (LinearLayout) findViewById(R.id.ll_group);
llConversation.setOnClickListener(this);
llFolder.setOnClickListener(this);
llGroup.setOnClickListener(this);
slideBackGround = (ImageView) findViewById(R.id.iv_slide_backgrounp);
// 此时 llConversation 仅仅创建了对象,还没有执行onMeasure 和 onLayout 方法 ,所以,此时
getWidth 返回值是0
//System.out.println("llConversation.getWidth()::"+llConversation.getWidth());
// 获得全局viewTree的观察者,并添加 全局的layout监听
llConversation.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
//由于此方法会执行多次,而我们只需要执行一次就可以了,
//所以,在执行一次的时候,将全局的layout监听取消,,此处
this 指的是,内部的匿名对象
llConversation.getViewTreeObserver().removeGlobalOnLayoutListener(this);
System.out.println("llConversation.getWidth()::"+llConversation.getWidth());
//设置图片的宽高,与会话标签相同
int width = llConversation.getWidth();
int height = llConversation.getHeight();
RelativeLayout.LayoutParams layoutParams = (android.widget.RelativeLayout.LayoutParams) slideBackGround.getLayoutParams();
layoutParams.width =
layoutParams.height =
//设置图片的左边距与会话的左边距相同
int left = llConversation.getLeft();// 获得llConversation 在他的父view中左边距
layoutParams.leftMargin =
// 将 llConversation的父view的宽度,设置给 itemLength
itemLength = ((ViewGroup)llConversation.getParent()).getWidth();
* 背景图片移动的单位宽度,即,屏幕的1/3
private int itemL
* 给tabHost添加标签
* @param tag
标签的命名
* @param label 标签显示的文字
* @param iconId 标签显示的图标
* @param clazz
该标签对应的显示内容
private void addTab(String tag, CharSequence label, int iconId, Class&?& clazz){
//创建新的 标签
tag 是该标签的命名,此命名是tabHost用来管理标签的标示。
TabSpec tabSpec =tabHost.newTabSpec(tag);
//给标签添加 文字,和图标
tabSpec.setIndicator(label, getResources().getDrawable(iconId));
// 给标签添加对应的显示内容
Intent intent = new Intent(this,clazz);
tabSpec.setContent(intent);
tabHost.addTab(tabSpec);
* 滑动背景的上一个位置
private int lastP
* 响应标签的点击事件
public void onClick(View v) {
switch (v.getId()) {
case R.id.ll_conversation:// 会话标签
// 判断当前页面是否是 会话页面,如果不是,切换至会话页面,
if(!"conversation".equals(tabHost.getCurrentTabTag())){
tabHost.setCurrentTabByTag("conversation");//根据tabHost的tag来切换页面
slideBackGround.startAnimation(getAnim(0));
lastPosition = 0;
case R.id.ll_folder:// 文件夹标签
// 判断当前页面是否是文件夹页面,如果不是,切换至
if(!"folder".equals(tabHost.getCurrentTabTag())){
tabHost.setCurrentTabByTag("folder");
slideBackGround.startAnimation(getAnim(itemLength));
lastPosition = itemL
case R.id.ll_group:// 群组标签
// 判断当前页面是否是文件夹页面,如果不是,切换至
if(!"group".equals(tabHost.getCurrentTabTag())){
tabHost.setCurrentTabByTag("group");
slideBackGround.startAnimation(getAnim(itemLength*2));
lastPosition = itemLength*2;
private TranslateAnimation getAnim(int destPosition) {
TranslateAnimation anim = new TranslateAnimation(lastPosition, destPosition, 0, 0);
anim.setDuration(500);
anim.setFillAfter(true);
2.会话页面读取会话短信 和 会话页面设置 ConversationUI
①.布局设置
&?xml version="1.0" encoding="utf-8"?&
&LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical" &
android:id="@+id/btn_new_message_conversation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_common_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="新建信息" /&
&LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" &
android:id="@+id/btn_select_all_conversation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="10dp"
android:layout_weight="2"
android:background="@drawable/btn_common_bg"
android:text="全选" /&
android:id="@+id/btn_select_null_conversation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="20dp"
android:layout_weight="2"
android:background="@drawable/btn_common_bg"
android:text="取消选择" /&
&/LinearLayout&
android:id="@+id/lv_conversation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2" &
&/ListView&
android:id="@+id/btn_delete_message_conversation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_common_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="删除信息" /&
&/LinearLayout&
②.ConversationUI 加载会话短信以及设置编辑状态
public class ConversationUI extends Activity{
private ListView listV
private Button btnNewM
private Button btnSelectA
private Button btnSelectN
private Button btnDeleteM
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_conversation);
this.ctx =
//设置页面
flushState();
//准备会话信息数据
prepareData();
* 要查询的列
private String[] projection={
"sms.body AS snippet",
"sms.thread_id AS _id",
"groups.msg_count AS msg_count",
"address as address",
"date as date"
* 短信内容所在列的索引值 为 0
private final int INDEX_BODY = 0;
* 会话ID所在列的索引值 为 1
private final int INDEX_THREAD_ID = 1;
* 短信数量所在列的索引值 为 2
private final int INDEX_MSG_COUNT = 2;
* 短信联系人电话所在列的索引值 为 3
private final int INDEX_ADDRESS = 3;
* 短信日期所在列的索引值 为 4
private final int INDEX_DATE = 4;
* 给listView 准备数据
private void prepareData() {
* 查询数据库,如果数据太多了,会造成ANR异常,所 以,一般都会开子线程,查询数据,然后,用handler将结果,回传
MyQueryHandler queryHandler = new
MyQueryHandler(getContentResolver());
queryHandler.startQuery(234, adapter, MyConstants.URI_CONVERSATION, projection, null, null, " date desc");
private void init() {
btnNewMessage = (Button) findViewById(R.id.btn_new_message_conversation);
btnSelectAll = (Button) findViewById(R.id.btn_select_all_conversation);
btnSelectNull = (Button) findViewById(R.id.btn_select_null_conversation);
btnDeleteMsg = (Button) findViewById(R.id.btn_delete_message_conversation);
listView = (ListView) findViewById(R.id.lv_conversation);
adapter = new MyListAdapter(this, null);
listView.setAdapter(adapter);
private boolean isEditState =
private void flushState() {
if(isEditState){//编辑状态
btnNewMessage.setVisibility(View.GONE);
btnSelectAll.setVisibility(View.VISIBLE);
btnSelectNull.setVisibility(View.VISIBLE);
btnDeleteMsg.setVisibility(View.VISIBLE);
}else{//非编辑状态
btnNewMessage.setVisibility(View.VISIBLE);
btnSelectAll.setVisibility(View.GONE);
btnSelectNull.setVisibility(View.GONE);
btnDeleteMsg.setVisibility(View.GONE);
private MyListA
class MyListAdapter extends CursorAdapter{
public MyListAdapter(Context context, Cursor c) {
super(context, c);
* 当 conterView是null时,需要用户创建view 的时候调
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = View.inflate(ctx, R.layout.list_item_conversation,null);
ViewHolder vh = new ViewHolder();
vh.face = (ImageView) view.findViewById(R.id.iv_face_list_item);
vh.address = (TextView) view.findViewById(R.id.tv_address_list_item);
vh.body = (TextView) view.findViewById(R.id.tv_body_list_item);
vh.date = (TextView) view.findViewById(R.id.tv_date_list_item);
view.setTag(vh);
* 为itemview设置内容时调用
* view 即 newView 方法的返回值
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder vh = (ViewHolder) view.getTag();
//显示短信内容
vh.body.setText(cursor.getString(INDEX_BODY));
//显示日期:
long when = cursor.getLong(INDEX_DATE);
String dateS
//使用系统工具类,判断是否是今天
if(DateUtils.isToday(when)){
dateStr = DateFormat.getTimeFormat(ctx).format(when);
dateStr = DateFormat.getDateFormat(ctx).format(when);
vh.date.setText(dateStr);
//显示联系人地址:
String number = cursor.getString(INDEX_ADDRESS);
int msgcount = cursor.getInt(INDEX_MSG_COUNT);// 获得短信的数量
String name = Tools.findNameByNumber(ctx, number);
if(name == null){//表明无此联系人
vh.address.setText(number+"("+msgcount+")");
vh.address.setText(name+"("+msgcount+")");
//显示联系人的头像:
// 根据电话号码,查询联系人ID
int contactId = Tools.findIDByNumber(ctx, number);
// 如果 ID = -1 表明,无此联系人,此时,应设置一个 未知的联系人头像
if(contactId == -1){
vh.face.setBackgroundResource(R.drawable.ic_unknow_contact_picture);
// 根据ID 获得联系人头像,
Bitmap bitmap = Tools.getFaceById(ctx, ""+contactId);
//如果 bitmpa == null 表明,此联系人,无头像,应设置,一个联系人的默认的头像
if(bitmap == null){
vh.face.setBackgroundResource(R.drawable.ic_contact_picture);
//否则,将bitmap 设置为联系人的头像
vh.face.setBackgroundDrawable(new BitmapDrawable(bitmap));
class ViewHolder{
public ImageV
public TextV
public TextV
public TextV
* 第一次点击menu按键时,调用,用于创建菜单选 项
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, ID_SEARCH, 0, "搜索");
menu.add(0, ID_EDIT, 0, "编辑");
menu.add(0, ID_CANCEL_EDIT, 0, "取消编辑");
private final int ID_SEARCH=100;
private final int ID_EDIT=101;
private final int ID_CANCEL_EDIT=102;
* 每次按menu按键的时候,调用,做一些准备工作
public boolean onPrepareOptionsMenu(Menu menu) {
if(isEditState){//如果是编辑状态
menu.findItem(ID_EDIT).setVisible(false);
menu.findItem(ID_SEARCH).setVisible(false);
menu.findItem(ID_CANCEL_EDIT).setVisible(true);
menu.findItem(ID_EDIT).setVisible(true);
menu.findItem(ID_SEARCH).setVisible(true);
menu.findItem(ID_CANCEL_EDIT).setVisible(false);
* 当选择某一个菜单时,调用
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case ID_SEARCH:
case ID_EDIT:
isEditState =
flushState();
case ID_CANCEL_EDIT:
isEditState =
flushState();
③.异步查询数据库(子线程)-----AsyncQueryHandler封装类(谷歌封装好的)
* 给listView 准备数据
private void prepareData() {
* 查询数据库,如果数据太多了,会造成ANR异常,所 以,一般都会开子线程,查询数据,然后,用handler将结果,回传
MyQueryHandler queryHandler = new
MyQueryHandler(getContentResolver());
queryHandler.startQuery(234, adapter, MyConstants.URI_CONVERSATION, projection, null, null, " date desc");
查询完成后
public class MyQueryHandler extends AsyncQueryHandler{
public MyQueryHandler(ContentResolver cr) {
super(cr);
* 当startQuery 执行完成后,回调 此方法
* token 是startQuery 方法中的第一个参数
* cookie 是startQuery 方法中的第二个参数
* cursor 查询后的结果
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
System.out.println("onQueryComplete : token:"+token);
System.out.println("onQueryComplete : cookie:"+cookie);
Tools.printCursor(cursor);
if(cookie!=null && cookie instanceof CursorAdapter){
CursorAdapter adapter = (CursorAdapter)
// 给adapter 设置新的cursor
adapter.changeCursor(cursor);
}当执行完之后,CursorAdapter中就有了cursor游标.就可以根据查询的数据库,进行数据操作
//显示短信内容
vh.body.setText(cursor.getString(INDEX_BODY));
//显示日期:
long when = cursor.getLong(INDEX_DATE);
String dateS
//使用系统工具类,判断是否是今天
if(DateUtils.isToday(when)){
dateStr = DateFormat.getTimeFormat(ctx).format(when);
dateStr = DateFormat.getDateFormat(ctx).format(when);
vh.date.setText(dateStr);
//显示联系人地址:
String number = cursor.getString(INDEX_ADDRESS);
int msgcount = cursor.getInt(INDEX_MSG_COUNT);// 获得短信的数量
String name = Tools.findNameByNumber(ctx, number);
if(name == null){//表明无此联系人
vh.address.setText(number+"("+msgcount+")");
vh.address.setText(name+"("+msgcount+")");
工具类Tools
public class Tools {
//打印游标的信息
public static void printCursor(Cursor cursor) {
if (cursor == null) {
System.out.println("cursor == null");
if (cursor.getCount() == 0) {
System.out.println("cursor.getCount() == 0");
while (cursor.moveToNext()) {
int columnCount = cursor.getColumnCount();
System.out.println("当前是第几行:" + cursor.getPosition());
for (int i = 0; i & columnC i++) {
String columnName = cursor.getColumnName(i);
String value = cursor.getString(i);
System.out.println(columnName + " : " + value);
* 1.通过电话号码,查询联系人姓名
* @param ctx
* @param number
要查询的电话号码
* @return 返回联系人姓名,或者 null 表明联系人中,无此人
public static String findNameByNumber(Context ctx, String number) {
String name =
Uri uri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number);
Cursor cursor = ctx.getContentResolver().query(uri2,
new String[] { "display_name" }, null, null, null);
if (cursor.getCount() == 0) {
// cursor 返回时,默认指向的是 第一行的上一行,即 -1 行 ,而所有的数据是从 第 0行开始的。
cursor.moveToNext();
name = cursor.getString(0);// cursor 仅查询一列内容,所以取的时候,列的索引值为 0
* 2.通过电话号码,查询联系人ID
* @param ctx
* @param number
要查询的电话号码
* @return 返回联系人ID,或者 -1 表明无此联系人中
public static int findIDByNumber(Context ctx, String number) {
int contactId;
Uri uri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number);
Cursor cursor = ctx.getContentResolver().query(uri2,
new String[] { "_id" }, null, null, null);
if (cursor.getCount() == 0) {
return -1;
// cursor 返回时,默认指向的是 第一行的上一行,即 -1 行 ,而所有的数据是从 第 0行开始的。
cursor.moveToNext();
contactId = cursor.getInt(0);// cursor 仅查询一列内容,所以取的时候,列的索引值为 0
return contactId;
* 3.通过联系人的ID,查询联系人的头像
* @param ctx
* @param contactId
* @return 返回 bitmap 头像, 如果此联系人没有头像的话,返回 null
public static Bitmap getFaceById(Context ctx, String contactId) {
Bitmap bitmap =
Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, contactId);
InputStream input = Contacts.openContactPhotoInputStream(ctx.getContentResolver(), uri);
if(input == null){
bitmap = BitmapFactory.decodeStream(input);
public class MyConstants {
public static Uri URI_CONVERSATION = Uri
.parse("content://sms/conversations");// 会话
* 要查询的列
public static String[] projection = { "sms.body AS snippet",
"sms.thread_id AS _id", "groups.msg_count AS msg_count",
"address as address", "date as date" };
public static final int INDEX_BODY = 0;
public static final int INDEX_THREAD_ID = 1;
public static final int INDEX_MSG_COUNT = 2;
public static final int INDEX_ADDRESS = 3;
public static final int INDEX_DATE = 4;
public static Uri URI_ALL = Uri.parse("content://sms/");// 所有短信的uri
public static String[] projection1 = new String[] { "_id", "address",
"person", "body", "date", "type" };
public static final int INDEX_THREAD_ID_1 = 0;
public static final int INDEX_ADDRESS_1 = 1;
public static final int INDEX_Person_1 = 2;
public static final int INDEX_BODY_1 = 3;
public static final int INDEX_DATE_1 = 4;
public static final int INDEX_TYPE_1 = 5;
* 表示短信的类型 ,1 表示是接收到的短信
public static int TYPE_RECEIVE = 1;
* 表示短信的类型 ,2 表示是发送的短信
public static int TYPE_SEND = 2;
public static Uri CONTENT_URI = Phone.CONTENT_URI;//联系人的uri
* 收件箱的URI
public static Uri URI_INBOX = Uri.parse("content://sms/inbox");
* 发件件箱的URI
public static Uri URI_OUTBOX = Uri.parse("content://sms/outbox");
* 草稿箱的URI
public static Uri URI_DRAFT = Uri.parse("content://sms/draft");
*已发送的URI
public static Uri URI_SENT = Uri.parse("content://sms/sent");
没有更多推荐了,

我要回帖

更多关于 手机短信验证码平台 的文章

 

随机推荐