• 网络摄像头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();
            }
        }
    
    }
  • 相关阅读:
    ubuntu怎么安装下载工具uget+aria2 for firefox
    #pragma once
    opencv3在CMakeLists.txt中的调用问题
    opencv之Mat数据类型
    windows10下笔记本电脑外接显示器设置
    ubuntu16.04下笔记本电脑扩展双屏安装过程
    【问题收录】Ubuntu14.04连接两个双显示器失败的解决方案
    获取jsapi_ticket
    微信公众号中高德地图显示路线开发
    微信公众号中高德地图显示路线
  • 原文地址:https://www.cnblogs.com/yihujiu/p/5997859.html
Copyright © 2020-2023  润新知