之前提到过GIF的合成和有两种方式,一种为JNI,一种是修改了某位国外前辈多年前些的AnimatedGifEncoder.java。既然要对GIF进行处理,还会涉及到GIF的拆分,即将GIF图片拆分成单针的格式。

其实也挺简单的,主要是用了gifview中的GifAction.java对其进行改造。然后加载gif图片成功后,利用自定义AsyncTask进行拆分保存。不废话了,贴一下关键代码吧。下面的代码有地方命名不规范(是我参照一个叫GIF图片处理的工具扣下来的),但是大家凑合看吧哈~~~

文章的最后我会放出GIF合成,分解,和处理的DEMO,能顺利运行。但是由于工作比较忙,demo就没有对代码重构。大家有时间可以帮忙重构一下分享出来。

package com.johdan.gif.separate.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;

import com.ant.liao.GifAction;
import com.johdan.gif.merge.R;

/**
 * gif动画变多张jpg图片
 * @author jodhan
 *
 */
public class SeparateGifTask implements GifAction {

	private Activity context;
	private File file;
	private String filename;
	private GifDecoderSeparate gifDecoder;
	private Handler handler;
	private ProgressDialog progressDialog;
	private String savePath;

	private int[] delays;
	private String[] listpath;
	public static boolean isDecodeOK=false;
	public int[] getDelays(){
		return delays;
	}
	public String[] getListPath(){
		return listpath;
	}
	Handler outHandler;

	public Handler getOutHandler() {
		return outHandler;
	}
	public void setOutHandler(Handler outHandler) {
		this.outHandler = outHandler;
	}
	public SeparateGifTask(File paramFile, String paramString, Activity context){
	    this.file = paramFile;
	    this.savePath = paramString;
	    this.context = context;
	    GifDecoderSeparate localGifDecoder = new GifDecoderSeparate(this);
	    this.gifDecoder = localGifDecoder;
	    String str = this.file.getName();
	    this.filename = str;
	    Handler local1 = new Handler(){

			@Override
			public void handleMessage(Message msg) {
				// TODO Auto-generated method stub
//				super.handleMessage(msg);
				switch(msg.what){
					case 0:
					      //**********************************************/
					      SeparateTask separateTask = new SeparateTask();
					      separateTask.execute(null);
						break;
					case 1:
						 /******将对应的分解后路径放入listpath中*****/
						//设置对应的属性为true
						isDecodeOK=true;
						break;
					default:
						break;
				}
			}

	    };
	    this.handler = local1;

	  }

	@Override
	public void parseOk(boolean parseStatus, int frameIndex) {
		// TODO Auto-generated method stub
//		System.out.println(R.string.now_sepa_gif);
		System.out.println("----2222---"+this.gifDecoder.parseOk());
		if (this.gifDecoder.parseOk())
	    {
	      this.progressDialog.dismiss();
	      System.out.println("-------"+this.gifDecoder.parseOk());
	      this.handler.sendEmptyMessage(0);
//	      boolean bool = this.handler.sendEmptyMessage(0);
//	      int[] delays = this.gifDecoder.getDelays();
	      this.delays=this.gifDecoder.getDelays();
	      listpath =new String[delays.length];

	      System.out.println("delays的长度"+delays.length);
	      for(int delay : delays){
	    	  System.out.println("间隔的"+delays.length);
	      }
	      System.out.println(R.string.now_sepa_gif);
	      //停止线程不知道为啥就报错,郁闷。
	      //在合并的时候影响图片,也不知道是不是因为停止线程的缘故。
//	      this.gifDecoder.stop();

	    }
	}
	  public void saveBitmap(int paramInt, Bitmap paramBitmap, String paramString)
	  {

	    String str1;
	    try
	    {
	      str1 = String.valueOf(this.savePath);
	      StringBuilder localStringBuilder = new StringBuilder(str1);
	      String str2 = this.filename;
	      int i = this.filename.indexOf(".gif");
	      String str3 = str2.substring(0, i);
	      String str4 = str3 + "_" + paramInt + paramString;
	      File localFile = new File(str1+str4);

	      //对每个图片的路径进行保存
	      listpath[paramInt-1]=str1+str4;
	      System.out.println("path:"+str1+str4);

	      localFile.createNewFile();
	      FileOutputStream localFileOutputStream = new FileOutputStream(localFile);
//	      Bitmap.CompressFormat localCompressFormat = Bitmap.CompressFormat.JPEG;
	      Bitmap.CompressFormat localCompressFormat = Bitmap.CompressFormat.PNG;

	      boolean bool = paramBitmap.compress(localCompressFormat, 100, localFileOutputStream);
	      localFileOutputStream.flush();
	      localFileOutputStream.close();
	      return;
	    }
	    catch (Exception localException)
	    {
	      localException.printStackTrace();
	    }
	  }

	  public void start()
	  {
		 GifDecoderSeparate localGifDecoder;
	    try
	    {
	      localGifDecoder = this.gifDecoder;
	      File localFile = this.file;
	      FileInputStream localFileInputStream = new FileInputStream(localFile);
	      localGifDecoder.setGifImage(localFileInputStream);
	      this.gifDecoder.start();
	      Activity localGifPlayerAndMakerActivity = this.context;
	      StringBuilder localStringBuilder = new StringBuilder("正在解析");
	      String str1 = this.filename;
	      String str2 = localStringBuilder + str1 + "中...";
	      ProgressDialog localProgressDialog = ProgressDialog.show(localGifPlayerAndMakerActivity, "解析GIF", str2);
	      this.progressDialog = localProgressDialog;
	      this.progressDialog.show();
	      return;
	    }
	    catch (FileNotFoundException localFileNotFoundException)
	    {
	      localFileNotFoundException.printStackTrace();
	    }
	  }

	  class SeparateTask extends AsyncTask<Void, Integer, Void>
	  {
		private ProgressDialog progressDialog;
		@Override
		protected Void doInBackground(Void... params) {
		      int i = 0;
		      while (true)
		      {
		        int j = SeparateGifTask.this.gifDecoder.getFrameCount();
		        if (i >= j)
		          return null;
		        SeparateGifTask localSeparateGifTask = SeparateGifTask.this;
		        int k = i + 1;
		        Bitmap localBitmap = SeparateGifTask.this.gifDecoder.getFrameImage(i);
		        localSeparateGifTask.saveBitmap(k, localBitmap, ".png");
		        Integer[] arrayOfInteger = new Integer[1];
		        Integer localInteger = Integer.valueOf(i + 1);
		        arrayOfInteger[0] = localInteger;
		        publishProgress(arrayOfInteger);
		        i += 1;
		      }
		}

		@Override
		protected void onPreExecute() {
			// TODO Auto-generated method stub
			Activity localGifPlayerAndMakerActivity = SeparateGifTask.this.context;
		    ProgressDialog localProgressDialog1 = new ProgressDialog(localGifPlayerAndMakerActivity);
		    this.progressDialog = localProgressDialog1;
		    this.progressDialog.setTitle(R.string.prompt);
		    ProgressDialog localProgressDialog2 = this.progressDialog;
		    int i = SeparateGifTask.this.gifDecoder.getFrameCount();
		    localProgressDialog2.setMax(i);
		    ProgressDialog localProgressDialog3 = this.progressDialog;
		    String str1 = String.valueOf(SeparateGifTask.this.context.getResources().getString(R.string.now_sepa_gif));
		    StringBuilder localStringBuilder = new StringBuilder(str1).append("0/");
		    int j = SeparateGifTask.this.gifDecoder.getFrameCount();
		    String str2 = j+"";
		    localProgressDialog3.setMessage(localStringBuilder+str2);
		    this.progressDialog.setProgressStyle(1);
		    this.progressDialog.show();
			super.onPreExecute();
		}

		@Override
		protected void onPostExecute(Void result) {
			// TODO Auto-generated method stub
			this.progressDialog.dismiss();
			Activity localGifPlayerAndMakerActivity1 = SeparateGifTask.this.context;
//		    String str1 = SeparateGifTask.this.context.getNowPath();
//		    localGifPlayerAndMakerActivity1.listPath(str1);
			Activity localGifPlayerAndMakerActivity2 = SeparateGifTask.this.context;
		    String str2 = SeparateGifTask.this.context.getResources().getString(R.string.cancel_success);
		    Toast.makeText(localGifPlayerAndMakerActivity2, str2, 0).show();

		    isDecodeOK =true;
		    outHandler.sendEmptyMessage(3);
		    handler.sendEmptyMessage(1);

			super.onPostExecute(result);
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			// TODO Auto-generated method stub
			 ProgressDialog localProgressDialog1 = this.progressDialog;
//			 int i = paramArrayOfInteger[0].intValue();
		     int i = values[0].intValue();
		     localProgressDialog1.setProgress(i);
		     ProgressDialog localProgressDialog2 = this.progressDialog;
		     String str1 = String.valueOf(SeparateGifTask.this.context.getResources().getString(R.string.now_sepa_gif));
		     StringBuilder localStringBuilder1 = new StringBuilder(str1);
//		     Integer localInteger = paramArrayOfInteger[0];
		     Integer localInteger = values[0];
		     StringBuilder localStringBuilder2 = localStringBuilder1.append(localInteger).append("/");
		     int j = SeparateGifTask.this.gifDecoder.getFrameCount();
		     String str2 = j+"";
		     localProgressDialog2.setMessage(localStringBuilder2+str2);

			super.onProgressUpdate(values);
		}

		@Override
		protected void onCancelled() {
			// TODO Auto-generated method stub
			this.progressDialog.dismiss();
			Activity localGifPlayerAndMakerActivity = SeparateGifTask.this.context;
		    String str = SeparateGifTask.this.context.getResources().getString(R.string.cancel_sepa);
		    Toast.makeText(localGifPlayerAndMakerActivity, str, 0).show();
			super.onCancelled();
		}

	  }
}

这里的GifDecoderSeparate是对GifDecoder进行了简单的改造,添加了2个方法。

 

package com.johdan.gif.separate.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.os.Environment;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

import com.ant.liao.GifAction;
import com.ant.liao.GifFrame;

public class GifDecoderSeparate extends Thread
{

	/**状态:正在解码中*/
	public static final int STATUS_PARSING = 0;
	/**状态:图片格式错误*/
	public static final int STATUS_FORMAT_ERROR = 1;
	/**状态:打开失败*/
	public static final int STATUS_OPEN_ERROR = 2;
	/**状态:解码成功*/
	public static final int STATUS_FINISH = -1;

	private InputStream in;
	private int status;

	public int width; // full image width
	public int height; // full image height
	private boolean gctFlag; // global color table used
	private int gctSize; // size of global color table
	private int loopCount = 1; // iterations; 0 = repeat forever

	private int[] gct; // global color table
	private int[] lct; // local color table
	private int[] act; // active color table

	private int bgIndex; // background color index
	private int bgColor; // background color
	private int lastBgColor; // previous bg color
	private int pixelAspect; // pixel aspect ratio

	private boolean lctFlag; // local color table flag
	private boolean interlace; // interlace flag
	private int lctSize; // local color table size

	private int ix, iy, iw, ih; // current image rectangle
	private int lrx, lry, lrw, lrh;
	private Bitmap image; // current frame
	private Bitmap lastImage; // previous frame
	private GifFrame currentFrame = null;

	private boolean isShow = false;

	private byte[] block = new byte[256]; // current data block
	private int blockSize = 0; // block size

	// last graphic control extension info
	private int dispose = 0;
	// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
	private int lastDispose = 0;
	private boolean transparency = false; // use transparent color
	private int delay = 0; // delay in milliseconds
	private int transIndex; // transparent color index

	private static final int MaxStackSize = 4096;
	// max decoder pixel stack size

	// LZW decoder working arrays
	private short[] prefix;
	private byte[] suffix;
	private byte[] pixelStack;
	private byte[] pixels;

	private GifFrame gifFrame; // frames read from current file
	private int frameCount;

	private GifAction action = null;

	private byte[] gifData = null;

	public GifDecoderSeparate(byte[] data,GifAction act){
		gifData = data;
		action = act;
	}

	public GifDecoderSeparate(InputStream is,GifAction act){
		in = is;
		action = act;
	}

	public void run(){
		if(in != null){
			readStream();
		}else if(gifData != null){
			readByte();
		}
	}

	/**
	 * 释放资源
	 */
	public void free(){
		GifFrame fg = gifFrame;
		while(fg != null){
			fg.image = null;
			fg = null;
			gifFrame = gifFrame.nextFrame;
			fg = gifFrame;
		}
		if(in != null){
			try{
			in.close();
			}catch(Exception ex){}
			in = null;
		}
		gifData = null;
	}

	/**
	 * 当前状态
	 * @return
	 */
	public int getStatus(){
		return status;
	}

	/**
	 * 解码是否成功,成功返回true
	 * @return 成功返回true,否则返回false
	 */
	public boolean parseOk(){
		return status == STATUS_FINISH;
	}

	/**
	 * 取某帧的延时时间
	 * @param n 第几帧
	 * @return 延时时间,毫秒
	 */
	public int getDelay(int n) {
		delay = -1;
		if ((n >= 0) && (n < frameCount)) {
			// delay = ((GifFrame) frames.elementAt(n)).delay;
			GifFrame f = getFrame(n);
			if (f != null)
				delay = f.delay;
		}
		return delay;
	}

	/**
	 * 取所有帧的延时时间
	 * @return
	 */
	public int[] getDelays(){
		GifFrame f = gifFrame;
		int[] d = new int[frameCount];
		int i = 0;
		while(f != null && i < frameCount){
			d[i] = f.delay;
			f = f.nextFrame;
			i++;
		}
		return d;
	}

	/**
	 * 取总帧 数
	 * @return 图片的总帧数
	 */
	public int getFrameCount() {
		return frameCount;
	}

	/**
	 * 取第一帧图片
	 * @return
	 */
	public Bitmap getImage() {
		return getFrameImage(0);
	}

	public int getLoopCount() {
		return loopCount;
	}

	private void setPixels() {
		int[] dest = new int[width * height];
		// fill in starting image contents based on last image's dispose code
		if (lastDispose > 0) {
			if (lastDispose == 3) {
				// use image before last
				int n = frameCount - 2;
				if (n > 0) {
					lastImage = getFrameImage(n - 1);
				} else {
					lastImage = null;
				}
			}
			if (lastImage != null) {
				lastImage.getPixels(dest, 0, width, 0, 0, width, height);
				// copy pixels
				if (lastDispose == 2) {
					// fill last image rect area with background color
					int c = 0;
					if (!transparency) {
						c = lastBgColor;
					}
					for (int i = 0; i < lrh; i++) {
						int n1 = (lry + i) * width + lrx;
						int n2 = n1 + lrw;
						for (int k = n1; k < n2; k++) {
							dest[k] = c;
						}
					}
				}
			}
		}

		// copy each source line to the appropriate place in the destination
		int pass = 1;
		int inc = 8;
		int iline = 0;
		for (int i = 0; i < ih; i++) {
			int line = i;
			if (interlace) {
				if (iline >= ih) {
					pass++;
					switch (pass) {
					case 2:
						iline = 4;
						break;
					case 3:
						iline = 2;
						inc = 4;
						break;
					case 4:
						iline = 1;
						inc = 2;
					}
				}
				line = iline;
				iline += inc;
			}
			line += iy;
			if (line < height) {
				int k = line * width;
				int dx = k + ix; // start of line in dest
				int dlim = dx + iw; // end of dest line
				if ((k + width) < dlim) {
					dlim = k + width; // past dest edge
				}
				int sx = i * iw; // start of line in source
				while (dx < dlim) {
					// map color and insert in destination
					int index = ((int) pixels[sx++]) & 0xff;
					int c = act[index];
					if (c != 0) {
						dest[dx] = c;
					}
					dx++;
				}
			}
		}
		image = Bitmap.createBitmap(dest, width, height, Config.ARGB_4444);
	}

	/**
	 * 取第几帧的图片
	 * @param n 帧数
	 * @return 可画的图片,如果没有此帧或者出错,返回null
	 */
	public Bitmap getFrameImage(int n) {
		GifFrame frame = getFrame(n);
		if (frame == null)
			return null;
		else
			return frame.image;
	}

	/**
	 * 取当前帧图片
	 * @return 当前帧可画的图片
	 */
	public GifFrame getCurrentFrame(){
		return currentFrame;
	}

	/**
	 * 取第几帧,每帧包含了可画的图片和延时时间
	 * @param n 帧数
	 * @return
	 */
	public GifFrame getFrame(int n) {
		GifFrame frame = gifFrame;
		int i = 0;
		while (frame != null) {
			if (i == n) {
				return frame;
			} else {
				frame = frame.nextFrame;
			}
			i++;
		}
		return null;
	}

	/**
	 * 重置,进行本操作后,会直接到第一帧
	 */
	public void reset(){
		currentFrame = gifFrame;
	}

	/**
	 * 下一帧,进行本操作后,通过getCurrentFrame得到的是下一帧
	 * @return 返回下一帧
	 */
	public GifFrame next() {
		if(isShow == false){
			isShow = true;
			return gifFrame;
		}else{
			if(status == STATUS_PARSING){
				if(currentFrame.nextFrame != null)
					currentFrame = currentFrame.nextFrame;
				//currentFrame = gifFrame;
			}else{
				currentFrame = currentFrame.nextFrame;
				if (currentFrame == null) {
					currentFrame = gifFrame;
				}
			}
			return currentFrame;
		}
	}

	private int readByte(){
		in = new ByteArrayInputStream(gifData);
		gifData = null;
		return readStream();
	}

//	public int read(byte[] data){
//		InputStream is = new ByteArrayInputStream(data);
//		return read(is);
//	}

	private int readStream(){
		init();
		if(in != null){
			readHeader();
			if(!err()){
				readContents();
				if(frameCount < 0){
					status = STATUS_FORMAT_ERROR;
					action.parseOk(false,-1);
				}else{
					status = STATUS_FINISH;
					action.parseOk(true,-1);
				}
			}
			try {
				in.close();
			} catch (Exception e) {
				e.printStackTrace();
			}

		}else {
			status = STATUS_OPEN_ERROR;
			action.parseOk(false,-1);
		}
		return status;
	}

	private void decodeImageData() {
		int NullCode = -1;
		int npix = iw * ih;
		int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;

		if ((pixels == null) || (pixels.length < npix)) {
			pixels = new byte[npix]; // allocate new pixel array
		}
		if (prefix == null) {
			prefix = new short[MaxStackSize];
		}
		if (suffix == null) {
			suffix = new byte[MaxStackSize];
		}
		if (pixelStack == null) {
			pixelStack = new byte[MaxStackSize + 1];
		}
		// Initialize GIF data stream decoder.
		data_size = read();
		clear = 1 << data_size;
		end_of_information = clear + 1;
		available = clear + 2;
		old_code = NullCode;
		code_size = data_size + 1;
		code_mask = (1 << code_size) - 1;
		for (code = 0; code < clear; code++) {
			prefix[code] = 0;
			suffix[code] = (byte) code;
		}

		// Decode GIF pixel stream.
		datum = bits = count = first = top = pi = bi = 0;
		for (i = 0; i < npix;) {
			if (top == 0) {
				if (bits < code_size) {
					// Load bytes until there are enough bits for a code.
					if (count == 0) {
						// Read a new data block.
						count = readBlock();
						if (count <= 0) {
							break;
						}
						bi = 0;
					}
					datum += (((int) block[bi]) & 0xff) << bits;
					bits += 8;
					bi++;
					count--;
					continue;
				}
				// Get the next code.
				code = datum & code_mask;
				datum >>= code_size;
				bits -= code_size;

				// Interpret the code
				if ((code > available) || (code == end_of_information)) {
					break;
				}
				if (code == clear) {
					// Reset decoder.
					code_size = data_size + 1;
					code_mask = (1 << code_size) - 1;
					available = clear + 2;
					old_code = NullCode;
					continue;
				}
				if (old_code == NullCode) {
					pixelStack[top++] = suffix[code];
					old_code = code;
					first = code;
					continue;
				}
				in_code = code;
				if (code == available) {
					pixelStack[top++] = (byte) first;
					code = old_code;
				}
				while (code > clear) {
					pixelStack[top++] = suffix[code];
					code = prefix[code];
				}
				first = ((int) suffix[code]) & 0xff;
				// Add a new string to the string table,
				if (available >= MaxStackSize) {
					break;
				}
				pixelStack[top++] = (byte) first;
				prefix[available] = (short) old_code;
				suffix[available] = (byte) first;
				available++;
				if (((available & code_mask) == 0)
						&& (available < MaxStackSize)) {
					code_size++;
					code_mask += available;
				}
				old_code = in_code;
			}

			// Pop a pixel off the pixel stack.
			top--;
			pixels[pi++] = pixelStack[top];
			i++;
		}
		for (i = pi; i < npix; i++) {
			pixels[i] = 0; // clear missing pixels
		}
	}

	private boolean err() {
		return status != STATUS_PARSING;
	}

	private void init() {
		status = STATUS_PARSING;
		frameCount = 0;
		gifFrame = null;
		gct = null;
		lct = null;
	}

	private int read() {
		int curByte = 0;
		try {

			curByte = in.read();
		} catch (Exception e) {
			status = STATUS_FORMAT_ERROR;
		}
		return curByte;
	}

	private int readBlock() {
		blockSize = read();
		int n = 0;
		if (blockSize > 0) {
			try {
				int count = 0;
				while (n < blockSize) {
					count = in.read(block, n, blockSize - n);
					if (count == -1) {
						break;
					}
					n += count;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (n < blockSize) {
				status = STATUS_FORMAT_ERROR;
			}
		}
		return n;
	}

	private int[] readColorTable(int ncolors) {
		int nbytes = 3 * ncolors;
		int[] tab = null;
		byte[] c = new byte[nbytes];
		int n = 0;
		try {
			n = in.read(c);
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (n < nbytes) {
			status = STATUS_FORMAT_ERROR;
		} else {
			tab = new int[256]; // max size to avoid bounds checks
			int i = 0;
			int j = 0;
			while (i < ncolors) {
				int r = ((int) c[j++]) & 0xff;
				int g = ((int) c[j++]) & 0xff;
				int b = ((int) c[j++]) & 0xff;
				tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
			}
		}
		return tab;
	}

	private void readContents() {
		// read GIF file content blocks
		boolean done = false;
		while (!(done || err())) {
			int code = read();
			switch (code) {
			case 0x2C: // image separator
				readImage();
				break;
			case 0x21: // extension
				code = read();
				switch (code) {
				case 0xf9: // graphics control extension
					readGraphicControlExt();
					break;
				case 0xff: // application extension
					readBlock();
					String app = "";
					for (int i = 0; i < 11; i++) {
						app += (char) block[i];
					}
					if (app.equals("NETSCAPE2.0")) {
						readNetscapeExt();
					} else {
						skip(); // don't care
					}
					break;
				default: // uninteresting extension
					skip();
				}
				break;
			case 0x3b: // terminator
				done = true;
				break;
			case 0x00: // bad byte, but keep going and see what happens
				break;
			default:
				status = STATUS_FORMAT_ERROR;
			}
		}
	}

	private void readGraphicControlExt() {
		read(); // block size
		int packed = read(); // packed fields
		dispose = (packed & 0x1c) >> 2; // disposal method
		if (dispose == 0) {
			dispose = 1; // elect to keep old image if discretionary
		}
		transparency = (packed & 1) != 0;
		delay = readShort() * 10; // delay in milliseconds
		transIndex = read(); // transparent color index
		read(); // block terminator
	}

	private void readHeader() {
		String id = "";
		for (int i = 0; i < 6; i++) {
			id += (char) read();
		}
		if (!id.startsWith("GIF")) {
			status = STATUS_FORMAT_ERROR;
			return;
		}
		readLSD();
		if (gctFlag && !err()) {
			gct = readColorTable(gctSize);
			bgColor = gct[bgIndex];
		}
	}

	private void readImage() {
		ix = readShort(); // (sub)image position & size
		iy = readShort();
		iw = readShort();
		ih = readShort();
		int packed = read();
		lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
		interlace = (packed & 0x40) != 0; // 2 - interlace flag
		// 3 - sort flag
		// 4-5 - reserved
		lctSize = 2 << (packed & 7); // 6-8 - local color table size
		if (lctFlag) {
			lct = readColorTable(lctSize); // read table
			act = lct; // make local table active
		} else {
			act = gct; // make global table active
			if (bgIndex == transIndex) {
				bgColor = 0;
			}
		}
		int save = 0;
		if (transparency) {
			save = act[transIndex];
			act[transIndex] = 0; // set transparent color if specified
		}
		if (act == null) {
			status = STATUS_FORMAT_ERROR; // no color table defined
		}
		if (err()) {
			return;
		}
		decodeImageData(); // decode pixel data
		skip();
		if (err()) {
			return;
		}
		frameCount++;
		// create new image to receive frame data
		image = Bitmap.createBitmap(width, height, Config.ARGB_4444);
		// createImage(width, height);
		setPixels(); // transfer pixel data to image
		if (gifFrame == null) {
			gifFrame = new GifFrame(image, delay);
			currentFrame = gifFrame;
		} else {
			GifFrame f = gifFrame;
			while(f.nextFrame != null){
				f = f.nextFrame;
			}
			f.nextFrame = new GifFrame(image, delay);
		}
		// frames.addElement(new GifFrame(image, delay)); // add image to frame
		// list
		if (transparency) {
			act[transIndex] = save;
		}
		resetFrame();
		action.parseOk(true, frameCount);
	}

	private void readLSD() {
		// logical screen size
		width = readShort();
		height = readShort();
		// packed fields
		int packed = read();
		gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
		// 2-4 : color resolution
		// 5 : gct sort flag
		gctSize = 2 << (packed & 7); // 6-8 : gct size
		bgIndex = read(); // background color index
		pixelAspect = read(); // pixel aspect ratio
	}

	private void readNetscapeExt() {
		do {
			readBlock();
			if (block[0] == 1) {
				// loop count sub-block
				int b1 = ((int) block[1]) & 0xff;
				int b2 = ((int) block[2]) & 0xff;
				loopCount = (b2 << 8) | b1;
			}
		} while ((blockSize > 0) && !err());
	}

	private int readShort() {
		// read 16-bit value, LSB first
		return read() | (read() << 8);
	}

	private void resetFrame() {
		lastDispose = dispose;
		lrx = ix;
		lry = iy;
		lrw = iw;
		lrh = ih;
		lastImage = image;
		lastBgColor = bgColor;
		dispose = 0;
		transparency = false;
		delay = 0;
		lct = null;
	}

	/**
	 * Skips variable length blocks up to and including next zero length block.
	 */
	private void skip() {
		do {
			readBlock();
		} while ((blockSize > 0) && !err());
	}
	/**************************************/
	  private String imagePath;
	  private boolean cacheImage;

	  public GifDecoderSeparate(GifAction paramGifAction)
	  {
	    byte[] arrayOfByte = new byte[256];
	    this.block = arrayOfByte;
	    this.blockSize = 0;
	    this.dispose = 0;
	    this.lastDispose = 0;
	    this.transparency = false;
	    this.delay = 0;
	    this.action = null;
	    this.gifData = null;
	    this.imagePath = null;
	    this.cacheImage = false;
	    this.action = paramGifAction;
	  }
	  private boolean createDir(String paramString){
		File localFile;
		boolean bool2;
		try{
			localFile = new File(paramString);
			if (!(localFile.exists()))
		      {
		        boolean bool1 = localFile.mkdirs();
		        bool2 = bool1;
		        return bool2;
		      }
		    bool2 = true;
		}catch (Exception localException){
			bool2 = false;
		}
		return bool2;
	  }

	  public void setGifImage(InputStream paramInputStream)
	  {
	    this.in = paramInputStream;
	  }
	  public void setGifImage(byte[] paramArrayOfByte)
	  {
	    this.gifData = paramArrayOfByte;
	  }
}

DEMO下载:
GIF图片的合成、分解与简单处理GifMerge

博文很久没更新了,之前写过一篇文章Android合成GIF图片写到了通过JNI方式合成GIF图片。其实网上还有一种方式就是通过JAVA方式合成图片,废话不说,拿出代码来了。demo自己写一个就行。后续的文章我会将一个demo拿出来,从gif图片的分解和合成与绘制。

之前一直困扰着,GIF图片失真的问题。研究了好几天,后来发现竟然是GIF图片只支持256色。

原因可以参照:http://bbs.blueidea.com/thread-2603107-1-1.html

http://tieba.baidu.com/p/1415083019?see_lz=1

Util类GifUtilJava .java

package com.johdan.gif.merge.java.util;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

/**
 * 通过java合成GIF图片
 * @author johdan
 *
 */
public class GifUtilJava {

	/**
	 * @param pics JPEG文件的路径数组
	 * @param newPic 生成后的gif文件
	 * @param delay 两帧之间的间隔
	 */
	public static void jpgToGif(String pics[],String newPic,int delay) {
        try {
        	Log.i("jpgToGif","is connection ="+newPic);
            AnimatedGifEncoder e = new AnimatedGifEncoder();
            e.setRepeat(1);
            e.start(newPic);   

            for (int i = 0; i < pics.length; i++) {
                e.setDelay(delay); // 设置播放的延迟时间
                Bitmap src=BitmapFactory.decodeFile(pics[i]);
                e.addFrame(src); // 添加到帧中
            }
            e.finish();//刷新任何未决的数据,并关闭输出文件
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 

	/**
	 *
	 * @param pics JPEG文件的路径数组
	 * @param newPic 生成后的gif文件
	 * @param delays 每帧之间的间隔
	 */
	public static void jpgToGif(String pics[],String newPic,int[] delays) {
        try {
        	Log.i("jpgToGif","is connection ="+newPic);
            AnimatedGifEncoder e = new AnimatedGifEncoder();
            e.setRepeat(1);
            e.start(newPic);   

            for (int i = 0; i < pics.length; i++) {
                e.setDelay(delays[i]); // 设置播放的延迟时间
                Bitmap src=BitmapFactory.decodeFile(pics[i]);
                e.addFrame(src); // 添加到帧中
            }
            e.finish();//刷新任何未决的数据,并关闭输出文件
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

核心类AnimatedGifEncoder.java 这个之前不适合android,修改了一些android上的相关类库。

 

package com.johdan.gif.merge.java.util;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;

public class AnimatedGifEncoder
{
  protected boolean closeStream;
  protected int colorDepth;
  protected byte[] colorTab;
  protected int delay = 0;
  protected int dispose;
  protected boolean firstFrame;
  protected int height;
  protected Bitmap image;
  protected byte[] indexedPixels;
  protected OutputStream out;
  protected int palSize;
  protected byte[] pixels;
  protected int repeat = -1;
  protected int sample;
  protected boolean sizeSet;
  protected boolean started ;
  protected int transIndex;
  protected int transparent = 0;
  protected boolean[] usedEntry;
  protected int width;
  public AnimatedGifEncoder()
  {
    boolean[] arrayOfBoolean = new boolean[256];
    this.usedEntry = arrayOfBoolean;
    this.palSize = 7;
    this.dispose = -1;
    this.closeStream = false;
    this.firstFrame = true;
    this.sizeSet = false;
    this.sample = 10;
  }
  public boolean addFrame(Bitmap paramBitmap)
  {
    boolean ok = true;
    if(paramBitmap==null  || !started)
    {
     return false;
    }

      try
      {
       Log.i("AnimatedGifEncode...","AnimatedGifEncode is addFrame ="+paramBitmap);
        if (!sizeSet)
        {
          int i = paramBitmap.getWidth();
          int l = paramBitmap.getHeight();
          setSize(i, l);
        }
        this.image = paramBitmap;
        getImagePixels();
        analyzePixels();
      if(firstFrame)
        {
          writeLSD();
          writePalette();
          if(repeat>=0)
            writeNetscapeExt();
        }
        writeGraphicCtrlExt();
        writeImageDesc();

        if (!firstFrame)
          writePalette();
        writePixels();
        this.firstFrame = false;

      }
      catch (IOException localIOException1)
      {
       ok=false;

      }
      return ok;

  }
  protected void analyzePixels()
  {
    int len = this.pixels.length;
    int nPix = len / 3;
    byte[] arrayOfByte1 = new byte[nPix];
    this.indexedPixels = arrayOfByte1;
    byte[] arrayOfByte2 = this.pixels;
    int k = this.sample;
    NeuQuant nq = new NeuQuant(arrayOfByte2, len, k);
    this.colorTab = nq.process();
    int l = 0;
    int i1 = this.colorTab.length;
    Object localObject;
    if (l >= i1)
    {
      l = 0;
      localObject = null;
    }

        for (int i = 0; i < colorTab.length; i += 3) {
               byte temp = colorTab[i];
               colorTab[i] = colorTab[i + 2];
               colorTab[i + 2] = temp;
               usedEntry[i / 3] = false;
           }
        int k1 = 0;
           for (int i = 0; i < nPix; i++) {
               int index =
                   nq.map(pixels[k1++] & 0xff,
                          pixels[k1++] & 0xff,
                          pixels[k1++] & 0xff);
               usedEntry[index] = true;
               indexedPixels[i] = (byte) index;
           }
           pixels = null;
           colorDepth = 8;
           palSize = 7;
           if (transparent != 0) {
               transIndex = findClosest(transparent);
           }   

  }
  protected int findClosest(int paramInt)
  {

    if (colorTab == null)
    {
      return -1;
    }
    int r = Color.red(paramInt);
    int g = Color.green(paramInt);
    int b = Color.blue(paramInt);
    int minpos = 0;
    int dmin = 256 * 256 * 256;
    int len = colorTab.length;   

    for (int i = 0; i < len;) {
        int dr = r - (colorTab[i++] & 0xff);
        int dg = g - (colorTab[i++] & 0xff);
        int db = b - (colorTab[i] & 0xff);
        int d = dr * dr + dg * dg + db * db;
        int index = i / 3;
        if (usedEntry[index] && (d < dmin)) {
            dmin = d;
            minpos = index;
        }
        i++;
    }
    return minpos;
  }

  public boolean finish()
  {
   if (!started) return false;
      boolean ok = true;
      started = false;
      try {
          out.write(0x3b); // gif trailer
          out.flush();
          if (closeStream) {
              out.close();
          }
      } catch (IOException e) {
          ok = false;
      }
      // reset for subsequent use
      transIndex = 0;
      out = null;
      image = null;
      pixels = null;
      indexedPixels = null;
      colorTab = null;
      closeStream = false;
      firstFrame = true;
      return ok;
  }
  protected void getImagePixels()
  {
    int w = this.image.getWidth();
    int h = this.image.getHeight();
    Bitmap.Config localConfig = Bitmap.Config.ARGB_8888;
    Bitmap localBitmap1 = Bitmap.createBitmap(w, h, localConfig);
    Canvas localCanvas = new Canvas(localBitmap1);
    localCanvas.save();
    Paint localPaint = new Paint();
    localCanvas.drawBitmap(image, 0, 0, localPaint);
    localCanvas.restore();

    this.pixels =new byte[w * h * 3];
    int[] arrayOfInt = new int[w * h];
    int k = 0;
    int l = 0;
    int i1 = w;
    localBitmap1.getPixels(arrayOfInt, 0, w, k, l, i1, h);
    int localObject = 0;
    while (true)
    {

      if (localObject >= arrayOfInt.length)
        return;
      pixels[localObject * 3] = (byte)Color.blue(arrayOfInt[localObject]);
      pixels[localObject * 3+1] = (byte)Color.green(arrayOfInt[localObject]);
      pixels[localObject * 3+2] = (byte)Color.red(arrayOfInt[localObject]);
      ++localObject;
    }
  }
  public void setDelay(int ms) {
      delay = Math.round(ms / 10.0f);
  }  

  public void setDispose(int code) {
      if (code >= 0) {
          dispose = code;
      }
  }   

  public void setFrameRate(float fps) {
      if (fps != 0f) {
          delay = Math.round(100f / fps);
      }
  }
  public void setQuality(int quality) {
      if (quality < 1) quality = 1;
      sample = quality;
  }

  public void setRepeat(int iter) {
      if (iter >= 0) {
        Log.i("AnimatedGifEncode...","AnimatedGifEncode is setRepeat..setRepeat =");
          repeat = iter;
      }
  }
  public void setSize(int w, int h) {
      if (started && !firstFrame) return;
      width = w;
      height = h;
      if (width < 1) width = 320;
      if (height < 1) height = 240;
      sizeSet = true;
  }   

  public void setTransparent(int c)
  {
    this.transparent = c;
  }
  public boolean start(OutputStream os)
  {
    if (os == null) return false;
       boolean ok = true;
       closeStream = false;
       out = os;
       Log.i("AnimatedGifEncode...","AnimatedGifEncode is start outputSteam");
       try {
           writeString("GIF89a"); // header
       } catch (IOException e) {
           ok = false;
       }
       return started = ok;
  }

  public boolean start(String file) {
      boolean ok = true;
      try {
          out = new BufferedOutputStream(new FileOutputStream(file));
          ok = start(out);
          Log.i("AnimatedGifEncode...","AnimatedGifEncode is start ="+file);
          closeStream = true;
      } catch (IOException e) {
          ok = false;
      }
      return started = ok;
  }
  protected void writeGraphicCtrlExt() throws IOException {
      out.write(0x21); // extension introducer
      out.write(0xf9); // GCE label
      out.write(4); // data block size
      int transp, disp;
      if (transparent == 0) {
          transp = 0;
          disp = 0; // dispose = no action
      } else {
          transp = 1;
          disp = 2; // force clear if using transparent color
      }
      if (dispose >= 0) {
          disp = dispose & 7; // user override
      }
      disp <<= 2;
      // packed fields
      out.write(0 | // 1:3 reserved
             disp | // 4:6 disposal
                0 | // 7   user input - 0 = none
           transp); // 8   transparency flag
      writeShort(delay); // delay x 1/100 sec
      out.write(transIndex); // transparent color index
      out.write(0); // block terminator
  }
  protected void writeImageDesc() throws IOException {
      out.write(0x2c); // image separator
      writeShort(0); // image position x,y = 0,0
      writeShort(0);
      writeShort(width); // image size
      writeShort(height);
      // packed fields
      if (firstFrame) {
          // no LCT  - GCT is used for first (or only) frame
          out.write(0);
      } else {
          // specify normal LCT
          out.write(0x80 | // 1 local color table  1=yes
                       0 | // 2 interlace - 0=no
                       0 | // 3 sorted - 0=no
                       0 | // 4-5 reserved
                 palSize); // 6-8 size of color table
      }
  }   

  protected void writeLSD() throws IOException {
      // logical screen size
      writeShort(width);
      writeShort(height);
      // packed fields
      out.write((0x80 | // 1   : global color table flag = 1 (gct used)
                 0x70 | // 2-4 : color resolution = 7
                 0x00 | // 5   : gct sort flag = 0
             palSize)); // 6-8 : gct size
      out.write(0); // background color index
      out.write(0); // pixel aspect ratio - assume 1:1
  }  

  protected void writeNetscapeExt() throws IOException {
      out.write(0x21); // extension introducer
      out.write(0xff); // app extension label
      out.write(11); // block size
      writeString("NETSCAPE" + "2.0"); // app id + auth code
      out.write(3); // sub-block size
      out.write(1); // loop sub-block id
      writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
      out.write(0); // block terminator
  }
  protected void writePalette() throws IOException {
      out.write(colorTab, 0, colorTab.length);
      int n = (3 * 256) - colorTab.length;
      for (int i = 0; i < n; i++) {
          out.write(0);
      }
  }   

  protected void writePixels() throws IOException {
      LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
      encoder.encode(out);
  }   

  protected void writeShort(int value) throws IOException {
      out.write(value & 0xff);
      out.write((value >> 8) & 0xff);
  }   

  protected void writeString(String s) throws IOException {
      for (int i = 0; i < s.length(); i++) {
          out.write((byte) s.charAt(i));
          Log.i("AnimatedGifEncode...","AnimatedGifEncode is read header!!!");
      }
  }
}

服务器推送技术,目前应用广泛的大部分都是对xmpp协议的在此封装。 没接触过xmpp?在linux用一些im客户端,默认都会让你添加支持xmpp协议的账户,比如icq、msn等等,另外,不都说qq也是基于xmpp的么,包括android下gmail、gtalk等等也都是基于xmpp协议的。 下面对android下服务器推送技术的一个封装androidpn进行简单的分析,以后还会对xmpp协议的android封装smack进行分析学习。 androidpn也是构建与xmpp协议之上,好在它把服务端与客户端都进行了封装,很容易使用与扩展,提高了很多开发人员的效率,这也是选择它最好的理由。

客户端简易流程

step1:配置客户端
位于工程->res->raw->androidpn.properties文件


apiKey=1234567890  #key
xmppHost=192.168.1.1 #ip
xmppPort=5222 #端口

step2:


//创建新的服务
ServiceManager serviceManager = new ServiceManager(this);
//设置通知栏图标
serviceManager.setNotificationIcon(R.drawable.notification);
//启动服务
serviceManager.startService();

详细分析

初始化ServiceManager:


this.context = context;

//这里获取调用者activity得包名类名
if (context instanceof Activity) {
    Log.i(LOGTAG, "Callback Activity...");
    Activity callbackActivity = (Activity) context;
    callbackActivityPackageName = callbackActivity.getPackageName();
    callbackActivityClassName = callbackActivity.getClass().getName();
}

//loadProperties()读取raw中androidpn.properties文件的内容,并返回Properties对象

props = loadProperties();
apiKey = props.getProperty("apiKey", "");
xmppHost = props.getProperty("xmppHost", "127.0.0.1");
xmppPort = props.getProperty("xmppPort", "5222");

//将上面获取的Properties存入SharedPreferences方便以后直接调用
sharedPrefs = context.getSharedPreferences(
        Constants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
...
...
...
editor.commit();

启动服务startService()


//用一个线程开启服务
Thread serviceThread = new Thread(new Runnable() {
    @Override
    public void run() {
        Intent intent = NotificationService.getIntent();
        context.startService(intent);
    }
});
serviceThread.start();

NotificationService类分析,它是Service的子类,着重分析一下这个Service


public NotificationService() {

    /*NotificationReceiver为BroadcastReceiver的子类
    *用于接收推送广播并用NotificationManager通知用户
    *也就是系统通知栏的通知
    */
    notificationReceiver = new NotificationRece

    /*ConnectivityReceiver接收手机网络状态的广播
    *来管理xmppManager与服务器的连接与断开
    */
    connectivityReceiver = new ConnectivityReceiver(this);

    /*集成于android.telephony.PhoneStateListener,
    *同上,用于监听数据链接的状态
    */
    phoneStateListener = new PhoneStateChangeListener(this);

    //线程池
    executorService = Executors.newSingleThreadExecutor();

    /*TaskSubmitter类包含了向上面的线程池提交一个Task任务
    *的方法
    */
    taskSubmitter = new TaskSubmitter(this);

    /*任务计数器
    *用以维护当前工作的Task
    */
    taskTracker = new TaskTracker(this);
}

一切声明好以后,就开始执行服务了


private void start() {
    Log.d(LOGTAG, "start()...");

    //注册通知广播接收者
    registerNotificationReceiver();

    //注册手机网络连接状态接收者
    registerConnectivityReceiver();
    // Intent intent = getIntent();
    // startService(intent);

    //开始与服务器进行xmpp长链接
    //关于XmppManager后面会有分析
    xmppManager.connect();
}

XmppManager 管理Xmpp链接:


public XmppManager(NotificationService notificationService) {
    context = notificationService;

    //获取Task提交管理器,这里用于维护并行任务
    taskSubmitter = notificationService.getTaskSubmitter();

    //Task的计数器
    taskTracker = notificationService.getTaskTracker();

    //下面都是获取配置信息
    sharedPrefs = notificationService.getSharedPreferences();
    xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost");
    xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222);
    username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
    password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");

   /*设置xmpp链接状态的监听器,查看代码发现Xmpp链接状态有5种
    * 1 connectionClosed
    * 2 connectionClosedOnError
    * 3 reconnectingIn
    * 4 reconnectionFailed
    * 5 reconnectionSuccessful
    */
    connectionListener = new PersistentConnectionListener(this);

    /* 服务器推送监听器
    * 服务器如果有消息推送,NotificationPacketListener会
    * 自己解析好,并通过XmppManager发送广播
    */
    notificationPacketListener = new NotificationPacketListener(this);

    //当xmpp因异常重新连接服务器时,这期间发生异常的话,会在这个handler中处理
    handler = new Handler();

    //任务队列
    taskList = new ArrayList<Runnable>();

    /* 当xmppManager因异常与服务器断开链接时
    * ReconnectionThread会在一定的时间内尝试重新连接
    * 也就是说,当PersistentConnectionListener监听器监听到异常断开连接
    * 会调用ReconnectionThread中重新连接的方法以进行连接尝试
    reconnection = new ReconnectionThread(this);
}

androidpn与服务器连接流程

这里涉及很多smack包的操作,下篇会分析android下xmpp协议的封装smack。

Runable 1: ConnectTask
与服务器建立链接

Runable 1.5: RegisterTask
如果没有配置androidpn客户端的账户信息,它会自动生成一个随机账户并注册到服务器

Runalbe 2: LoginTask
读取本地的账户信息,并登录,开始等待服务器推送消息


===============================================================
主动发送信息给某个用户
------------------------------------
XMPPConnection.DEBUG_ENABLED = true;
//设置服务器地址
XMPPConnection conn = new XMPPConnection("127.0.0.1");
conn.connect();
//输入账号和密码登陆
conn.login("test2@127.0.0.1", "123456");
//创建一个和test1的对话,并设置信息监听
Chat mychat = conn.getManager().create("test1@127.0.0.1",
        new MessageListener() {
            @Override
            public void processMessage(Chat chat,, Message message) {
                String messageBody = message.getBody();
                System.out.println("收到信息:"+messageBody);
            }
});
//发送给test1文本信息
mychat.sendMessage("hello");
//退出登陆
conn.disconnect();
===============================================================
设置自己的登陆状态
------------------------------------
XMPPConnection.DEBUG_ENABLED = true;
//设置服务器地址
XMPPConnection conn = new XMPPConnection("127.0.0.1");
conn.connect();
//输入账号和密码登陆
conn.login("test2@127.0.0.1", "123456"); 

//设置登陆后的个人状态信息
Presence p = new Presence(Presence.Type.available);
p.setStatus("发呆中。。。");
conn.sendPacket(p);

//退出登陆
conn.disconnect();
==========================================================================
被动接收用户发来的信息
------------------------------------
XMPPConnection.DEBUG_ENABLED = true;
//设置服务器地址
XMPPConnection conn = new XMPPConnection("127.0.0.1");
conn.connect();
//输入账号和密码登陆
conn.login("test1@127.0.0.1", "123456");
//设置信息的监听
conn.getChatManager().addChatListener(new ChatManagerListener() {
    @Override
    public void chatCreated(Chat chat, boolean createdLocally) {
        chat.addMessageListener(new MessageListener() {
            @Override
            public void processMessage(Chat chat, Message message) {
                String messageBody = message.getBody();
                System.out.println("接收到信息:"+messageBody);
            }
        });
    }
});
//退出登陆
conn.disconnect();
==========================================================================
获取我的好友列表
------------------------------------
//设置服务器地址
XMPPConnection conn = new XMPPConnection("127.0.0.1");
conn.connect();
//输入账号和密码登陆
conn.login("test1@127.0.0.1", "123456");
Collection<RosterEntry> rosters = conn.getRoster().getEntries();
System.out.println("我的好友列表:=======================");
for(RosterEntry rosterEntry : rosters){
    System.out.print("name: "+rosterEntry.getName()+",jid: "+rosterEntry.getUser());
    System.out.println("");
}
System.out.println("我的好友列表:=======================");

conn.disconnect();
==========================================================================
聊天窗口输入状态,使用XEP-0085 协议
------------------------------------
//发送给test1初始文本信息,附带输入状态
Message mess = new Message();
mess.addExtension(new ChatStateExtension(ChatState.active));
mychat.sendMessage(mess);
//发送给test1初始文本信息,附带正在输入的状态
Message mess = new Message();
mess.addExtension(new ChatStateExtension(ChatState.composing));
mychat.sendMessage(mess);

//发送给test1初始文本信息,附带暂停输入的状态
Message mess = new Message();
mess.addExtension(new ChatStateExtension(ChatState.paused));
mychat.sendMessage(mess);
//其他略。。。

//接收的时候
public void processMessage(Chat chat, Message message) {
    String messageBody = message.getBody();
    PacketExtension pe;

    pe = message.getExtension("composing","http://jabber.org/protocol/chatstates");
    if(pe != null){
        System.out.println("对方正在输入......");
    }

    pe = message.getExtension("active","http://jabber.org/protocol/chatstates");
    if(pe != null){
        System.out.println("接收到信息:"+messageBody);
    }

    pe = message.getExtension("paused","http://jabber.org/protocol/chatstates");
    if(pe != null){
        System.out.println("对方已暂停输入");
    }

    pe = message.getExtension("inactive","http://jabber.org/protocol/chatstates");
    if(pe != null){
        System.out.println("对方聊天窗口失去焦点");
    }

    pe = message.getExtension("gone","http://jabber.org/protocol/chatstates");
    if(pe != null){
        System.out.println("对方聊天窗口被关闭");
    }
}
==========================================================================
接收邀请,加入多人聊天房间
------------------------------------
MultiUserChat.addInvitationListener(conn, new InvitationListener() {
    @Override
    public void invitationReceived(XMPPConnection conn, String room,
            String inviter, String reason, String password, Message message) {
        MultiUserChat multiUserChat = new MultiUserChat(conn, room);
        System.out.println("收到来自 "+inviter+" 的聊天室邀请。邀请附带内容:"+reason);
        try {
            multiUserChat.join("test2", password);
        } catch (XMPPException e) {
            System.out.println("加入聊天室失败");
            e.printStackTrace();
        }
        System.out.println("成功加入聊天室");
        multiUserChat.addMessageListener(new PacketListener() {
            @Override
            public void processPacket(Packet packet) {
                Message message = (Message)packet;
                //接收来自聊天室的聊天信息
                System.out.println(message.getFrom()+":"+message.getBody());
            }
        });
    }
    //发送信息到聊天室
    multiUserChat.sendMessage("新手到来,大家关照!");
});

==========================================================================
登陆gtalk
------------------------------------
XMPPConnection conn = new XMPPConnection(new ConnectionConfiguration("talk.google.com", 5222, "gmail.com"));
conn.connect();
//输入gtalk的账号密码
conn.login("88888888", "8888888888");
Collection<RosterEntry> rosters = conn.getRoster().getEntries();
//获取gtalk上的好友列表
System.out.println("我的好友列表:=======================");
for(RosterEntry rosterEntry : rosters){
    System.out.print("name: "+rosterEntry.getName()+",jid: "+rosterEntry.getUser());
    System.out.println("");
}
System.out.println("我的好友列表:=======================");

conn.disconnect();
==========================================================================

最近几天研究android下GIF相关知识,如GIF播放,GIF合成,GIF的解码,得到了一点收获,赶紧共享出来给需要的哥们。

本篇文章讲的是android下通过NDK开发,使用JNI讲多张图片合成GIF图片

首先看一下最终效果:

这三个图片为和成前的图片。

废话不说,直入主题。

 

一、NDK环境的搭建与Eclipse配置NDK_R7开发环境

这里主要是大家查下网上的资料即可,我参考的是:

http://blog.csdn.net/chaseli1984/article/details/7166542

 

二、下载合成GIF的C++相关源码文件

http://jiggawatt.org/badc0de/android/index.html#gifflen

 三、使用gifflen

1、在你的项目中根目录建立jni文件夹

2、将下载好解压缩后的文件放入jni文件夹下

3、修正对应的方法名。

NDK里面书写方法的名称需同你native 类的包名相同,举个例子我的native方法类的完整路径是:com.johdan.gif.merge.util.GifUtil.java那么我需要将下载回来的源码里面的jni call的方法名称改为以Java_com_johdan_gif_merge_util_GifUtil开头即可。

4、编译你的JNI,用cygwin进入该项目的jni目录下,或者使用配置eclipse的NDK builder 也可以。

我这里用的是eclipse编译JNI。

四、编译通过后,大功告成,直接使用吧!

这里我准备了一个Demo,想尝试的同学可以看看下载安装看看演示效果。

核心代码:

package com.johdan.gif.merge;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import com.johdan.gif.merge.util.GifUtil;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;
/**
 * MainActivity
 * @author www.jodhan.com
 *
 */
public class MainActivity extends Activity {

	Boolean isSuccess =false;
	ProgressDialog progressDialog;

	private Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what){
				case 1:
					if (progressDialog != null){
						progressDialog.dismiss();
					}
					Toast.makeText(getApplicationContext(), "图片合成成功,请查看SD卡test.gif!", 2000).show();
					break;
				case 2:
			        getGif();
			        break;

			}
		}

	};

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main); 

        progressDialog=new ProgressDialog(this);
    	progressDialog.setMessage("图片合成中,请稍等。");
    	progressDialog.show();

        getGif();

    }

	public void getGif() {
		new Thread(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				String filepath=Environment.getExternalStorageDirectory() +"/test.gif";
				isSuccess = convertBitmapToGIF(filepath);
				Message msg= new Message();
				if(isSuccess){
					msg.what =1;
					mHandler.sendMessage(msg);
					isSuccess=false;
				}else{
					msg.what =2;
					mHandler.sendMessage(msg);
				}
				super.run();
			}

        }.start();
	}

   /**
    * 合成gif
    * @param filepath 保存的路径
    * @return
    */
   public boolean convertBitmapToGIF(String filepath){
	   Boolean isSuccess=false;
	   Bitmap[] bitmaps = getBitmaps();
	   int delay =100;
	   new GifUtil().Encode(filepath, bitmaps, delay);
	   File f=new File(filepath);
	   if(f.exists()){
		   isSuccess=true;
	   }
	   return isSuccess;
   }

   /**
    * 产生多个bitmap
    * @return
    */
   public  Bitmap[] getBitmaps(){
	   Bitmap[] bitmaps =new Bitmap[3];
	   String sdPath=Environment.getExternalStorageDirectory() +"";

	   File file1 = new File(sdPath+"/1.jpg");
	   File file2 = new File(sdPath+"/2.jpg");
	   File file3 = new File(sdPath+"/3.jpg");

	   Bitmap bitmap1 = decodeFile(file1);
	   Bitmap bitmap2 = decodeFile(file2);
	   Bitmap bitmap3 = decodeFile(file3);

	   bitmaps[0]=bitmap1;
	   bitmaps[1]=bitmap2;
	   bitmaps[2]=bitmap3;

	   return bitmaps;
   }    

	/**
	 * 将对应的文件转换为bitmap
	 * decodes image and scales it to reduce memory consumption
	 * @param f
	 * @return
	 */
	private Bitmap decodeFile(File f) {
		try {
			// decode image size
			BitmapFactory.Options o = new BitmapFactory.Options();
			o.inJustDecodeBounds = true;
			BitmapFactory.decodeStream(new FileInputStream(f), null, o);

			// Find the correct scale value. It should be the power of 2.
			final int REQUIRED_SIZE = 70;
			int width_tmp = o.outWidth, height_tmp = o.outHeight;
			int scale = 1;
			while (true) {
				if (width_tmp / 2 < REQUIRED_SIZE
						|| height_tmp / 2 < REQUIRED_SIZE)
					break;
				width_tmp /= 2;
				height_tmp /= 2;
				scale *= 2;
			}

			// decode with inSampleSize
			BitmapFactory.Options o2 = new BitmapFactory.Options();
			o2.inSampleSize = scale;
			return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
		} catch (FileNotFoundException e) {
		}
		return null;
	}

}

 

 


package com.johdan.gif.merge.util;

import android.graphics.Bitmap;
import android.util.Log;

/**
 * 需要new 出本类,然后调用Encode方法即可
 * @param fileName 文件保存路径
 * @param bitmaps  bitmap的集合
 * @param delay    每帧间隔的秒
 * @author www.johdan.com
 *
 */
public class GifUtil {

//	 private static final String PATH = "/data/data/com.bingzer.android.stickdraw/lib/libgifflen.so";
//	 private boolean hasError = false;
//
//	 public GifUtil(){
//	    try{
//	      System.load("/data/data/com.bingzer.android.stickdraw/lib/libgifflen.so");
//	      return;
//	    }
//	    catch (Throwable localThrowable){
//	      Log.e("Giffle", "not found: /data/data/cn.qtt.giffle/lib/libgifflen.so");
//	      this.hasError = true;
//	    }
//	 }
//	 public boolean hasError(){
//	    return this.hasError;
//	 }

	private final String TAG=this.getClass().getName();
	static
	{
		System.loadLibrary("gifflen");
	}

	/**
	 * Init the gif file
	 * @param gifName name
	 * @param w width
	 * @param h height
	 * @param numColors colors
	 * @param quality
	 * @param frameDelay times
	 * @return
	 */
	public native int Init(String gifName, int w, int h, int numColors, int quality,
            int frameDelay);

	/*
	 * close
	 *
	 */
	public native void Close();

	public native int AddFrame(int[] pixels);

	/**
	 * encode the bitmaps to gif
	 * @param fileName
	 * @param bitmaps
	 * @param delay
	 */
	public void Encode(String fileName,Bitmap[] bitmaps,int delay)
	{
		if(bitmaps==null||bitmaps.length==0)
		{
			throw new NullPointerException("Bitmaps should have content!!!");

		}
		int width=bitmaps[0].getWidth();
		int height=bitmaps[0].getHeight();

		if(Init(fileName,width,height,256,100,delay)!=0)
		{
			Log.e(TAG, "GifUtil init failed");
			return;
		}

		for(Bitmap bp:bitmaps)
		{

			int pixels[]=new int[width*height];	

			bp.getPixels(pixels, 0, width, 0, 0, width, height);
			AddFrame(pixels);
		}

		Close();

	}

}


Demo下载:GifMerge_GIF本地图片合成案例

最后,感谢leepood的文章http://blog.leepood.com/new-to-android/android-gif-maker

下篇文章将分享一下GIF解码的相关知识,敬请期待,欢迎大家拍砖指导。