zoukankan      html  css  js  c++  java
  • 网络摄像头Androi端显示(mjpeg)源码分析

    main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        
          <TextView 
              android:id="@+id/Tips"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:text="@string/init_tips"
              android:textSize="40px"
              android:textColor="#00ff00"
              />
           <Button 
            android:id="@+id/btn_network" 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/login"
            android:textSize="40px"
            android:textColor="#00ff00"
            />
           <TextView
            android:id="@+id/statc001" 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"      
            android:textSize="40px"
            android:textColor="#00ff00"
            />
           <Button 
               android:id="@+id/btn_video"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@string/move"
               android:textSize="40px"
               />
           
    </LinearLayout>

    flash.xml

    <?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"
        android:orientation="vertical" >
        
           <TextView
            android:id="@+id/hintTv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/login_hint" />
     
    
             <EditText
                android:id="@+id/ip"
                android:hint="@string/ip_hint"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="192.168.0.10"
              />
    
            <EditText
                android:id="@+id/port"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="8080"
                android:gravity="center"
                
              />
            
            
        <Button
            android:id="@+id/connect"
            android:layout_width="fill_parent"
            android:layout_height="40.0dip"
            android:layout_marginLeft="10.0dip"
            android:layout_marginRight="10.0dip"
            android:layout_marginTop="20.0dip"
            android:text="@string/connect"
            android:textColor="#ffffffff"
            android:textSize="18.0sp" />
           
            
           
        
    
    </LinearLayout>

    mainactivity.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
        
        <TextView 
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="视频显示"/>
      
        <com.mjpeg.view.MjpegView
            android:id="@+id/mjpegview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
             />    
    
    </RelativeLayout>

    strings.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <string name="login">连接服务器</string>
        <string name="app_name">梧州学院图书馆刷卡入座系统</string>
        <string name="move">视频</string>
         <string name="init_tips">提示:请先打开WiFi或GPRS再连接网络</string>
         <string name="people1">空座</string>
         <string name="people2">有人</string>
       
         <string name="login_hint">connecting......;</string>
         <string name="ip">IP:</string>
         <string name="ip_hint">请输入IP地址</string>
         <string name="port">Port:</string>
         <string name="port_hint">端口1000到65535</string>
         <string name="connect">链接</string>
         <string name="connect_failed">链接失败</string>
        
    
    </resources>

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="my.work.Library"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk android:minSdkVersion="15" />
    
        <application
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" >
            <activity
                android:name=".WsnActivty"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            
            <activity
                android:name=".FlashActivity"
                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                android:screenOrientation="portrait" >
                <intent-filter>
                    
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            
              <activity
                android:name=".LinearLayout_activity"
                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                android:screenOrientation="portrait" >
                <intent-filter>
                    
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            
        </application>
       
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
        <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    </manifest>

    Generic.java

    package tools;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    import java.net.SocketException;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.Date;
    import java.util.Enumeration;
    import java.util.List;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.media.ThumbnailUtils;
    import android.os.Environment;
    import android.text.format.Time;
    import android.util.Log;
    import android.widget.Toast;
    
    public class Generic {
        public static void showMsg(Context c, String msg, boolean flag){
            if(flag)
                /**
                 * Toast是已经用于显示给用户的控件,显示一段时间后消失,可以多久消失
                 * LENGTH_SHORT:断的显示时间
                 * LENGTH_LONG :长的显示时间
                 */
                Toast.makeText(c, msg, Toast.LENGTH_SHORT).show(); 
            else
                Toast.makeText(c, msg, Toast.LENGTH_LONG).show();
        }
        
     
        // get sysTime
        public static String getSysNowTime() {
            Time localTime = new Time();
            localTime.setToNow();
            String strTime = localTime.format("%Y-%m-%d-%H-%M-%S");
    
            return strTime;
        }
        
        /**
         * 得到sdcard的路径
         * @return 失败返回null
         */
        public static File getSdCardFile(){
            if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
                return Environment.getExternalStorageDirectory();
            }
            return null;
        }
        
        
    
        /**
         * 获取所有连接到本wifi热点的手机IP地址
         */  
        public static ArrayList<String> getConnectedIP() {  
            ArrayList<String> connectedIP = new ArrayList<String>();  
            try {  
                BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp"));  
                String line; 
                br.readLine();
                while ((line = br.readLine()) != null) {  
                    String[] splitted = line.split(" ");  
                    if (splitted != null && splitted.length >= 4) {  
                        String ip = splitted[0];  
                        connectedIP.add(ip);  
                    }  
                }  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
              
            return connectedIP;  
        } 
    
        /**
         * 得到照片的缩略图
         * @param f 照片文件
         * @param w 图片缩小的目标宽度
         * @param h 图片缩小的目标高度
         * @return
         * 1.根据android提供的BitmapFactory.Options类创建并设置好options
         * 2.根据File获得流对象
         * 3.根据BitmapFactory.decodeStream获得位图
         * 4.改变图片为居中缩放,返回位图
         */
        public static Bitmap getShrinkedPic(File f){
            Bitmap smallBitmap = null;
            
            // 直接通过图片路径将图片转化为bitmap,并将bitmap压缩,避免内存溢出
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 10;// 图片宽高都为原来的十分之一
            options.inPreferredConfig = Bitmap.Config.ARGB_4444;// 每个像素占用2byte内存
            options.inPurgeable = true;// 如果 inPurgeable
            // 设为True的话表示使用BitmapFactory创建的Bitmap
            // 用于存储Pixel的内存空间在系统内存不足时可以被回收
            options.inInputShareable = true;
            FileInputStream fInputStream;
            try {
                fInputStream = new FileInputStream(f);
                // 建议使用BitmapFactory.decodeStream
                Bitmap bitmap = BitmapFactory.decodeStream(
                        fInputStream, null, options);// 直接根据图片路径转化为bitmap
                smallBitmap = ThumbnailUtils.extractThumbnail(
                        bitmap, 64, 48);// 创建所需尺寸居中缩放的位图
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return null;
            }
            
            return smallBitmap;
        }
    
        /**
         * Integer值越大,则排在前面
         * @author Administrator
         *
         */
        public static class DescendSortByIndex implements Comparator<Integer>{
            /**
             * @return 负数:object2<object1,正数:object2>object1,0:相等
             */
            @Override
            public int compare(Integer object1, Integer object2) {
                
                return object2.compareTo(object1);
            }
            
        }
        
        /**
         * File的最后修改时间值越大,则排在前面
         * @author Administrator
         *
         */
        public static class DescendSortByTime implements Comparator<File>{
            /**
             * @return 负数:object2<object1,正数:object2>object1,0:相等
             */
            @Override
            public int compare(File object1, File object2) {
                
                return (int) (object2.lastModified() - object1.lastModified());
            }
            
        }
    }

    Mjpeg.java

    package com.mjpeg.view;
    
    import java.io.IOException;
    
    import tools.Generic;
    
    import my.work.Library.R;
    import com.mjpeg.io.MjpegInputStream;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.PorterDuff;
    import android.graphics.PorterDuffXfermode;
    import android.graphics.Rect;
    import android.graphics.Typeface;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    /**
     * 此类继承了SurfaceView实现了SurfaceHolder.Callback接口
     * SurfaceView是视图类(view)的继承类,这个视图里内嵌入了一个专门用于绘制的Surface    ,可以控制这个Surface的格式和尺寸
     * SurfaceView控制这个Surface的绘制位置
     * surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。surfaceview提供了一个可见区域
     * 只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。surface的排版显示受到视图层级关系的影响
     * 它的兄弟视图结点会在顶端显示,这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)
     * 可以通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口
     * surfaceview变得可见时    ,surface被创建;surfaceview隐藏前,surface被销毁;这样能节省资源。如果你要查看 surface被创建和销毁的时机
     * 可以重载surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)
     * surfaceview的核心在于提供了两个线程:UI线程和渲染线程,这里应注意:
     * 1> 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程,渲染线程所要访问的各种变量应该作同步处理。
     * 2> 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,
     * 所以要确保渲染线程访问的是合法有效的surface
     * 整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()获得SurfaceHolder对象(Surface控制器) 
     * ---->SurfaceHolder.addCallback(callback)添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布
     * ----> Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。
     */
    public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {
        /*fps显示位置*/
        public final static int POSITION_UPPER_LEFT = 9;
        public final static int POSITION_UPPER_RIGHT = 3;
        public final static int POSITION_LOWER_LEFT = 12;
        public final static int POSITION_LOWER_RIGHT = 6;
        /*图像显示模式*/
        public final static int STANDARD_MODE = 1;//标准尺寸
        public final static int KEEP_SCALE_MODE = 4;//保持宽高比例
        public final static int FULLSCREEN_MODE = 8;//全屏
    
        private Context mContext = null;
        private MjpegViewThread mvThread = null;
        private MjpegInputStream mIs = null;
        private Paint overlayPaint = null;//用于fps涂层绘画笔
        private boolean bIsShowFps = true;
        private boolean bRun = false;
        private boolean bsurfaceIsCreate = false;
        private int overlayTextColor;
        private int overlayBackgroundColor;
        private int ovlPos;
        private int dispWidth;//MjpegView的宽度
        private int dispHeight;//MjpegView的高度
        private int displayMode;//覆盖模式
    
        public MjpegView(Context context) {
            super(context);
            init(context);
        }
        
        /**
         * 因为在res/layout目录下的main.xml中作为自定义的控件使用了这个类,所以需要给此类提供带有属性形参的构造函数
         * 当在MainActivity通过ID找到这自定义的控件时,该构造函数将被调用,所以将该构造函数设为public    
         * @param context
         * @param attrs
         */
        public MjpegView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
        /**
         * 类的私有方法
         * 1.获得Surface控制器,为Surface控制器添加回调接口
         * 2.新建渲染线程MjpegViewThread
         * 3.新建覆盖画笔,设置文本的对齐方式、文本长度、字体、画笔文本颜色、画笔背景
         * 4.设置覆盖动态文本的覆盖位置 //如果你只需要实现监控画面的功能,3和4步可以省略
         * 5.设置MjpegView显示模式
         * @param context
         */
        private void init(Context context) {
            mContext = context;
            SurfaceHolder holder = getHolder();
            holder.addCallback(this);
            mvThread = new MjpegViewThread(holder, context);
            setFocusable(true);
            overlayPaint = new Paint();
            overlayPaint.setTextAlign(Paint.Align.LEFT);
            overlayPaint.setTextSize(12);
            overlayPaint.setTypeface(Typeface.DEFAULT);
    
            overlayTextColor = Color.RED;
            overlayBackgroundColor = Color.TRANSPARENT;
            ovlPos = MjpegView.POSITION_UPPER_RIGHT;
            displayMode = MjpegView.KEEP_SCALE_MODE;
            
        }
        /**
         *  Surface的任何结构性结构性的改变(如格式,大小)将激发此方法
         *  主要调用渲染线程的setSurfaceSize来设置Surface的宽和高
         */
        public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
            mvThread.setSurfaceSize(w, h);
        }
        /**
         * Surface被销毁之前将激发此方法,这里只设置标记位,表示Surface“被销毁了”
         */
        public void surfaceDestroyed(SurfaceHolder holder) {
            bsurfaceIsCreate = false;
        }
        /**
         * Surface被第一次创建后将激发此方法,这里只设置标记位,表示Surface“被创建了”
         */
        public void surfaceCreated(SurfaceHolder holder) {
            bsurfaceIsCreate = true;
        }
        /**
         * setFps,getFps,set source都在MaiActivity使用
         * @param b
         */
        public void setFps(boolean b) {
            bIsShowFps = b;
        }
        
        public boolean getFps(){
            return bIsShowFps;
        }
    
        public void setSource(MjpegInputStream source) {
            mIs = source;
        }
        
        /**
         * 开始播放线程
         * 设置标记,表示“Surface被创建了”,然后调用渲染线程的的run方法启动渲染
         */
        public void startPlay() {
            if (mIs != null) {
                bRun = true;
                mvThread.start();
            }
        }
    
        /**
         * 停止播放线程
         * 1.先设置标记,表示"停止播放"
         * 2.等待播放线程的退出
         * 3.关闭输入流
         */
        public void stopPlay() {
            bRun = false;
            boolean retry = true;
            while (retry) {
                try {
                    mvThread.join();
                    retry = false;
                } catch (InterruptedException e) {
                }
            }
            
            //线程停止后关闭Mjpeg流(很重要)
            mIs.closeInstance();
        }
        /**
         * mjpegview的获取位图方法,调用渲染线程的获取位图方法
         * @return
         */
        public Bitmap getBitmap(){
            return mvThread.getBitmap();
        }
        
        /**
         * 设置显示模式,在MainActivity的initview调用
         * @param s
         */
        public void setDisplayMode(int s) {
            displayMode = s;
        }
        /**
         * 既然有设置显示模式,就应该也有获得显示模式,这是java在设置方法方面的风格
         * @return
         */
        public int getDisplayMode() {
            return displayMode;
        }
        /**
         * 此渲染线程类在主类上是重点,应该重点掌握
         * @author Administrator
         *
         */
        public class MjpegViewThread extends Thread {
            private SurfaceHolder mSurfaceHolder = null;
            private int frameCounter = 0;
            private long start = 0;
            private Canvas c = null;
            private Bitmap overlayBitmap = null;
            private Bitmap mjpegBitmap = null;
            private PorterDuffXfermode mode = null;
            /**
             * 用一个变量来保存传进来的surfaceHolder
             * 新建一个目的图层和覆盖图层的相交模式,mjpegview为目的图层,覆盖图层为右上角的动态"文本"
             * mode在calculateFps方法里使用
             * @param surfaceHolder:Surfaceview控制器
             * @param context : 上下文环境
             */
            public MjpegViewThread(SurfaceHolder surfaceHolder, Context context) {
                mSurfaceHolder = surfaceHolder;
                mode = new PorterDuffXfermode(PorterDuff.Mode.DST_OVER);/*相交时动态文本覆盖mjpegview*/
            }
            
            public Bitmap getBitmap(){
                return mjpegBitmap;
            }
    
            /**
             * 计算图像尺寸
             * @param bmw bitmap宽
             * @param bmh bitmap高
             * @return 图像矩阵
             */
            private Rect destRect(int bmw, int bmh) {
                int tempx;
                int tempy;
                /**
                 * 显示模式只会在全屏和半屏模式之间切换,根本不会进入STANDARD_MODE模式,故下面的if分支可以去掉
                 */
                if (displayMode == MjpegView.STANDARD_MODE) {
                    tempx = (dispWidth / 2) - (bmw / 2);
                    tempy = (dispHeight / 2) - (bmh / 2);
                    return new Rect(tempx, tempy, bmw + tempx, bmh + tempy);
                }
                /**
                 * 一开始,程序处于KEEP_SCALE_MODE模式,表示半屏显示画面
                 */
                if (displayMode == MjpegView.KEEP_SCALE_MODE) {
                    float bmasp = (float) bmw / (float) bmh;
                    bmw = dispWidth;
                    bmh = (int) (dispWidth / bmasp);/*宽是手机屏幕的一半*/
                    if (bmh > dispHeight) {
                        bmh = dispHeight;
                        bmw = (int) (dispHeight * bmasp);
                    }
                    tempx = (dispWidth / 2) - (bmw / 2);
                    tempy = (dispHeight / 2) - (bmh / 2);
                    /**
                     * Rect(左边,顶边,右边,下边),功能是绘制一个特定坐标的矩形
                     * 简单说就是左上角坐标为(0,0),右下角坐标为(bmw,bmh)
                     */
                    return new Rect(0, 0, bmw + 0, bmh + 0);
                }
                /**
                 * 如果显示模式为全屏,则全屏显示画面
                 * dispWidth和dispHeight在下面的setSurfaceSize方法使用,它们表示mjpegview的宽和高
                 */
                if (displayMode == MjpegView.FULLSCREEN_MODE)
                    return new Rect(0, 0, dispWidth, dispHeight);
                return null;
            }
            /**
             * 当mjpegview发生任何结构性的改变时,将激发此方法,前面也提到,渲染线程使用的各种变量需做同步处理
             * synchronized内的就是同步代码块,为了防止线程之间对临界资源的竞争
             * @param width
             * @param height
             */
            public void setSurfaceSize(int width, int height) {
                synchronized (mSurfaceHolder) {
                    dispWidth = width;
                    dispHeight = height;
                }
            }
            /**
             * 此方法被calculateFps使用,calculateFps又被渲染线程的run方法使用
             * 功能是返回一个位图
             * @param p:覆盖"文本"用的画笔
             * @param text:要绘制的字符 如:帧
             * @return bm
             */
            private Bitmap makeFpsOverlay(Paint p, String text) {
                int nWidth, nHeight;
                
                Rect b = new Rect();
                //int  a = b.left ;
                /**
                 * 功能是获得从原点开始,字符围绕的最小的矩形
                 * text:字符
                 * 0:表示第一个字符
                 * text.length:测量的最后一个字符
                 * b:用于存放获得的字符矩形
                 * 获得了text的边界后就可以得到矩形的宽和高
                 */
                p.getTextBounds(text, 0, text.length(), b);
                nWidth = b.width() + 2;
                nHeight = b.height() + 2;
                /**
                 * 每一个像素4字节,根据上面获得的宽和高返回一个位图
                 */
                Bitmap bm = Bitmap.createBitmap(nWidth, nHeight,
                        Bitmap.Config.ARGB_8888);
                /**
                 * Canvas :画布,这是图像处理的基本单元
                 * 画图时,需要4个重要的元素:
                 * 1.操作像素的位图
                 * 2.绘图到位图的画布 
                 * 3.矩形 
                 * 4. 描述颜色和绘制风格的画笔 
                 * Canvas(bm):构造出一个要绘制到位图的画布
                 */
                Canvas c = new Canvas(bm);
            /**
             * Paint类介绍  
             * Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色,  
             * 样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法,  
             * 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。         
             *   
             * 1.图形绘制        
             * setColor(int color);  
             * 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。    
             * setDither(boolean dither);       
             * setXfermode(Xfermode xfermode);  
             * 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果  
             *   
             * 2.文本绘制  
             * setFakeBoldText(boolean fakeBoldText);  
             * 模拟实现粗体文字,设置在小字体上效果会非常差     
             * setSubpixelText(boolean subpixelText);  
             * 设置该项为true,将有助于文本在LCD屏幕上的显示效果  
             *   
             * setTextAlign(Paint.Align align);  
             * 设置绘制文字的对齐方向    
             * setTextSize(float textSize);  
             * 设置绘制文字的字号大小  
             * setTypeface(Typeface typeface);  
             * 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等    
             */  
             
                p.setColor(overlayBackgroundColor);// 背景颜色
                c.drawRect(0, 0, nWidth, nHeight, p);/*绘制矩形*/
                p.setColor(overlayTextColor);// 文字颜色
                /**
                 * 画布的绘制文字方法
                 * test:要绘制的字符
                 * -b.left:字符起始位置的x坐标,这里是矩形的左边
                 * (nHeight / 2) - ((p.ascent() + p.descent()) / 2) + 1:字符起始位置的y坐标
                 * p:用到的画笔
                 * 关于涉及的矩形属性可看博客  http://mikewang.blog.51cto.com/3826268/871765
                 */
                c.drawText(text, -b.left + 1,
                        (nHeight / 2) - ((p.ascent() + p.descent()) / 2) + 1, p);
                
                return bm;
            }
    
            /**
             * 重头戏
             * 如果线程是运行的,SurfaceView也创建了的
             * 则锁定画布com/mjpeg/io/MjpegInputStream.java中的readMjpegFrame方法获得mjpeg视频流的内容
             * mjpeg视频的内容就是类位图,然后根据类位图绘制矩形,再绘制相应的位图,这个位图才是我们需要的
             * 如果设置了帧率文本,就在mjpegview上覆盖,最后解锁画布
             */
            public void run() {
                start = System.currentTimeMillis();
                Rect destRect;
                Paint p = new Paint();
        //        String fps = "";
                while (bRun) {
                    if (bsurfaceIsCreate) {
                        c = mSurfaceHolder.lockCanvas();
                        try {
                            mjpegBitmap = mIs.readMjpegFrame();/*调用Inputstrean的方法*/
                            /*同步图像的宽高设置*/
                            synchronized (mSurfaceHolder) {
                                destRect = destRect(mjpegBitmap.getWidth(),
                                        mjpegBitmap.getHeight());
                            }
                            /**
                             * 当主activity点击相册和设置跳转时,Surfaceview被销毁,此时c将为空
                             */
                            if(c != null){
                                c.drawPaint(new Paint());
                                c.drawBitmap(mjpegBitmap, null, destRect, p);
                                if (bIsShowFps)
                                    calculateFps(destRect, c, p);
                                mSurfaceHolder.unlockCanvasAndPost(c);
                            }
                        } catch (IOException e) {
                        }
                    }else {
                        try {
                            Thread.sleep(500);//线程休眠,让出调度
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            /**
             * 使用前面的方法,绘制出“显示帧率”文本,效果为"i帧",i自增
             * @param destRect
             * @param c
             * @param p
             */
            public void calculateFps(Rect destRect, Canvas c, Paint p) {
                int width;
                int height;
                String fps;
                
                p.setXfermode(mode);/* 设置两个画面相交时的模式*/
                if (overlayBitmap != null) {
                    /**
                     * 计算好文本的宽和高
                     * 然后调用画布的绘制位图方法绘图
                     */
                    height = ((ovlPos & 1) == 1) ? destRect.top
                            : destRect.bottom - overlayBitmap.getHeight();
                    width = ((ovlPos & 8) == 8) ? destRect.left 
                            : destRect.right - overlayBitmap.getWidth();
                    c.drawBitmap(overlayBitmap, width, height, null);
                }
                p.setXfermode(null);
                frameCounter++;
                /**
                 * currentTimeMillis表示系统从January 1, 1970 00:00:00.0 UTC开始的毫秒数
                 * start在前面已经设置好,表示渲染线程开始的系统时间
                 */
                if ((System.currentTimeMillis() - start) >= 1000) {
                    fps = frameCounter+ "fps";
                    start = System.currentTimeMillis();
                    overlayBitmap = makeFpsOverlay(overlayPaint, fps);/*真正的绘制这个"文本"*/
                    frameCounter = 0;                
                }
            }
            
            
        }
    
    }

    MjpegInputStream.java

    package com.mjpeg.io;
    
    import java.io.BufferedInputStream;
    import java.io.ByteArrayInputStream;
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.Serializable;
    import java.util.Properties;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.CoreConnectionPNames;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Parcelable;
    import android.util.Log;
    /**
     * 该类继承了DataInputStream实现了Serializable接口
     * 1. 实例化流,获取初始化流和关闭实例流的方法
     * 2. 一个构造函数
     * 3. 一个根据帧数据大小获得位图方法
     */
    public class MjpegInputStream extends DataInputStream implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        /**
         * 用UE打开发现 每一个jpg格式的图片 开始两字节都是 0xFF,0xD8
         */
        private final byte[] SOI_MARKER = { (byte) 0xFF, (byte) 0xD8 };
    //    private final byte[] EOF_MARKER = { (byte) 0xFF, (byte) 0xD9 };
        /**
         * 表示服务器发给客户端的一帧数据的长度
         */
        private final String CONTENT_LENGTH = "Content-Length";
        private final static int HEADER_MAX_LENGTH = 100;
        private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;
        private int mContentLength = -1;
        private static MjpegInputStream mis = null;
        /**
         * 调用该类的构造方法 创建MjpegInputStream流
         * @param is
         */
        public static void initInstance(InputStream is){
            if(mis == null)
                mis = new MjpegInputStream(is);
            
        }
        /**
         * 获得创建的mjpegInputsteam流
         * @return
         */
        public static MjpegInputStream getInstance(){
            if(mis != null)
                return mis;
            
            return null;
        }
        /**
         * 因为mpjeginputstream继承了datainputstream
         * 所以可以调用mpjeginputstream的关闭流方法
         */
        public static void closeInstance(){
            try {
                mis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            mis = null;
        }
    
        private MjpegInputStream(InputStream in) {
            super(new BufferedInputStream(in, FRAME_MAX_LENGTH));
        }
        /**
         * 在数据流里面找SOI_MARKER={(byte)0xFF,(byte) 0xD8}
         * 所有对IO流的操作都会抛出异常
         * @param in
         * @param sequence
         * @return
         * @throws IOException
         */
        private int getEndOfSeqeunce(DataInputStream in, byte[] sequence)
                throws IOException {
            int seqIndex = 0;
            byte c;
            for (int i = 0; i < FRAME_MAX_LENGTH; i++) {// 0 1 2 3
                c = (byte) in.readUnsignedByte();
                if (c == sequence[seqIndex]) {
                    seqIndex++;
                    if (seqIndex == sequence.length)//2
                        return i + 1;//3
                } else
                    seqIndex = 0;
            }
            return -1;
        }
        /**
         * 此方法功能是找到索引0xFF,0XD8在字符流的位置
         * 整个数据流形式:http头信息 帧头(0xFF 0xD8) 帧数据 帧尾(0xFF 0xD9)
         * 1、首先通过0xFF 0xD8找到帧头位置
         * 2、帧头位置前的数据就是http头,里面包含Content-Length,这个字段指示了整个帧数据的长度
         * 3、帧头位置后面的数据就是帧图像的开始位置
         * @param in
         * @param sequence
         * @return
         * @throws IOException
         */
        private int getStartOfSequence(DataInputStream in, byte[] sequence)
                throws IOException {
            int end = getEndOfSeqeunce(in, sequence);
            return (end < 0) ? (-1) : (end - sequence.length);
        }
        /**
         * 从http的头信息中获取Content-Length,知道一帧数据的长度
         * @param headerBytes
         * @return
         * @throws IOException
         * @throws NumberFormatException
         */
        private int parseContentLength(byte[] headerBytes) throws IOException,
                NumberFormatException {
            /**
             * 根据字节流创建ByteArrayInputStream流
             * Properties是java.util包里的一个类,它有带参数和不带参数的构造方法,表示创建无默认值和有默认值的属性列表
             * 根据流中的http头信息生成属性文件,然后找到属性文件CONTENT_LENGTH的value,这就找到了要获得的帧数据大小
             * 创建一个 ByteArrayInputStream,使用 headerBytes作为其缓冲区数组
             */
            ByteArrayInputStream headerIn = new ByteArrayInputStream(headerBytes);
            Properties props = new Properties();/*创建一个无默认值的空属性列表*/
            props.load(headerIn);/*从输入流中生成属性列表(键和元素对)。*/
            return Integer.parseInt(props.getProperty(CONTENT_LENGTH));/*用指定的键在此属性列表中搜索属性。*/
        }
    
        /**
         * 
         * @return
         * @throws IOException
         */
        public Bitmap readMjpegFrame() throws IOException {
            mark(FRAME_MAX_LENGTH);/*流中当前的标记位置*/
            int headerLen = getStartOfSequence(this, SOI_MARKER);
            reset();/*将缓冲区的位置重置为标记位置*/
            byte[] header = new byte[headerLen];
    
            readFully(header);/*会一直阻塞等待,直到数据全部到达(数据缓冲区装满)*/
    //        String s = new String(header);
            try {
                mContentLength = parseContentLength(header);// ?
            } catch (NumberFormatException e) {
                return null;
            }
            /**
             * 根据帧数据的大小创建字节数组
             */
            byte[] frameData = new byte[mContentLength];
            readFully(frameData);
            /**
             * 根据不同的源(file,stream,byte-arrays)创建位图
             * 把输入字节流流转为位图
             */
            return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));
        }
    }

    WsnActivty.java

    package my.work.Library;
    
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ImageButton;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    
    
    public class WsnActivty extends Activity {
        /** Called when the activity is first created. */
        private Button btnNetwork,btnVideo;
        private String strIpAddr = null;
        static TextView textTips,seat;
        private ClientThread clientThread = null;
        private Message MainMsg;
        public static Handler mainHandler;
        static final int TIPS_UPDATE_UI = 3;   //tips_update_ui
        static final int SEAT_UPDATE_UI = 6;   //seat_update_ui
        
        static final int MAX_NODE = 4;
        static byte NodeData[][] = new byte[MAX_NODE][5];; // [5] 0=温度 1=湿度 2=气体 3=灯
        static final int RX_DATA_UPDATE_UI = 1;
        
        
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            initControl();
            initMainHandler();
      
        }
        private void initControl() {
            // TODO Auto-generated method stub
              btnNetwork = (Button) findViewById(R.id.btn_network);
              btnNetwork.setOnClickListener(new ButtonClick());
              
              textTips = (TextView) findViewById(R.id.Tips);
                textTips.setText(R.string.init_tips);
                
                seat = (TextView) findViewById(R.id.statc001);
                seat.setText(R.string.people1);
                
                btnVideo = (Button) findViewById(R.id.btn_video);
              btnVideo.setOnClickListener(new ButtonClick());
            
        }
        class ButtonClick implements OnClickListener {
            @Override
            public void onClick(View v) {
                
                switch (v.getId()) {
                case R.id.btn_network: // 连接网络
                    showDialog(WsnActivty.this);
                    break;
                case R.id.btn_video: // 暂时用作停止自动刷新功能  ,跳转到FlashActivity去
                    // mainTimer.cancel(); //看视频关闭定时查询数据 要判断
                    if (clientThread != null) {
                        MainMsg = ClientThread.childHandler
                                .obtainMessage(ClientThread.RX_EXIT);  //关闭线程
                        ClientThread.childHandler.sendMessage(MainMsg);
                    }
    
                     /*new一个Intent对象,并指定class*/ 
                    Intent intent = new Intent();  
                    intent.setClass(WsnActivty.this,FlashActivity.class);  
                      
                    /*new一个Bundle对象,并将要传递的数据传入*/ 
                    Bundle bundle = new Bundle();  
                    bundle.putString("IP",strIpAddr);  
    
                    /*将Bundle对象assign给Intent*/ 
                    intent.putExtras(bundle);  
                    
                    /*调用Activity FlashActivity*/ 
                    startActivity(intent); 
                    //startActivity(new Intent(WsnActivity.this, FlashActivity.class));//简单跳转
                    break;
                    }
       }
        }
    
                // 显示连接对话框
                private void showDialog(Context context) {
                    final EditText editIP = new EditText(context);
                    editIP.setText("192.168.0.10");
    
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    // builder.setIcon(R.drawable.ic_launcher);
                    builder.setTitle("请输入服务器IP地址");
                    builder.setView(editIP);
                    builder.setPositiveButton("连接", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            strIpAddr = editIP.getText().toString();
                            boolean ret = isIPAddress(strIpAddr);
    
                            if (ret) {
                                textTips.setText("服务器IP地址:" + strIpAddr);
                            } else {
                                strIpAddr = null;
                                textTips.setText("IP地址不合法,请重新设置");
                                return;
                            }
    
                            clientThread = new ClientThread(strIpAddr);// 建立客户端线程
                            clientThread.start();
    
                            //mainTimer = new Timer();// 定时查询所有终端信息
                            //setTimerTask();
                        }
                    });
                    builder.setNeutralButton("取消", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            if (clientThread != null) {
                                MainMsg = ClientThread.childHandler
                                        .obtainMessage(ClientThread.RX_EXIT);
                                ClientThread.childHandler.sendMessage(MainMsg);
                                textTips.setText("与服务器断开连接");
                            }
                        }
                    });
    
                    builder.show();
                }
            
            // 判断输入IP是否合法
            private boolean isIPAddress(String ipaddr) {
                boolean flag = false;
                Pattern pattern = Pattern
                        .compile("\b((?!\d\d\d)\d+|1\d\d|2[0-4]\d|25[0-5])\.((?!\d\d\d)\d+|1\d\d|2[0-4]\d|25[0-5])\.((?!\d\d\d)\d+|1\d\d|2[0-4]\d|25[0-5])\.((?!\d\d\d)\d+|1\d\d|2[0-4]\d|25[0-5])\b");
                Matcher m = pattern.matcher(ipaddr);
                flag = m.matches();
                return flag;
            }
            
            void initMainHandler() {
                mainHandler = new Handler() {
    
                    // 主线程消息处理中心
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                           case TIPS_UPDATE_UI:
                            String str = (String) msg.obj;  //连接成功
                            textTips.setText(str);
                            break;
                           case SEAT_UPDATE_UI:
                                String strseat = (String) msg.obj;  //rfid                            
                                seat.setText(strseat);
                                break;
                        }
                        super.handleMessage(msg);
                    }
                };
            }
    
    }
        

    ClientThread.java

    package my.work.Library;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.net.SocketAddress;
    
    import android.content.Context;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.widget.Toast;
    
    public class ClientThread extends Thread {
    
        private OutputStream outputStream = null;
        private InputStream inputStream = null;
        private Socket socket;
        private SocketAddress socketAddress;
        public static Handler childHandler;
        private boolean RxFlag = true;
        final int TEXT_INFO = 12;
        static final int RX_EXIT = 11;
        static final int TX_DATA = 10;
        Context mainContext;
        Message msg;
        private String strIP;
        final int SERVER_PORT = 33333;
        byte cNodeData[][] = new byte[4][5]; // [5] 0=温度 1=湿度 2=气体 3=灯
        int RxCount = 0, nRecvLen, index = 0;
        byte CheckSum;
        // byte strRxBuf[] = new byte[256];
        int ucRecvLen = 7;
        
        private RxThread rxThread;
    
        //获取WsnActivty.java 开辟子线程clientThread对象线程传递过来的ip地址
        public ClientThread(String ip) {
            strIP = ip;
        }
    
        // 连接网络
        void connect() {
            RxFlag = true;
            socketAddress = new InetSocketAddress(strIP, SERVER_PORT);
            socket = new Socket();
    
            try {
                socket.connect(socketAddress, SERVER_PORT);
                inputStream = socket.getInputStream();
                outputStream = socket.getOutputStream();
    
                msg = WsnActivty.mainHandler.obtainMessage(
                        WsnActivty.TIPS_UPDATE_UI, "连接成功");
                WsnActivty.mainHandler.sendMessage(msg);
                
                
                rxThread = new RxThread();
                rxThread.start();
    
            } catch (IOException e) {
                try {
                    sleep(10);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                msg = WsnActivty.mainHandler.obtainMessage(
                        WsnActivty.TIPS_UPDATE_UI, "无法连接到服务器");
                WsnActivty.mainHandler.sendMessage(msg);
                e.printStackTrace();
            } catch (NumberFormatException e) {
    
            }
        }
    
        void initChildHandler() {
    
            Looper.prepare(); // 在子线程中创建Handler必须初始化Looper
    
            childHandler = new Handler() {
                // 子线程消息处理中心
                public void handleMessage(Message msg) {
    
                    // 接收主线程及其他线程的消息并处理...
                    /**
                     * MainMsg = ClientThread.childHandler.obtainMessage(ClientThread.TX_DATA,
                     * len, 0, (Object) buffer);
                     * SendData(SendBuf, 7);
                     */
                    switch (msg.what) {
                    
    
                    case RX_EXIT:
                        RxFlag = false;
                        try {
                            if (socket.isConnected()) {
                                inputStream.close();
                                outputStream.close();
                                socket.close();
                            }
    
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
    
                        childHandler.getLooper().quit();// 结束消息队列
    
                        break;
    
                    default:
                        break;
                    }
    
                }
            };
    
            // 启动该线程的消息队列
            Looper.loop();
    
        }
    
        public void run() {
            connect();
            initChildHandler();
            msg = WsnActivty.mainHandler.obtainMessage(WsnActivty.TIPS_UPDATE_UI,
                    "与服务器断开连接");
            WsnActivty.mainHandler.sendMessage(msg);
        }
        
        // socket 接收线程
        public class RxThread extends Thread {
            public void run() {
                try {
                    while (socket.isConnected() && RxFlag) {
                        byte strRxBuf[] = new byte[30];
                        
                        byte i;
                        int RxIndex, len, readBytes = 0;    
                        
                        while (readBytes < ucRecvLen) {           //接收到数据,存放到strRxBuf
                            len = inputStream.read(strRxBuf,readBytes, 8);
                            readBytes += len;
                            
                            if (len == -1)
                                break;
                            }
                        
                            
                            String strRead =new String(strRxBuf);
                            String str=new String("822350C2");
                            String str1="822350C2";
                            
                            if(str.equals(str1))
                            {
                            msg = WsnActivty.mainHandler.obtainMessage(
                                    WsnActivty.SEAT_UPDATE_UI, "有人");
                            WsnActivty.mainHandler.sendMessage(msg);
                            }
                            msg = WsnActivty.mainHandler.obtainMessage(
                                    WsnActivty.SEAT_UPDATE_UI, strRead);
                            WsnActivty.mainHandler.sendMessage(msg);
                            
                        
                    }
                    if (socket.isConnected())
                        socket.close();
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
    
        byte GetDataLen(byte fc) {
            byte len = 0;
    
            switch (fc) {
            case 0x01:
                len = 22;
                break;
            case 0x07:
            case 0x08:
            case 0x0A:
            case 0x0B:
            case 0x0C:
            case 0x0D:
                len = 7;
                break;
            }
    
            return len;
        }
        
    
    }

    FlashActivity.java

    package my.work.Library;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.ArrayList;
    
    import my.work.Library.WsnActivty.ButtonClick;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.CoreConnectionPNames;
    
    import tools.Generic;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    import android.net.DhcpInfo;
    import android.net.wifi.WifiManager;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.ArrayAdapter;
    import android.widget.AutoCompleteTextView;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.mjpeg.io.MjpegInputStream;
    
    /**
     * 应用程序执行时,该类首先被调用
     */
    public class FlashActivity extends Activity {
        private Context mContext = this;
        private EditText ipEdt = null;
        private EditText portEdt = null;
        private TextView hintTv = null;
        private DhcpInfo dpInfo = null;
        private WifiManager wifi = null;
        private InputStream is = null;
        private SharedPreferences sp = null;
        private Editor editor = null;
        private String port = "8080";/* 用来保存获得用户输入的端口 */
        private Bundle bundle;
        private Button connectin;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.flash);/* 设置布局为res/layout/flash.xml*/
    
            init();
            int state = wifi.getWifiState();/* 获得wifi当前状态 */
            
            if (state != WifiManager.WIFI_STATE_ENABLED) {
                /**
                 * 为了程序的扩展性和可读性,单独在tools目录定义一个Generic类,它有很多方法
                 * 1.有showMsg方法,用于控制显示时间来显示一个Toast
                 * 2.有getSysNowTime方法,用于获取当前的系统时间
                 * 3.有getSdCardFile方法,用于获取SD卡的绝对路径,成功返回File值,失败返回NULL
                 * 4.有getConnectedIP方法,用于获取连接到wifi热点的所有的手机ip,成功返回ArrayList<String>型的容器
                 * 5.有getShrinkedPic方法,用于获取照片的缩略图
                 * 6.定义了一个DescendSortByIndex类:实现了整型比较器
                 * 7.定义个DescendSortByTime类:实现了File比较器
                 */
                Generic.showMsg(this, "请打开wifi", false);
                finish();
            } else
                 /* 取得Intent中的Bundle对象 */ 
                bundle = this.getIntent().getExtras();  
                  
                /* 取得Bundle对象中的数据 */ 
                String strIP = bundle.getString("IP");  
                
                //autoConnect(strIP);
        }
    
        @Override
        /**
         * 调用finish方法时,这方法将被激发
         * 设置输入流为空,调用父类的onDestroy销毁资源
         */
        protected void onDestroy() {
            is = null;
            super.onDestroy();
        }
    
        private void init() {
            /**
             * 获取在本Activity要使用的控件和WiFi
             */
            hintTv = (TextView) findViewById(R.id.hintTv);
            ipEdt = (EditText) findViewById(R.id.ip);
            portEdt = (EditText) findViewById(R.id.port);
            
            connectin = (Button) findViewById(R.id.connect);
            connectin.setOnClickListener(new ButtonClick());
             
            
            /**
             * 因为要用到WIFI和Internet所以在AndroidMenufest.xml 中添加如下权限 <uses-permission
             * android:name="android.permission.INTERNET"/> <uses-permission
             * android:name="android.permission.ACCESS_WIFI_STATE"/>
             * <uses-permission
             * android:name="android.permission.CHANGE_WIFI_STATE"/>
             */
            wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
    
            //initSp();/* 主要是方便查找以前登录成功了的IP */
        }
    
        class ButtonClick implements OnClickListener {
            @Override
            public void onClick(View v) {
                
                String ip = ipEdt.getText().toString();/* 获得输入的IP */
                port = portEdt.getText().toString();/* 获得输入的端口 */
    
                // port不能为空,ip地址格式正确
                if (!port.equals("") && checkAddr(ip, Integer.valueOf(port))) {
                    new ConnectTask().execute(ip);
                } else {
                    Generic.showMsg(mContext, "请检查ip或port", true);
                }
                }
            }
    //    /**
    //     * 生成配置文件config,它在 /data/data/<package name>/shared_prefs/config.xml
    //     * 取出配置文件的ip用冒号隔开,并为自动完成列表设置适配器
    //     */
    //    private void initSp() {
    //        sp = getSharedPreferences("config", MODE_PRIVATE);
    //        /* 创建好配置文件后,以后就可以用它的edit来操作配置文件了 */
    //        editor = sp.edit();
    //        String names[] = sp.getString("ip", "").split(":");
    //        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
    //                android.R.layout.simple_dropdown_item_1line, names);
    //        //ipEdt.setAdapter(adapter);
    //    }
    //
    //    /**
    //     * 自动连接 先将获取到的wifi热点服务器地址和连接到wifi热点的设备的ip放入容器,启动连接线程扫描容器中的ip
    //     * 
    //     * @return
    //     */
    //    private void autoConnect(String strIP) {
    //        ArrayList<String> addr = new ArrayList<String>();/* 创建容器 用于存放ip */
    //
    //        dpInfo = wifi.getDhcpInfo();
    //        addr.add(int32ToIp(dpInfo.serverAddress));/* 把服务IP放入容器的尾部 */
    //        addr.addAll(Generic.getConnectedIP());// Adds the objects in the specified collection to this ArrayList
    //
    //        // 为了在执行连接时 不会卡住UI,故采用异步任务方式,若读者想减缩程序,也可不使用异步任务
    //        if (strIP != null) {
    //            new ConnectTask().execute(strIP);
    //        } else {
    //            //因为连接线程的执行方法必须String类型,所以要toArray    
    //            new ConnectTask().execute(addr.toArray(new String[addr.size()]));
    //        }
    //    }
    
        /**
         * 按照一定的格式返回输入的Ip
         * 
         * @param ip
         * @return
         */
        private String int32ToIp(int ip) {
            return (ip & 0xff) + "." + (ip >> 8 & 0xff) + "." + (ip >> 16 & 0xff)
                    + "." + (ip >> 24 & 0xff);
        }
    
    //    /**
    //     * 手动连接 为控件绑定监听器有2种方法 1.给出布局文件并设置,findViewById()找到控件,调用API为其绑定相应监听器
    //     * 2.给出布局文件并设置,在布局文件里设置相应控件的OnClick,然后在源文件里具体实现相应控件的OnClick//本类用的就是这方法
    //     * 在layout目录下的flash.xml里声明了connectBtn的Button控件 点击"连接"按钮将调用此方法
    //     * 
    //     * @param v
    //     */
    //    public void connectBtn() {
    //        String ip = ipEdt.getText().toString();/* 获得输入的IP */
    //        port = portEdt.getText().toString();/* 获得输入的端口 */
    //
    //        // port不能为空,ip地址格式正确
    //        if (!port.equals("") && checkAddr(ip, Integer.valueOf(port))) {
    //            new ConnectTask().execute(ip);
    //        } else {
    //            Generic.showMsg(mContext, "连接失败", true);
    //        }
    //    }
    
        /**
         * 分割的ip是4段,ip端口范围在1000-65535
         * 
         * @param ip
         * @param port
         * @return
         */
        private boolean checkAddr(String ip, int port) {
            if (ip.split("\.").length != 4)
                return false;
            if (port < 1000 || port > 65535)
                return false;
    
            return true;
        }
    
        /**
         * 连接线程 此类的作用是在后台线程里执行http连接,连接卡住不会影响UI运行,适合于运行时间较长但又不能影响前台线程的情况
         * 异步任务,有3参数和4步
         * :onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute()
         * onPreExecute():运行于UI线程,一般为后台线程做准备,如在用户接口显示进度条
         * doInBackground():当onPreExecute执行后,马上被触发,执行花费较长时间的后台运算,将返回值传给onPostExecute
         * onProgressUpdate():当用户调用 publishProgress()将被激发,执行的时间未定义,这个方法可以用任意形式显示进度
         * 一般用于激活一个进度条或者在UI文本领域显示logo onPostExecute():当后台进程执行后在UI线程被激发,把后台执行的结果通知给UI
         * 参数一:运行于后台的doInBackground的参数类型
         * 参数二:doInBackground计算的通知给UI线程的单元类型,即运行于UI线程onProgressUpdate的参数类型,这里没用到
         * 参数三:doInBackground的返回值,将传给onPostExecute作参数
         * 
         * @author Administrator
         * 
         */
        private class ConnectTask extends AsyncTask<String, Integer, String> {
    
            @Override
            protected String doInBackground(String... params) {
                for (int i = 0; i < params.length; i++) {
                    String ip = params[i];/* 取出每一个ip */
    
                    if (ip.split("\.").length == 4) {
                        /**
                         * 在浏览器观察画面时,也是输入下面的字符串网址
                         */
                        String action = "http://" + ip + ":" + port
                                + "/?action=stream";
                        is = http(action);
                        if (is != null) { /* 第一次必须输入IP,下次登录时才可找到之前登录成功后的IP */
                            //writeSp(ip);
                            MjpegInputStream.initInstance(is);  //消息实体内容is
                            break;
                        }
                    }
                }
    
                return null;
            }
    
            @Override
            protected void onPostExecute(String result) {
                if (is != null) {
                    /**
                     * Intent是Android特有的东西,可以在Intent指定程序要执行的动作(比如:view,edit,dial)
                     * 都准备好程序执行该工作所需要的材料后
                     * ,只要调用startActivity,Android系统会自动寻找最符合你指定要求的应用程序 并执行该程序
                     */
                    startActivity(new Intent(FlashActivity.this, LinearLayout_activity.class));
                    finish();/* 结束本Activity */
                } else {
                    hintTv.setText(getResources()
                            .getString(R.string.connect_failed));
                    Generic.showMsg(mContext, "连接失败", true);
                }
    
                super.onPostExecute(result);
            }
    
            /**
             * 功能:http连接 Android提供两种http客户端, HttpURLConnection 和 Apache HTTP
             * Client,它们都支持HTTPS,能上传和下载文件 配置超时时间,用于IPV6和 connection pooling, Apache
             * HTTP client在Android2.2或之前版本有较少BUG
             * 但在Android2.2或之后,HttpURLConnection是更好的选择,在这里我们用的是 Apache HTTP Client
             * 凡是对IO的操作都会涉及异常,所以要try和catch
             * 
             * @param url
             * @return InputStream
             */
            private InputStream http(String url) {
                HttpResponse res;
                DefaultHttpClient httpclient = new DefaultHttpClient();/*
                                                                         * 创建http客户端,
                                                                         * 才能调用它的各种方法
                                                                         */
                httpclient.getParams().setParameter(
                        CoreConnectionPNames.CONNECTION_TIMEOUT, 500);/* 设置超时时间 */
    
                try {
                    HttpGet hg = new HttpGet(url);/*
                                                 * 这是GET方法的http API,
                                                 * GET方法是默认的HTTP请求方法
                                                 */
                    res = httpclient.execute(hg);
                    return res.getEntity().getContent(); // 从响应中获取消息实体内容
                } catch (IOException e) {
                }
    
                return null;
            }
    
        }
    }
    
    
    //    /**
    //     * 更新SharedPreferences 1.先判断ip是否有"ip"值,没有就将传进来的data赋值给ip 2.ip有值就取出,然后用冒号分隔开
    //     * 3.sp数组只能存放10组ip,如果超过了10组,先清零配置文件再更新 4.遍历数组,如果已有当前登录成功的ip,则返回
    //     * 5.数组里不包含登录成功的ip,则将当前登录成功的ip添加至sp数组并提交
    //     * 
    //     * @param ip
    //     */
    //    private void writeSp(String data) {
    //        if (!sp.contains("ip")) {
    //            editor.putString("ip", data);
    //            editor.commit();
    //            return;
    //        }
    //
    //        /**
    //         * 配置文件里有ip,表示之前登录成功了
    //         */
    //        String ip = sp.getString("ip", "");
    //        String[] ips = ip.split(":");
    //
    //        if (ips.length >= 10) {
    //            editor.clear();
    //            editor.commit();
    //            editor.putString("ip", data);
    //            editor.commit();
    //            return;
    //        }
    //
    //        for (int i = 0; i < ips.length; i++) {
    //            if (ips[i].equals(data))
    //                return;
    //        }
    //        editor.putString("ip", data + ":" + ip);/* 放在以前成功了的ip的前面 */
    //        editor.commit();
    //    }
    //
    //    /**
    //     * 自动完成框的下拉选项 当点击"history_user"ImageView控件时将调用该方法 这里只是具体实现xml文件的Onclick
    //     */
    //    public void showDropDown(View v) {
    //        ipEdt.showDropDown();
    //    }

    LinearLayout_activity.java

    package my.work.Library;
    
    import com.mjpeg.io.MjpegInputStream;
    import com.mjpeg.view.MjpegView;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    
    import my.work.Library.R;
    
    public class LinearLayout_activity extends Activity {
        public static LinearLayout_activity instance = null;
        private MjpegInputStream mis = null;
        private MjpegView mjpegView = null;
    
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.mainactivty);/*构造RadioGroup的5的RadioButton*/
    
            instance = this;
            mis = MjpegInputStream.getInstance();
            mjpegView = (MjpegView) findViewById(R.id.mjpegview);
            
            initMjpegView();
        }    
        private void initMjpegView() {
            if (mis != null) {
                mjpegView.setSource(mis);// 设置数据来源
                mjpegView.setDisplayMode(mjpegView.getDisplayMode());/*设置mjpegview的显示模式*/
                /**
                 * setFps和getFps方法是为了在屏幕的右上角动态显示当前的帧率
                 * 如果我们只需观看画面,下面这句完全可以省去
                 */
                mjpegView.setFps(mjpegView.getFps());
                /**
                 * 调用mjpegView中的线程的run方法,开始显示画面
                 */
                mjpegView.setDisplayMode(MjpegView.FULLSCREEN_MODE);/*全屏*/
                mjpegView.startPlay();
            }
        }
    
    }
  • 相关阅读:
    [BJOI2019] 光线
    C# 从零开始写 SharpDx 应用 笔刷
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    PowerShell 拿到显卡信息
    PowerShell 拿到显卡信息
    win10 uwp 如何使用DataTemplate
    win10 uwp 如何使用DataTemplate
  • 原文地址:https://www.cnblogs.com/yihujiu/p/5997859.html
Copyright © 2011-2022 走看看