安卓开发之Android帧动画的结束事件

 更新时间:2016年9月20日 19:57  点击:1449
Android帧动画的结束事件对于做android开发的朋友非常的有用了,下面我们来看看Android帧动画的结束事件的例子。


最近在一个Android应用中,用到了帧动画。这东西的具体介绍就不讲了,网上到处是(虽然基本都是抄来抄去……)。用起来很简单效果也很好,不过这一次我有一个特殊的要求,希望帧动画在播放完毕的时候做一些其他的事情。

在渐变动画中,我们可以很简单的使用监听器:

XXX.setAnimationListener(new AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
    }
    @Override
    public void onAnimationRepeat(Animation animation) {
    }
    @Override
    public void onAnimationEnd(Animation animation) {
        //渐变动画结束....
    }
});

然而帧动画却没有这样方便的功能,为什么??

当然,我们自己写的动画,自己应该是知道它会什么时候结束,启动动画的时候顺便启动一个线程就好了,说说简单做起来毕竟也是要花时间的啊,咱自己写一个类吧,以后就好重复用了,共享出来,要是谁也有这样的要求就直接用吧!

public abstract class MyAnimationDrawable extends AnimationDrawable {
    Handler finishHandler;      // 判断结束的Handler
    public MyAnimationDrawable(AnimationDrawable ad) {
        // 这里得自己把每一帧加进去
        for (int i = 0; i < ad.getNumberOfFrames(); i++) {
            this.addFrame(ad.getFrame(i), ad.getDuration(i));
        }
    }
    @Override
    public void start() {
        super.start();
        /**
         * 首先用父类的start()
         * 然后启动线程,来调用onAnimationEnd()
         */
        finishHandler = new Handler();
        finishHandler.postDelayed(
            new Runnable() {
                public void run() {
                    onAnimationEnd();
                }
            }, getTotalDuration());
    }
    /**
     * 这个方法获得动画的持续时间(之后调用onAnimationEnd())
     */
    public int getTotalDuration() {
        int durationTime = 0;
        for (int i = 0; i < this.getNumberOfFrames(); i++) {
            durationTime += this.getDuration(i);
        }
        return durationTime;
    }
    /**
     * 结束时调用的方法,一定要实现
     */
    abstract void onAnimationEnd();
}
 
/******************************************
 ***************** 使用方法 *****************
 ******************************************/
 
// 新建我们的类的实例
MyAnimationDrawable mad = new MyAnimationDrawable(
    (AnimationDrawable) getResources().getDrawable(R.drawable.anim1)) {
        @Override
        void onAnimationEnd() {
            // 实现这个方法,结束后会调用
        }
    };
// 把这个动画“赐福”给某个ImageView
iv.setBackgroundDrawable(mad);
// 开始吧
mad.start();
但是这个方法未必好,为什么呢,因为我们没法保证动画会完整流畅的播放完毕,也许因为其他的事情终止了,也不知到机器卡的时候会不会使动画播放时间变长,仅用在要求不高的情况下,至少,我这里是够了。要是有更好方法一定告知~

现在的app应用都可以调用手机的摄像头了,这样可以直接拍照了,对于在开发端来讲摄像头调用有许多的一些细节要注意了,下面来看一篇小编整理的Android应用调用摄像头开发例子


这两天玩Android玩的废寝忘食,Blog都好几天没加东西了,惭愧!记录一下这两天最崩溃的一个问题。

好早就装了开发环境,真正着手还是这两天,非常的生疏,虽然有SDK文档,那么多蚊子一般的字,实在没心思慢慢研究。这不想调用摄像头,原以为很容易就能搞定的,累计花了大概有一天的时间才只能保证不出错……至于效果嘛,难说啊!

先看API-examples里有调用 摄像头的例子,在模拟器上虽然看不出什么效果,毕竟还是能执行的,就是一个方块在黑白相间的背景上移动呗。

就这么一个Google提供的范例,传到我的HTC G2上也能一执行就报错,我对Google的尊敬之情顿时减少了0.0001%啊……(当然有可能是G2不够标准,但毕竟其他的软件都是能用的,看来是有不少健壮代码了啊)。联机调试看了一下,出错的这一行(android-7里的):


parameters.setPreviewSize(w, h);

查一下,摄像头不是所有随便的(w, h)都能够认识的,所以呢,我们有了下面这样的增强版:

List<Size> mSupportedPreviewSizes;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
 
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;
 
        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
 
        int targetHeight = h;
 
        // Try to find an size match aspect ratio and size
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
 
        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }
后来的Sample里有了这段代码,看起来强大了不少。然而非常不幸的,首先getSupportedPreviewSizes()这个函数在2.1之后才有,我一开始是打算用1.6开发的……好吧我改,这个先不说,自己的手机已经刷到2.1了,这个函数的返回值居然是null?!如果确实想老版本上也用的话,怎么办??

有鉴于有软件可以达成,所以肯定是有方法的!得这么写:

public class SupportedSizesReflect {
 private static Method Parameters_getSupportedPreviewSizes = null; 
 private static Method Parameters_getSupportedPictureSizes = null;
 
 static {
  initCompatibility();
 };
 
 private static void initCompatibility() {
  try {
   Parameters_getSupportedPreviewSizes = Camera.Parameters.class.getMethod(
     "getSupportedPreviewSizes", new Class[] {});
 
   Parameters_getSupportedPictureSizes = Camera.Parameters.class.getMethod(
     "getSupportedPictureSizes", new Class[] {});
 
  } catch (NoSuchMethodException nsme) {
   nsme.printStackTrace();
   Parameters_getSupportedPreviewSizes = Parameters_getSupportedPictureSizes = null;   
  }  
 }
 
 /**
  * Android 2.1之后有效
  * @param p
  * @return Android1.x返回null
  */
 public static List<Size> getSupportedPreviewSizes(Camera.Parameters p) {
  return getSupportedSizes(p, Parameters_getSupportedPreviewSizes);
 }
 
 public static List<Size> getSupportedPictureSizes(Camera.Parameters p){
  return getSupportedSizes(p, Parameters_getSupportedPictureSizes);
 } 
 
 @SuppressWarnings("unchecked")
 private static List<Size> getSupportedSizes(Camera.Parameters p, Method method){
  try {
   if (method != null) {
    return (List<Size>) method.invoke(p);
   } else {
    return null;
   }
  } catch (InvocationTargetException ite) {
   Throwable cause = ite.getCause();
   if (cause instanceof RuntimeException) {
    throw (RuntimeException) cause;
   } else if (cause instanceof Error) {
    throw (Error) cause;
   } else {
    throw new RuntimeException(ite);
   }
  } catch (IllegalAccessException ie) {
   return null;
  }
 } 
}

啊啊~,リフレクションなんか、大嫌い……然后还要用类似这样的方法调用~


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
 
 Camera.Parameters params = camera.getParameters();
 
 List<Size> supportedPictureSizes
    = SupportedSizesReflect.getSupportedPictureSizes(params);
 List<Size> supportedPreviewSizes
    = SupportedSizesReflect.getSupportedPreviewSizes(params);
 
 if ( supportedPictureSizes != null &&
  supportedPreviewSizes != null &&
  supportedPictureSizes.size() > 0 &&
  supportedPreviewSizes.size() > 0) {
 
  //2.x
  pictureSize = supportedPictureSizes.get(0);
 
  int maxSize = 1280;
  if(maxSize > 0){
   for(Size size : supportedPictureSizes){       
    if(maxSize >= Math.max(size.width,size.height)){
     pictureSize = size;
     break;
    }      
   }
  }
 
  WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
  Display display = windowManager.getDefaultDisplay();  
  DisplayMetrics displayMetrics = new DisplayMetrics();
  display.getMetrics(displayMetrics);
 
  previewSize = getOptimalPreviewSize(
       supportedPreviewSizes,
       display.getWidth(),
       display.getHeight());
 
  params.setPictureSize(pictureSize.width, pictureSize.height);  
  params.setPreviewSize(previewSize.width, previewSize.height);        
 
 }
 this.camera.setParameters(params);
 try {
  this.camera.setPreviewDisplay(holder);
 } catch (IOException e) {
  e.printStackTrace();
 }
 this.camera.startPreview();
}

死机无数次之后总结出来的啊,发现程序写的一个不好强制结束了,摄像头都无法再次启用了,kill都不行,只能重新启动手机才好。重启一次还那么慢,谁知道有比较适合G2的row?

哦还有一个,预览画面90°的,2.X后可以用parameters.set(“rotation”, “90″),之前的话得写成parameters.set(“orientation”, “portrait”)。但是据说不是所有的机器都可以的…


上面讲的是摄像头的初始化,如果觉得这么就万事OK的话,那就大错特错了。接下来的东西让人感到更加头痛。

在我的这个应用里,不需要把拍下来的图片存储,只需要把预览的图片数据处理一下就好,很自然的我只是用了onPreviewFrame调用,考虑处理传递进来的data数据流就是了。

网上很多帖子都说,然后用BitmapFactory的decodeByteArray()函数来解析图片就行了,我试了一下,发现这真是彻头彻尾的谎言,data字节流默认是YCbCr_420_SP(虽然可以改,但其他的格式未必兼容),decodeByteArray()压根儿不认!SDK2.2之后,似乎提供了一个YuvImage的类来转一下(那Google一开始提供这个借口是做什么的?),难道就要把老机给抛弃了么??万万不能啊(穷人最理解穷人们了)!

好在这个世界总是不缺少好人和牛人的,有人提供了这么一段转换的代码:

static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
    final int frameSize = width * height;
 
    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (0xff & ((int) yuv420sp[yp])) - 16;
            if (y < 0) y = 0;
            if ((i & 1) == 0) {
                v = (0xff & yuv420sp[uvp++]) - 128;
                u = (0xff & yuv420sp[uvp++]) - 128;
            }
 
            int y1192 = 1192 * y;
            int r = (y1192 + 1634 * v);
            int g = (y1192 - 833 * v - 400 * u);
            int b = (y1192 + 2066 * u);
 
            if (r < 0) r = 0; else if (r > 262143) r = 262143;
            if (g < 0) g = 0; else if (g > 262143) g = 262143;
            if (b < 0) b = 0; else if (b > 262143) b = 262143;
 
            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
        }
    }
}

我不是很清楚这里面的原理,但是它能在我这里工作,暂时可以了……然后你才可以吧处理完的rgb[]传给decodeByteArray()。

顺便好心的把使用SDK2.2之后的也贴上吧,万一有用呢……

public void onPreviewFrame(byte[] data, Camera arg1) {
    FileOutputStream outStream = null;
    try {
        YuvImage yuvimage = new YuvImage(data,ImageFormat.NV21,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height,null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0,0,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height), 80, baos);
 
        outStream = new FileOutputStream(String.format("/sdcard/%d.jpg", System.currentTimeMillis()));       
        outStream.write(baos.toByteArray());
        outStream.close();
 
        Log.d(TAG, "onPreviewFrame - wrote bytes: " + data.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    }
    Preview.this.invalidate();
}


哦,得到的图像旋转了90°(似乎有的机型设置一下setRotation(90)可以搞定,但还是那句话,不通用啊,况且这个是2.1之后的API)。手动转一下吧……


Matrix matrix = new Matrix();
matrix.postRotate(90);
// 这里的rgb就是刚刚转换处理的东东
Bitmap bmp = Bitmap.createBitmap(rgb, 0, w, w, h, Bitmap.Config.ARGB_4444);
Bitmap nbmp = Bitmap.createBitmap(bmp,
          0, 0, bmp.getWidth(),  bmp.getHeight(), matrix, true);
终于正常了~~~

考虑到需要做识别,自然得先把它转成灰度图像,经典心理公式Gray = R*0.299 + G*0.587 + B*0.114出场了,但是手机的计算速度不那么快,这样的浮点运算还是尽量避免吧~ 于是考虑Gray = (R*299 + G*587 + B*114 + 500) / 1000或者Gray = (R*30 + G*59 + B*11 + 50) / 100。但是除法总是还是不够快,用移位吧……Gray = (R*19595 + G*38469 + B*7472) >> 16,稍微小一点,用Gray = (R*38 + G*75 + B*15) >> 7也足够了。

经过一番努力学习,把写就的代码兴致勃勃的在手机上跑了一下,虽然不够快结果出来了,想想也是大负荷运算啊,自我安慰客户应该可以有这样的耐心吧。

就在这个时候,我突然想起一件很重要的事情!
我需要的是灰度图,也就是亮度风量,而最开始的YUV,不就是亮度色度饱和度么?!那么Y分类不就是我需要的灰度值吗!!我在做什么,辛辛苦苦转成RGB,再转成亮度,吃饱了撑着不是。想到这里我立刻用头撞墙九九一百八十一次,一悼念我那白白死去的脑细胞的在天之灵。立刻重写,删除大量代码,快多了,效果也好~~ 鄙视一下两小时前的自己!

在安卓开发应用中Live Wallpaper设定中加入AdMob广告是一个常事了,我们在下载的app中都会有广告了,这些广告是维护app收入来源的一个不错的方式了。

自从Android 2.1中加入了动态壁纸,一下子牛叉了很多啊,漂亮的壁纸层出不穷,看上去老土的Android手机总算也是可以炫一下了。

动态壁纸基础

制作方法,网上太多了,虽然基本都是抄的,其实都是从sample上发展出来的,我也把要点记一下,每次写新的都要把老的工程打开看,啥记性……

res/xml中指定动态壁纸的xml文件:

<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
 android:settingsActivity="xxx.yyy.LiveWallpaperSettings"
 android:thumbnail="@drawable/icon" />

这里是说明自己图标和设定Activity。
还有一个设定Activity的xml文件,就是普通的PreferenceScreen,省略。

创建WallpaperService的子类,需要再onCreateEngine方法中返回一个Engine,Engine中画画儿,就像用SurfaceView一样。


public class LiveWallpaper extends WallpaperService {
 public static final String SHARED_PREFS_NAME = "setting_file_name";
 @Override
 public void onCreate() {
  super.onCreate();
 }
 @Override
 public void onDestroy() {
  super.onDestroy();
 }
 @Override
 public Engine onCreateEngine() {
  return new TestPatternEngine();
 }
 class TestPatternEngine extends Engine implements
   SharedPreferences.OnSharedPreferenceChangeListener {
  private final Handler mHandler = new Handler();
  private final Runnable mDrawPattern = new Runnable() {
   public void run() {
    drawFrame();
   }
  };
  private boolean mVisible;
  private SharedPreferences mPreferences;
  TestPatternEngine() {
   mPreferences = LiveWallpaper.this.getSharedPreferences(
     SHARED_PREFS_NAME, 0);
   mPreferences.registerOnSharedPreferenceChangeListener(this);
   onSharedPreferenceChanged(mPreferences, null);
  }
  public void onSharedPreferenceChanged(SharedPreferences prefs,
    String key) {
  }
  @Override
  public void onCreate(SurfaceHolder surfaceHolder) {
   super.onCreate(surfaceHolder);
  }
  @Override
  public void onDestroy() {
   super.onDestroy();
   mHandler.removeCallbacks(mDrawPattern);
  }
  @Override
  public void onVisibilityChanged(boolean visible) {
   mVisible = visible;
   if (visible) {
    drawFrame();
   } else {
    mHandler.removeCallbacks(mDrawPattern);
   }
  }
  @Override
  public void onSurfaceChanged(SurfaceHolder holder, int format,
    int width, int height) {
   super.onSurfaceChanged(holder, format, width, height);
   drawFrame();
  }
  @Override
  public void onSurfaceCreated(SurfaceHolder holder) {
   super.onSurfaceCreated(holder);
  }
  @Override
  public void onSurfaceDestroyed(SurfaceHolder holder) {
   super.onSurfaceDestroyed(holder);
   mVisible = false;
   mHandler.removeCallbacks(mDrawPattern);
  }
  @Override
  public void onOffsetsChanged(float xOffset, float yOffset, float xStep,
    float yStep, int xPixels, int yPixels) {
   drawFrame();
  }
  void drawFrame() {
   final SurfaceHolder holder = getSurfaceHolder();
   Canvas c = null;
   try {
    c = holder.lockCanvas();
    if (c != null) {
     // draw something
    }
   } finally {
    if (c != null)
     holder.unlockCanvasAndPost(c);
   }
   mHandler.removeCallbacks(mDrawPattern);
   if (mVisible) {
    mHandler.postDelayed(mDrawPattern, 1000 / 25);
   }
  }
 }
}

还有设定Activity的实现:

public class LiveWallpaperSettings extends PreferenceActivity
    implements SharedPreferences.OnSharedPreferenceChangeListener {
    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        getPreferenceManager().setSharedPreferencesName(LiveWallpaper.SHARED_PREFS_NAME);
        addPreferencesFromResource(R.xml.livewallpaper_settings);
        getPreferenceManager().getSharedPreferences()
            .registerOnSharedPreferenceChangeListener(this);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
    }
 
    @Override
    protected void onDestroy() {
        getPreferenceManager().getSharedPreferences()
            .unregisterOnSharedPreferenceChangeListener(this);
        super.onDestroy();
    }
 
    public void onSharedPreferenceChanged(
        SharedPreferences sharedPreferences, String key) {
    }
}

androidmanifest.xml中一定要加入的:


<service android:name=".LiveWallpaper" android:label="@string/app_name"  android:icon="@drawable/icon">
    <intent-filter>
        <action android:name="android.service.wallpaper.WallpaperService" />
    </intent-filter>
    <meta-data android:name="android.service.wallpaper" android:resource="@xml/livewallpaper" />
</service>
 
<activity android:label="@string/livewallpaper_settings"
   android:name=".LiveWallpaperSettings"
   android:theme="@android:style/Theme.Light.WallpaperSettings"
   android:exported="true"
   android:icon="@drawable/icon">
</activity>
 
<uses-sdk android:minSdkVersion="7" />
<uses-feature android:name="android.software.live_wallpaper" />

“uses-feature android:name=”android.software.live_wallpaper””这句话要慎用啊!Google的电子市场会认这句话,然后把有些可以用的机器过滤掉,比如俺的破机器,本来是不支持的,升级到2.1按说是可以用的。我看了市场上很多有名的动态壁纸,就没有加这句话~~
另外,如果想在动态壁纸中使用OpenGL ES,请参考这篇文章:Android中使用OpenGL ES的一二事。

加入AdMob广告

上面都是废话,我这次想说的主题是如何在壁纸设定界面里加入AdMob广告,好不容易做好的东西,总是要意思意思是吧,加个广告是没法的事情了。

不同于一般的Activity,直接加入AdMob是不行的,连个Layout都没有,而且直接加到壁纸上则非常糟糕,谁也不希望好好的背景上塞一个广告。所以一般动态壁纸的广告都加在设置界面里,这样还真有些不容易。

一般有两种方法,把Admob的adView当做一个Preference,直接加入xml就好。


public class AdmobPreference extends Preference {
 
    public AdmobPreference(Context context) {
        super(context, null);
    }
 
    public AdmobPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    @Override
    protected View onCreateView(ViewGroup parent) {
        //override here to return the admob ad instead of a regular preference display
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        return inflater.inflate(R.layout.admob_preference, null);
    }
 
}

相对的admob_preference的配置:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/ad_layout"
    android:layout_width="fill_parent" android:layout_height="fill_parent">
 
    <com.google.ads.AdView
     xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
  android:id="@+id/ad"
  android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        ads.adSize="BANNER"
        ads:adUnitId="a14e51ca560d266"
        ads:loadAdOnCreate="true"
         />
 
</LinearLayout>
Android AdView missing required XML attribute "adsize"

这里还是有些学问的,网上查一下会有无数的“AdView missing required XML attribute ‘adSize’”的问题,据说是4.1.0之后带来的问题,一定要这么写(将”http://schemas.android.com/apk/lib/com.google.ads”当做一个命名空间,而不是加入自己的程序中)才能不出错,至少大家是如此说的,可惜我不行,怎么写都有这个错,耗费了两个小时尝试了各种写法,还是不行啊!!最后还是写在代码里了:(

不过光这么写貌似还是不行,AdView周围还有留白,貌似是PreferenceActivity里的padding,去不掉,最后还是使用了Tab的方式:


public class SettingsTabActivity extends TabActivity {
 
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.table_layout);
 
        TabHost tabHost = getTabHost();  // The activity TabHost
        TabHost.TabSpec spec;  // Resusable TabSpec for each tab
        Intent intent;  // Reusable Intent for each tab
 
        // Create an Intent for the regular live wallpaper preferences activity
        intent = new Intent().setClass(this, LivePaperSettings.class);
 
        // Initialize a TabSpec and set the intent
        spec = tabHost.newTabSpec("TabTitle").setContent(intent);
        spec.setIndicator("TabTitle");
 
        tabHost.addTab(spec);
 
        tabHost.setCurrentTab(0);   
    }

对应的layout:

 

<?xml version="1.0" encoding="utf-8"?>
 
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
 android:id="@android:id/tabhost" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 
 <LinearLayout android:orientation="vertical"
  android:id="@+id/main_layout"
  android:layout_width="fill_parent" android:layout_height="fill_parent">
 
  <com.google.ads.AdView android:id="@+id/ad"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   ads:backgroundColor="#000000"
   ads:primaryTextColor="#FFFFFF"
   ads:secondaryTextColor="#CCCCCC"
   ads.adSize="BANNER"
   ads:adUnitId="a14e51ca560d266"
   ads:loadAdOnCreate="true" />
 
  <TabWidget android:id="@android:id/tabs"
   android:layout_width="fill_parent" android:layout_height="1dp"
   android:visibility="invisible" />
 
  <FrameLayout android:id="@android:id/tabcontent"
   android:layout_width="fill_parent" android:layout_height="fill_parent"
   android:padding="1dp" />
 </LinearLayout>
</TabHost>
大概就是如此了。

对了还有一个浪费了我两个多小时的问题,对于已经按照的live wallpaper,你修改了setting的Activity,直接覆盖安装是没效果的,点配置还是出现以前的Activity,发现这一点之前,我做了不知几百次修改上传,都快整挂了,Google怎么考虑的?!

单选按钮就是一组按钮只能选中其中的一个了,这个在html中有一个单选按钮组,那么在android开发中要如何来实现,具体的我们来看看。

我们经常在淘宝下单时,会选择尺寸,样式,在一排按钮里设置只能选中一个,我们知道按钮Button是不能唯一选中的,当我们点击一排按钮时我们会发现这一排全会被选择。下面我们就来看一下怎么样实现只选中一个按钮。

首先根据获取的数据动态生成一排按钮。

List<SizeEntity> sizeEntityList=productEntity.getSize();
if(sizeEntityList!=null&&sizeEntityList.size()>0) {
    for (int i = 0; i < sizeEntityList.size(); i++) {
       final SizeEntity size=sizeEntityList.get(i);
       final Button button = new Button(getContext());
       button.setId(i);
       button.setText(sizeEntityList.get(i).getName());
       button.setBackgroundResource(R.drawable.shape_round_corner_0_stroke_bg_grey);
       button.setTextSize(10);
       mSizeContent.addView(button, Math.min(1, mSizeContent.getChildCount()));
   }
}

下面来处理点击事件:

button.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View view) {
          for (int j = 0; j < mSizeContent.getChildCount(); j++) {
               final Button mbutton = (Button) mSizeContent.getChildAt(j);
               if (button == mbutton) {
                  mbutton.setBackgroundResource(R.color.red);
                  mbutton.setTextColor(getResources().getColor(R.color.white));
               } else {
                  mbutton.setBackgroundResource(R.drawable.shape_round_corner_0_stroke_bg_grey);
                  mbutton.setTextColor(getResources().getColor(R.color.black));
                }
            }
 
       }
});

一个raid例子


xml文件

   <LinearLayout 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:orientation="vertical">

    <TextView
        android:id="@+id/txt"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="您的性别为"/>
   
    <RadioGroup
        android:id="@+id/sex"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <RadioButton
            android:id="@+id/male"
            android:text="男"/>
        <RadioButton
            android:id="@+id/female"
            android:text="女"/>
       
    </RadioGroup>

</LinearLayout>


java文件

public class MainActivity extends Activity {
private TextView txt=null;
private RadioGroup sex=null;
private RadioButton male=null;
private RadioButton female=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.txt=(TextView) super.findViewById(R.id.txt);
this.sex=(RadioGroup) super.findViewById(R.id.sex);
this.male=(RadioButton) super.findViewById(R.id.male);
this.female=(RadioButton) super.findViewById(R.id.female);
this.sex.setOnCheckedChangeListener(new OnCheckedChangeListenerImp());
}
private class OnCheckedChangeListenerImp implements OnCheckedChangeListener{

public void onCheckedChanged(RadioGroup group, int checkedId) {
String temp=null;
if(MainActivity.this.male.getId()==checkedId){
temp="男";
}
else if(MainActivity.this.female.getId()==checkedId){
temp="女";
}
MainActivity.this.txt.setText("您的性别是"+temp);
}
}

}

本教程的主要内容是运行两个Android模拟器,然后在这两个模拟器如何实现互相收发短信的功能,这个功能可以说是非常实现的,可以应用app短信实例中。

本文通过运行两个Android模拟器,介绍在Android中如何实现短信服务(SMS,short message service)的功能。通过这个例子,我想带给大家的是:更加熟悉之前介绍过的Android应用程序的概念及技术细节,且通过实例调度大家的兴趣。我之所以选择SMS为例子,主要原因是SMS已经非常成熟了,从中可以发掘更多的信息和技术细节,而且我相信大部分人发短信比打电话多。

1、温故知新

广播接收者:一个广播接收者是这样一个组件,它不做什么事,仅是接受广播公告并作出相应的反应。许多广播源自于系统代码,例如公告时区的改变、电池电量低、已采取图片、用户改变了语言偏好。应用程序也可以发起广播,例如为了他其他程序知道某些数据已经下载到设备且他们可以使用这些数据

BroadcastReceiver类:是接受sendBroadcast()发送的意图(intents)的基类。可以用Context.registerReceiver()动态地注册这个类的实例,或者通过AndroidManifest.xml中<receiver>标签静态发布。

广播接收者不显示一个用户界面。然而,它们启动一个活动去响应收到的信息,或者他们可能使用NotificationManager去通知用户。通知可以使用多种方式获得用户的注意——闪烁的背光、振动设备、播放声音等等。典型的是放在一个持久的图标在状态栏,用户可以打开获取信息。


2、准备工作:SMS涉及的主要类SmsManager

实现SMS主要用到SmsManager类,该类继承自java.lang.Object类,下面我们介绍一下该类的主要成员。

公有方法:

    ArrayList<String> divideMessage(String text)
    当短信超过SMS消息的最大长度时,将短信分割为几块。
    参数:text——初始的消息,不能为空
    返回值:有序的ArrayList<String>,可以重新组合为初始的消息
    static SmsManager getDefault()
    获取SmsManager的默认实例。
    返回值:SmsManager的默认实例
    void SendDataMessage(String destinationAddress, String scAddress, short destinationPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)
    发送一个基于SMS的数据到指定的应用程序端口。
    参数:
    1)、destinationAddress——消息的目标地址
    2)、scAddress——服务中心的地址or为空使用当前默认的SMSC 3)destinationPort——消息的目标端口号
    4)、data——消息的主体,即消息要发送的数据
    5)、sentIntent——如果不为空,当消息成功发送或失败这个PendingIntent就广播。结果代码是Activity.RESULT_OK表示成功,或RESULT_ERROR_GENERIC_FAILURE、RESULT_ERROR_RADIO_OFF、RESULT_ERROR_NULL_PDU之一表示错误。对应RESULT_ERROR_GENERIC_FAILURE,sentIntent可能包括额外的“错误代码”包含一个无线电广播技术特定的值,通常只在修复故障时有用。
    每一个基于SMS的应用程序控制检测sentIntent。如果sentIntent是空,调用者将检测所有未知的应用程序,这将导致在检测的时候发送较小数量的SMS。
    6)、deliveryIntent——如果不为空,当消息成功传送到接收者这个PendingIntent就广播。
    异常:如果destinationAddress或data是空时,抛出IllegalArgumentException异常。
    void sendMultipartTextMessage(String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent>  deliverIntents)
    发送一个基于SMS的多部分文本,调用者应用已经通过调用divideMessage(String text)将消息分割成正确的大小。
    参数:
    1)、destinationAddress——消息的目标地址
    2)、scAddress——服务中心的地址or为空使用当前默认的SMSC
    3)、parts——有序的ArrayList<String>,可以重新组合为初始的消息
    4)、sentIntents——跟SendDataMessage方法中一样,只不过这里的是一组PendingIntent
    5)、deliverIntents——跟SendDataMessage方法中一样,只不过这里的是一组PendingIntent
    异常:如果destinationAddress或data是空时,抛出IllegalArgumentException异常。
    void sendTextMessage(String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent)
    发送一个基于SMS的文本。参数的意义和异常前面的已存在的一样,不再累述。

常量:

    public static final int RESULT_ERROR_GENERIC_FAILURE
    表示普通错误,值为1(0x00000001)
    public static final int RESULT_ERROR_NO_SERVICE
    表示服务当前不可用,值为4 (0x00000004)
    public static final int RESULT_ERROR_NULL_PDU
    表示没有提供pdu,值为3 (0x00000003)
    public static final int RESULT_ERROR_RADIO_OFF
    表示无线广播被明确地关闭,值为2 (0x00000002)
    public static final int STATUS_ON_ICC_FREE
    表示自由空间,值为0 (0x00000000)
    public static final int STATUS_ON_ICC_READ
    表示接收且已读,值为1 (0x00000001)
    public static final int STATUS_ON_ICC_SENT
    表示存储且已发送,值为5 (0x00000005)
    public static final int STATUS_ON_ICC_UNREAD
    表示接收但未读,值为3 (0x00000003)
    public static final int STATUS_ON_ICC_UNSENT
    表示存储但为发送,值为7 (0x00000007)

3、简单的SMS发送程序

1)、首先,编辑布局文件res/layout/main.xml,达到我们想要的结果,界面如下:

image

图1、程序运行界面

对应的xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/txtPhoneNo"/>

    <!-- text's value define in res/values/strings.xml -->
    
    <EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:id="@+id/edtPhoneNo"/>
    
    <TextView android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/txtContent"/>
    
    <EditText android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:minLines="3"
            android:id="@+id/edtContent"/>
   
    <Button android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btnText"
            android:id="@+id/btnSend"/>
</LinearLayout>

相应的要在res/values/strings.xm中添加上面定义的视图的text的值,如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="txtPhoneNo">Please input phone NO:</string>
    <string name="txtContent">Please input SMS\'s content:</string>
    <string name="btnText">send!</string>
    <string name="app_name">SMS</string>
</resources>

2)、做完这些准备工作之后,我么要开始编写代码实现简单的短信发送了。

通过第一步我们构建好界面之后,现在要在上面的基础上编写业务逻辑了。大致过程为:在java源文件中,获取用户在edtPhoneNo中输入的电话号码,edtContent中输入要发送的内容;然后点击btnSend按钮发送短信,要达到这个目的我们要设置btnSend的OnClickListener以达到当点击它触发发送短信的功能,而且要发送短信就要用到我们前面介绍的SmsManager类提供的方法接口。

设置btnSend的OnClickListener的代码如下:

btnSend.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        String phoneNo = edtPhoneNo.getText().toString();
        String message = edtContent.getText().toString();
        if (phoneNo.length() > 0 && message.length() > 0){
            //call sendSMS to send message to phoneNo
            sendSMS(phoneNo, message);
        }
        else
            Toast.makeText(getBaseContext(),
                "Please enter both phone number and message.",
                Toast.LENGTH_SHORT).show();
    }
});

发送短信的功能的代码如下:

private void sendSMS(String phoneNumber, String message) {
    // ---sends an SMS message to another device---
    SmsManager sms = SmsManager.getDefault();
    PendingIntent pi = PendingIntent.getActivity(this, 0,

               new Intent(this,TextMessage.class), 0);
    //if message's length more than 70 ,
    //then call divideMessage to dive message into several part

        //and call sendTextMessage()
    //else direct call sendTextMessage()
    if (message.length() > 70) {
        ArrayList<String> msgs = sms.divideMessage(message);
        for (String msg : msgs) {
            sms.sendTextMessage(phoneNumber, null, msg, pi, null);
        }
    } else {
        sms.sendTextMessage(phoneNumber, null, message, pi, null);
    }
    Toast.makeText(TextMessage.this, "短信发送完成", Toast.LENGTH_LONG).show();
}


如果你已经看了第2节介绍的SmsManager类的介绍,代码应该很好理解。在这里要说明的是,sendTextMessage方法中的第4个和第5个参数PendingIntent设为null,这样的话不能根据短信发出之后的状态做相应的事情,如短信发送失败后的提醒、接收者成功接收后的回执……完整的流程源码如下:

package skynet.com.cnblogs.www;

import java.util.ArrayList;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.view.View;
import android.widget.*;

public class TextMessage extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        btnSend = (Button) findViewById(R.id.btnSend);
        edtPhoneNo = (EditText) findViewById(R.id.edtPhoneNo);
        edtContent = (EditText) findViewById(R.id.edtContent);

        btnSend.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                String phoneNo = edtPhoneNo.getText().toString();
                String message = edtContent.getText().toString();
                if (phoneNo.length() > 0 && message.length() > 0) {
                    // call sendSMS to send message to phoneNo
                    sendSMS(phoneNo, message);
                } else
                    Toast.makeText(getBaseContext(),
                            "Please enter both phone number and message.",
                            Toast.LENGTH_SHORT).show();
            }
        });
    }

    private Button btnSend;
    private EditText edtPhoneNo;
    private EditText edtContent;

    private void sendSMS(String phoneNumber, String message) {
        // ---sends an SMS message to another device---
        SmsManager sms = SmsManager.getDefault();
        PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,
                TextMessage.class), 0);
        // if message's length more than 70 ,
        // then call divideMessage to dive message into several part ,and call
        // sendTextMessage()
        // else direct call sendTextMessage()
        if (message.length() > 70) {
            ArrayList<String> msgs = sms.divideMessage(message);
            for (String msg : msgs) {
                sms.sendTextMessage(phoneNumber, null, msg, pi, null);
            }
        } else {
            sms.sendTextMessage(phoneNumber, null, message, pi, null);
        }
        Toast.makeText(TextMessage.this, "短信发送完成", Toast.LENGTH_LONG).show();
    }
}

3)运行前,还要在清单文件AndroidManifest.xml中加入允许发送短信的权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="skynet.com.cnblogs.www"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".TextMessage"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    <uses-permission android:name="android.permission.SEND_SMS"/>
</manifest>

3.1、运行SMS程序给另一个android模拟器发短信

运行上面我们编写的TextMessage程序,另外在Windows的命令行下切换到tools目录下,并输入emulator ?data smsReceiver,我的如下:

image

这样就会启动一个android模拟器,如下所示:(注意它的编号:5556,就是用这个编号与它通信的)

image

图2、通过emulator启动一个android模拟器

通过我们TextMessage程序启动的android模拟器,编写短信:

image

图3、TextMessage程序个5556模拟器发短信

点击发送之后,通过命令行启动的5556号android模拟器会收到我们刚才发送的短信,如下所示:

image

图4、收到短信的提示


tips:

如果通过命令行的emulator启动android模拟器提示“NO DNS servers found!”,这时我们发的短信模拟器是收不到的。

    在Windows下,如果电脑没有介入网络,即找不DNS服务器的话会出现这种情况!
    在Mac下,如果提示这个警告的话,可以这样解决:检查你是否有/etc/resolv.conf文件,如果没有的话,通过下面的命令行

    ln -s /private/var/run/resolv.conf /etc/resolv.conf可以解决。

4、SMS增强(一)

上面我们实现了一个简单的SMS程序,下面我们要对它进行增强!你肯定已经注意到了,我们上面的SMS程序的sendTextMessage方法中的第4个和第5个参数PendingIntent设为null,即sentIntent和deliveryIntent。

第4个参数-sendIntent,当消息成功发送或发送失败都将被触发。广播接收者的结果码,Activity.RESULT_OK表示成功,或RESULT_ERROR_GENERIC_FAILURE、RESULT_ERROR_RADIO_OFF、RESULT_ERROR_NULL_PDU之一表示错误。对应RESULT_ERROR_GENERIC_FAILURE,sentIntent可能包括额外的“错误代码”包含一个无线电广播技术特定的值,通常只在修复故障时有用。第5个参数-deliveryIntent,仅当目标接收到你的SMS消息才触发。

为了跟踪发出的短信的状态,实现和注册Broadcast Receiver(广播接收者)监听传递给sendTextMessage方法的参数Pending Intents。下面我们就实现和注册这个广播接收者:

String SENT_SMS_ACTION="SENT_SMS_ACTION";
String DELIVERED_SMS_ACTION="DELIVERED_SMS_ACTION";

//create the sentIntent parameter
Intent sentIntent=new Intent(SENT_SMS_ACTION);
PendingIntent sentPI=PendingIntent.getBroadcast(
        this,
        0,
        sentIntent,
        0);

//create the deilverIntent parameter
Intent deliverIntent=new Intent(DELIVERED_SMS_ACTION);
PendingIntent deliverPI=PendingIntent.getBroadcast(
        this,
        0,
        deliverIntent,
        0);

//register the Broadcast Receivers
registerReceiver(new BroadcastReceiver(){
    @Override
    public void onReceive(Context _context,Intent _intent)
    {
        switch(getResultCode()){
            case Activity.RESULT_OK:
                Toast.makeText(getBaseContext(),
                        "SMS sent success actions",
                        Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                Toast.makeText(getBaseContext(),
                        "SMS generic failure actions",
                        Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                Toast.makeText(getBaseContext(),
                        "SMS radio off failure actions",
                        Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                Toast.makeText(getBaseContext(),
                        "SMS null PDU failure actions",
                        Toast.LENGTH_SHORT).show();
                break;
        }
    }
},
new IntentFilter(SENT_SMS_ACTION));
registerReceiver(new BroadcastReceiver(){
    @Override
    public void onReceive(Context _context,Intent _intent)
    {
        Toast.makeText(getBaseContext(),
                "SMS delivered actions",
                Toast.LENGTH_SHORT).show();                
    }
},
new IntentFilter(DELIVERED_SMS_ACTION));

 

在基本完成了要做的工作,接下来要做的就是将sendTextMessage的第4个和第5个参数改为sentPI、deliverPI,这样工作基本完成,修改后的sendSMS方法如下:

private void sendSMS(String phoneNumber, String message) {
    // ---sends an SMS message to another device---
    SmsManager sms = SmsManager.getDefault();
    String SENT_SMS_ACTION = "SENT_SMS_ACTION";
    String DELIVERED_SMS_ACTION = "DELIVERED_SMS_ACTION";

    // create the sentIntent parameter
    Intent sentIntent = new Intent(SENT_SMS_ACTION);
    PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, sentIntent,
            0);

    // create the deilverIntent parameter
    Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION);
    PendingIntent deliverPI = PendingIntent.getBroadcast(this, 0,
            deliverIntent, 0);

    // register the Broadcast Receivers
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context _context, Intent _intent) {
            switch (getResultCode()) {
            case Activity.RESULT_OK:
                Toast.makeText(getBaseContext(),
                        "SMS sent success actions", Toast.LENGTH_SHORT)
                        .show();
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                Toast.makeText(getBaseContext(),
                        "SMS generic failure actions", Toast.LENGTH_SHORT)
                        .show();
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                Toast
                        .makeText(getBaseContext(),
                                "SMS radio off failure actions",
                                Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                Toast.makeText(getBaseContext(),
                        "SMS null PDU failure actions", Toast.LENGTH_SHORT)
                        .show();
                break;
            }
        }
    }, new IntentFilter(SENT_SMS_ACTION));
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context _context, Intent _intent) {
            Toast.makeText(getBaseContext(), "SMS delivered actions",
                    Toast.LENGTH_SHORT).show();
        }
    }, new IntentFilter(DELIVERED_SMS_ACTION));

    // if message's length more than 70 ,
    // then call divideMessage to dive message into several part ,and call
    // sendTextMessage()
    // else direct call sendTextMessage()
    if (message.length() > 70) {
        ArrayList<String> msgs = sms.divideMessage(message);
        for (String msg : msgs) {
            sms.sendTextMessage(phoneNumber, null, msg, sentPI, deliverPI);
        }
    } else {
        sms.sendTextMessage(phoneNumber, null, message, sentPI, deliverPI);
    }
}

运行之后的,发送短信成功的话就可以看到如下界面:

sms

图5、增强SMS(一)

5、SMS增强(二)

下面这个增强是使SMS能够发送二进制数据。要发送数据要使用SmsManager类的sendDataMessage方法,跟sendTextMessage方法类似,只不过该方法多了一个目标端口的参数,构建该SMS的过程跟前面的类似这里就不在累述。

6、温故知新之Intent

此系列前面简单地接受过意图(Intent),这里再次简单介绍一下,在短信接收程序和使用Intent发送SMS中我们要用到。android应用程序的三大组件——Activities、Services、Broadcast Receiver,通过消息触发,这个消息就称作意图(Intent)。下面以Acitvity为例,介绍一下Intent。Android用Intent这个特殊的类实现在Activity与Activity之间的切换。Intent类用于描述应用的功能。在Intent的描述结构中,有两个最重要的部分:动作和动作对应的数据。典型的动作类型有MAIN、VIEW、PICK、EDIT等,我们在短信接收程序中就用到从广播意图中提取动作类型并判断是否是"android.provider.Telephony.SMS_RECEIVED",进而作深一步的处理。而动作对应的数据则以URI的形式表示。例如,要查看一个人的联系方式,需要创建一个动作为VIEW的Intent,以及表示这个人的URI。

通过解析各种Intent,从一个屏幕导航到另一个屏幕是很简单的。当向前导航时,Activity将会调用startActivity("指定一个Intent")方法。然后,系统会在所有已安装的应用程序中定义的IntentFilter中查找,找到最匹配的Intent对应的Activity。新的Activity接收到指定的Intent的通知后,开始运行。当startActivity()方法被调用时,将触发解析指定Intent的动作,该机制提供了两个关键的好处:

    Activity能够重复利用从其他组件中以Intent形式产生的请求。
    Activity可以在任何时候被具有相同IntentFilter的新的Activity取代。

7、准备工作:SmsMessage类

顾名思义,SmsMessage类是一个表示短信的类,为了更好地了解Android的短信机制及以后更好地编写短信相关程序,这里介绍一下该类的公有方法和常量,及嵌套枚举、类成员。

公有方法:

    public static int[]  calculateLength  (CharSequence  msgBody, boolean use7bitOnly)
    参数:
    msgBody-要封装的消息、use7bitOnly-如果为TRUE,不是广播特定7-比特编码的部分字符被认为是单个空字符;如果为FALSE,且msgBody包含非7-比特可编码字符,长度计算使用16-比特编码。
    返回值:
    返回一个4个元素的int数组,int[0]表示要求使用的SMS数量、int[1]表示编码单元已使用的数量、int[2]表示剩余到下个消息的编码单元数量、int[3]表示编码单元大小的指示器。
    public static int[]  calculateLength  (String  messageBody, boolean use7bitOnly)
    参数和返回值跟上面类似
    public static SmsMessage   createFromPdu  (byte[] pdu)
    从原始的PDU(protocol description units)创建一个SmsMessage。这个方法很重要,在我们编写短信接收程序要用到,它从我们接收到的广播意图中获取的字节创建SmsMessage。
    public String  getDisplayMessageBody()
    返回短信消息的主体,或者Email消息主体(如果这个消息来自一个Email网关)。如果消息主体不可用,返回null。这个方法也很重要,在我们编写短信接收程序也要用到。
    public String   getDisplayOriginatingAddress  ()
    返回信息来源地址,或Email地址(如果消息来自Email网关)。如果消息主体不可用,返回null。这个方法在来电显示,短信接收程序中经常用到。
    public String   getEmailBody  ()
    如果isEmail为TRUE,即是邮件,返回通过网关发送Email的地址,否则返回null。
    public int  getIndexOnIcc  ()
    返回消息记录在ICC上的索引(从1开始的)
    public String   getMessageBody  ()
    以一个String返回消息的主体,如果它存在且是基于文本的。
    public SmsMessage.MessageClass   getMessageClass  ()
    返回消息的类。
    public String   getOriginatingAddress  ()
    以String返回SMS信息的来电地址,或不可用时为null。
    public byte[]  getPdu  ()
    返回消息的原始PDU数据。
    public int  getProtocolIdentifier  ()
    获取协议标识符。
    public String   getPseudoSubject  ()
    public String   getServiceCenterAddress  ()
    返回转播消息SMS服务中心的地址,如果没有的话为null。
    public int  getStatus  ()
    GSM:为一个SMS-STATUS-REPORT消息,它返回状态报告的status字段。这个字段表示之前提交的SMS消息的状态。
    CDMA:为不影响来自GSM的状态码,值移动到31-16比特。这个值由一个error类(25-16比特)和一个状态码(23-16比特)组成。
    如果是0,表示之前发送的消息已经被收到。
    public int  getStatusOnIcc  ()
    返回消息在ICC上的状态(已读、未读、已发送、未发送)。有下面的几个值:SmsManager.STATUS_ON_ICC_FREE、SmsManager.STATUS_ON_ICC_READ、SmsManager.STATUS_ON_ICC_UNREAD、SmsManager.STATUS_ON_ICC_SEND、SmsManager.STATUS_ON_ICC_UNSENT这几个值在上篇的SmsManager类介绍有讲到。
    public static SmsMessage.SubmitPdu   getSubmitPdu  (
           String  scAddress, String  destinationAddress,
           short destinationPort, byte[] data,
           boolean statusReportRequested)
    参数:scAddress - 服务中心的地址(Sercvice Centre address,为null即使用默认的)、destinationAddress - 消息的目的地址、destinationPort- 发送消息到目的的端口号、data - 消息数据。
    返回值:一个包含编码了的SC地址(如果指定了的话)和消息内容的SubmitPdu,否则返回null,如果编码错误。
    public static SmsMessage.SubmitPdu   getSubmitPdu  (
           String  scAddress, String  destinationAddress,
           String  message, boolean statusReportRequested)
    和上面类似。
    public static int  getTPLayerLengthForPDU  (String  pdu)
    返回指定SMS-SUBMIT PDU的TP-Layer-Length,长度单位是字节而不是十六进字符。
    public long  getTimestampMillis  ()
    以currentTimeMillis()格式返回服务中心时间戳。
    public byte[]  getUserData  ()
    返回用户数据减去用户数据头部(如果有的话)
    public boolean  isCphsMwiMessage  ()
    判断是否是CPHS MWI消息
    public boolean  isEmail  ()
    判断是否是Email,如果消息来自一个Email网关且Email发送者(sender)、主题(subject)、解析主体(parsed body)可用,则返回TRUE。
    public boolean  isMWIClearMessage  ()
    判断消息是否是一个CPHS 语音邮件或消息等待MWI清除(clear)消息。
    public boolean  isMWISetMessage  ()
    判断消息是否是一个CPHS 语音邮件或消息等待MWI设置(set)消息。
    public boolean  isMwiDontStore  ()
    如果消息是一个“Message Waiting Indication Group:Discard Message”通知且不应该保存,则返回TRUE,否则返回FALSE。
    public boolean  isReplace  ()
    判断是否是一个“replace short message”SMS
    public boolean  isReplyPathPresent  ()
    判断消息的TP-Reply-Path位是否在消息中设置了。
    public boolean  isStatusReportMessage  ()
    判断是否是一个SMS-STATUS-REPORT消息。

常量值:

    public static final int  ENCODING_16BIT :值为3(0x00000003)
    public static final int  ENCODING_8BIT :值为2 (0x00000002)
    public static final int  ENCODING_UNKNOWN :值为0 (0x00000000) ,用户数据编码单元的大小。
    public static final int  MAX_USER_DATA_BYTES :值为140 (0x0000008c),表示每个消息的最大负载字节数。
    public static final int  MAX_USER_DATA_BYTES_WITH_HEADER :134 (0x00000086),如果一个用户数据有头部,该值表示它的最大负载字节数,该值假定头部仅包含CONCATENATED_8_BIT_REFENENCE元素。
    public static final int  MAX_USER_DATA_SEPTETS :值为160 (0x000000a0) ,表示每个消息的最大负载septets数。
    public static final int  MAX_USER_DATA_SEPTETS_WITH_HEADER :值为153 (0x00000099),如果存在用户数据头部,则该值表示最大负载septets数该值假定头部仅包含CONCATENATED_8_BIT_REFENENCE元素。

嵌套枚举成员SmsMessage.MessageClass的枚举值:

    public static final SmsMessage.MessageClass   CLASS_0
    public static final SmsMessage.MessageClass   CLASS_1
    public static final SmsMessage.MessageClass   CLASS_2
    public static final SmsMessage.MessageClass   CLASS_3
    public static final SmsMessage.MessageClass   CLASS_UNKNOWN

嵌套枚举成员SmsMessage.MessageClass的公有方法:

    public static SmsMessage.MessageClass valueOf (String name):返回值的字符串的值
    public static final MessageClass[]   values  ():返回MessageClass的值数组

嵌套类成员SmsMessage.SubmitPdu的字段:

    public byte[]  encodedMessage :编码了的消息
    public byte[]  encodedScAddress :编码的服务中心地址

嵌套类成员SmsMessage.SubmitPdu的公有方法:

    public String   toString  ()
    返回一个包含简单的、可读的这个对象的描述字符串。鼓励子类去重写这个方法,并提供实现对象的类型和数据。默认实现简单地连接类名、@、十六进制表示的对象哈希码,即下面的形式: getClass().getName() + '@' + Integer.toHexString(hashCode())

8、SMS接收程序

当一个SMS消息被接收时,一个新的广播意图由android.provider.Telepony.SMS_RECEIVED动作触发。注意:这个一个字符串字面量(string  literal),但是SDK当前并没有包括这个字符串的引用,因此当要在应用程序中使用它时必须自己显示的指定它。现在我们就开始构建一个SMS接收程序:

1)、跟SMS发送程序类似,要在清单文件AndroidManifest.xml中指定权限允许接收SMS:<uses-permission android:name="android.permission.RECEIVER_SMS"/>

为了能够回发短信,还应该加上发送的权限。

2)、应用程序监听SMS意图广播,SMS广播意图包含了到来的SMS细节。我们要从其中提取出SmsMessage对象,这样就要用到pdu键提取一个SMS PDUs数组(protocol description units—封装了一个SMS消息和它的元数据),每个元素表示一个SMS消息。为了将每个PDU byte数组转化为一个SMS消息对象,需要调用SmsMessage.createFromPdu。

每个SmsMessage包含SMS消息的详细信息,包括起始地址(电话号码)、时间戳、消息体。下面编写一个接收短信的类SmsReceiver代码如下:

package skynet.com.conblogs.www;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class SmsReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context _context, Intent _intent) {
        if (_intent.getAction().equals(SMS_RECEIVER)) {
            SmsManager sms = SmsManager.getDefault();

            Bundle bundle = _intent.getExtras();
            if (bundle != null) {
                Object[] pdus = (Object[]) bundle.get("pdus");
                SmsMessage[] messages = new SmsMessage[pdus.length];
                for (int i = 0; i < pdus.length; i++)
                    messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                for (SmsMessage message : messages) {
                    String msg = message.getMessageBody();
                    String to = message.getOriginatingAddress();
                    if (msg.toLowerCase().startsWith(queryString)) {
                        String out = msg.substring(queryString.length());
                        sms.sendTextMessage(to, null, out, null, null);

                        Toast.makeText(_context, "success",
                                Toast.LENGTH_LONG).show();
                    }
                }
            }
        }
    }
    
    private static final String queryString="@echo";
    private static final String SMS_RECEIVER=
        "android.provider.Telephony.SMS_RECEIVED";
}

上面代码的功能是从接收到的广播意图中提取来电号码、短信内容,然后将短信加上@echo头部回发给来电号码,并在屏幕上显示一个Toast消息提示成功。

9、另一种发送短信的方式:使用Intent

上篇我们使用SmsManager类实现了发送SMS的功能,且并没有用到内置的客户端。实际上,我们很少这样做,自己在应用程序中去完全实现一个完整的SMS客户端。相反我们会去利用它,将需要发送的内容和目的手机号传递给内置的SMS客户端,然后发送。

下面我就向大家介绍如何利用Intent实现利用将我们的东西传递给内置SMS客户端发送我们SMS。为了实现这个功能,就要用到startActivity("指定一个Intent")方法,且指定Intent的动作为Intent.ACTION_SENDTO,用sms:指定目标手机号,用sms_body指定信息内容。java源文件如下所示:

package skynet.com.cnblogs.www;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;


public class TextMessage extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        btnSend = (Button) findViewById(R.id.btnSend);
        edtPhoneNo = (EditText) findViewById(R.id.edtPhoneNo);
        edtContent = (EditText) findViewById(R.id.edtContent);

        btnSend.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                String phoneNo = edtPhoneNo.getText().toString();
                String message = edtContent.getText().toString();
                if (phoneNo.length() > 0 && message.length() > 0) {
                     Intent smsIntent=new Intent(Intent.ACTION_SENDTO,
                             Uri.parse("sms:"+edtPhoneNo.getText().toString()));
                     smsIntent.putExtra("sms_body", edtContent.getText().toString());
                     TextMessage.this.startActivity(smsIntent);
                } else
                    Toast.makeText(getBaseContext(),
                            "Please enter both phone number and message.",
                            Toast.LENGTH_SHORT).show();
            }
        });
    }

    private Button btnSend;
    private EditText edtPhoneNo;
    private EditText edtContent;
}

注意代码中的红色粗体部分,就是实现这个功能的核心代码!布局文件maim.xml和值文件string.xml跟上篇中的一样,这里不再累述。运行结果如下图:

http://images.cnblogs.com/cnblogs_com/skynet/WindowsLiveWriter/768129bbcf7f_E171/image_thumb.png

图2、程序主界面

点击send按钮之后,转到内置的SMS客户端并且将我们输入的值传入了,如下图:

image

图3、内容传至内置SMS客户端

发送之后,5556号android模拟器会收到我们发送的消息,如下图:
image

图5、发送之后5556号android模拟器收到消息


10、增强SMS为MMS

我们讲了这么多,都还只是实现了简单的发生SMS的功能,如果我们想发送图片、音频怎么办(⊙o⊙)?不急,现在我们就将第9节介绍的SMS发送程序改造为MMS。

我们可以附加一个文件到我们的消息做为附件发送,用Intent.EXTRA_STREAM和附件资源的Uri做为参数调用putExtra()方法,附加到信息。并设置Intent的类型为mime-type。要注意的是:内置的MMS并不包括一个ACTION_SENDTO动作的Intent接收器,我们需要使用的动作类型是ACTION_SEND,并且目标手机号不在是使用sms:而是address。主要代码如下:

// Get the URI of a piece of media to attach.
Uri attached_Uri = Uri.parse("content://media/external/images/media/1");
// Create a new MMS intent
Intent mmsIntent = new Intent(Intent.ACTION_SEND, attached_Uri);
mmsIntent.putExtra("sms_body", edtContent.getText().toString());
mmsIntent.putExtra("address", edtPhoneNo.getText().toString());
mmsIntent.putExtra(Intent.EXTRA_STREAM, attached_Uri);
mmsIntent.setType("image/png");
startActivity(mmsIntent);

 

将这段代码替换第9节中的红色粗体代码,就完成而来一个MMS的构建。

[!--infotagslink--]

相关文章

  • 苹果告别“高大上”,越来越向安卓和中国用户靠近

    “一起,让我们将这个世界变得更好。”苹果首席执行官蒂姆 库克对着台下5000多名开发者说道,声音略有些沙哑和颤抖。...2016-07-04
  • ps动态环绕动画效果怎么制作

    ps动态环绕动画效果是现在很多人都非常喜欢的,大多数人还不知道ps动态环绕动画效果怎么制作下面文章就给大家介绍下ps怎么制作科技感十足的动态环绕动画效果,一起来看看...2017-07-06
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • 安卓开发之设置密码只能输入字母和数字的组合

    设置登录密码我们一般会有限制的如由什么组合了,下面我们来看一篇关于安卓开发之设置密码只能输入字母和数字的组合方法,具体的细节如下所示。 无论是电脑还是手机...2016-09-20
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • 自动设置安卓手机wifi代理的PowerShell脚本

    这篇文章主要介绍了自动设置安卓手机wifi代理的PowerShell脚本,帮助大家进行抓包测试,感兴趣的朋友可以了解下...2020-10-17
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 详解vue过度效果与动画transition使用示例

    Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果,Vue 提供了内置的过渡封装组件transition,该组件用于包裹要实现过渡效果的组件...2021-10-10