Janseon 2015-12-11T09:19:04+00:00 janseon@gmail.com Afinal扩展 2014-06-12T20:30:00+00:00 Janseon http://janseon.github.io/android-afinal-extend Afinal是什么,到github看看吧:Afinal

而该文章中,为Afinal扩展了什么,详细代码可以到我的github看看:Afinal扩展

1、添加publishStart

添加这个方法,主要是为了给http相关的操作进行扩展。

通过这个扩展,可以使得在http请求进行之前,先加载之前曾经请求的http缓存。

在下面的类中,进行修改:

1 net.tsz.afinal.http.HttpHandler

修改为:

 1 @Override
 2    protected Object doInBackground(Object... params) {
 3       if (params != null && params.length == 3) {
 4          targetUrl = String.valueOf(params[1]);
 5          isResume = (Boolean) params[2];
 6       }
 7       try {
 8          publishStart();
 9          makeRequestWithRetries((HttpUriRequest) params[0]);
10    
11       } catch (IOException e) {
12          String msg = e.getMessage();
13          if (msg == null) {
14             msg = e.toString();
15          }
16          publishProgress(UPDATE_FAILURE, e, 0, msg); // 结束
17       }
18    
19       return null;
20    }

因为publicProgress方法是final方法,不能被重写;

所以封装一下publicProgress(UPDATE_START)方法的调用,那么pubicStart()方法就可以被重写了。

1 protected void publishStart() {
2       publishProgress(UPDATE_START); // 开始
3    }

使用时,继承HttpHandler,重写如下,进行缓存的加载:

 1 @Override
 2    protected void publishStart() {
 3       super.publishStart();
 4       if (ajaxCallBack instanceof AsyncCallBack) {
 5          final AsyncCallBack asyncCallBack = ((AsyncCallBack) ajaxCallBack);
 6          if (asyncCallBack.toLoad == 1) {
 7             String urlId = getUrlId();
 8             final Response response = Response.load(urlId);
 9             if (response != null) {
10                parse(response, null);
11                AppContext.runUI(new Runnable() {
12                   @Override
13                   public void run() {
14                      asyncCallBack.onLoadResponse(response);
15                   }
16                });
17             }
18          }
19       }
20    }

2、加载图片时,显示进度

显示如下加载进度:

img

修改如下类:

1 net.tsz.afinal.FinalBitmap

修改如下:

  1 public static abstract class AsyncDrawable extends Drawable {
  2       private final BitmapLoadAndDisplayTask mTask;
  3       public Drawable mDrawable;
  4    
  5       /**
  6        * 画笔对象的引用
  7        */
  8       private Paint paint;
  9    
 10       /**
 11        * 圆环的颜色
 12        */
 13       private int roundColor;
 14    
 15       /**
 16        * 圆环进度的颜色
 17        */
 18       private int roundProgressColor;
 19    
 20       /**
 21        * 中间进度百分比的字符串的颜色
 22        */
 23       private int textColor;
 24    
 25       /**
 26        * 中间进度百分比的字符串的字体
 27        */
 28       private float textSize;
 29    
 30       /**
 31        * 圆环的宽度
 32        */
 33       private float roundWidth;
 34       /**
 35        * 圆环的半径
 36        */
 37       private float roundRadius;
 38    
 39       /**
 40        * 当前进度
 41        */
 42       private int progress;
 43    
 44       public AsyncDrawable(Context context, BitmapLoadAndDisplayTask bitmapWorkerTask) {
 45          mTask = bitmapWorkerTask;
 46          paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 47          paint.setAntiAlias(true);
 48          DisplayMetrics metrics = context.getResources().getDisplayMetrics();
 49          textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, metrics);
 50          roundWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, metrics);
 51          roundRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, metrics);
 52          roundColor = Color.WHITE;
 53          roundProgressColor = Color.BLUE;
 54          textColor = Color.WHITE;
 55          progress = -1;
 56       }
 57    
 58       public BitmapLoadAndDisplayTask getBitmapWorkerTask() {
 59          return mTask;
 60       }
 61    
 62       @Override
 63       public void draw(Canvas canvas) {
 64          Rect bounds = getBounds();
 65          if (mDrawable != null) {
 66             mDrawable.setBounds(bounds);
 67             mDrawable.draw(canvas);
 68          }
 69          if (progress == -1) {
 70             return;
 71          }
 72          // if (bounds.width() / 2 < roundRadius || bounds.height() / 2 <
 73          // roundRadius) {
 74          // return;
 75          // }
 76    
 77          /**
 78           * 画最外层的大圆环
 79           */
 80          int centreX = bounds.centerX(); // 获取圆心的x坐标
 81          int centreY = bounds.centerY(); // 获取圆心的x坐标
 82    
 83          paint.setColor(roundColor); // 设置圆环的颜色
 84          paint.setStyle(Paint.Style.STROKE); // 设置空心
 85          paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
 86          canvas.drawCircle(centreX, centreY, roundRadius, paint); // 画出圆环
 87    
 88          /**
 89           * 画圆弧 ,画圆环的进度
 90           */
 91          paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
 92          paint.setColor(roundProgressColor); // 设置进度的颜色
 93          RectF oval = new RectF(centreX - roundRadius, centreY - roundRadius, centreX + roundRadius, centreY + roundRadius); // 用于定义的圆弧的形状和大小的界限
 94          paint.setStyle(Paint.Style.STROKE);
 95          canvas.drawArc(oval, 0, 360 * progress / 100, false, paint); // 根据进度画圆弧
 96    
 97          /**
 98           * 画进度百分比
 99           */
100          paint.setStrokeWidth(0);
101          paint.setColor(textColor);
102          paint.setTextSize(textSize);
103          // paint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体
104          String progressText = progress + "%";
105          float textWidth = paint.measureText(progressText); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间
106          canvas.drawText(progressText, centreX - textWidth / 2, centreY + textSize / 2, paint); // 画出进度百分比
107       }
108    
109       public void setProgress(int progress) {
110          this.progress = progress;
111          invalidateSelf();
112       }
113    
114       @Override
115       public int getOpacity() {
116          return 0;
117       }
118    
119       @Override
120       public void setAlpha(int arg0) {
121       }
122    
123       @Override
124       public void setColorFilter(ColorFilter arg0) {
125       }
126    };

以上类,详细的实现可以看draw方法会progress进行绘制的操作

 1 public static class AsyncColorDrawable extends AsyncDrawable {
 2       public static int color = 0xaabbbbbb;
 3    
 4       public AsyncColorDrawable(Context context, BitmapLoadAndDisplayTask bitmapWorkerTask) {
 5          this(context, bitmapWorkerTask, color);
 6       }
 7    
 8       public AsyncColorDrawable(Context context, BitmapLoadAndDisplayTask bitmapWorkerTask, float radius) {
 9          super(context, bitmapWorkerTask);
10          if (radius == -1) {
11             ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
12             shapeDrawable.getPaint().setColor(color);
13             mDrawable = shapeDrawable;
14          } else if (radius == 0) {
15             mDrawable = new ColorDrawable(color);
16          } else if (radius > 0) {
17             float[] outerR = new float[] { radius, radius, radius, radius, radius, radius, radius, radius };
18             RoundRectShape roundRectShape = new RoundRectShape(outerR, null, null);
19             ShapeDrawable shapeDrawable = new ShapeDrawable(roundRectShape);
20             shapeDrawable.getPaint().setColor(color);
21             mDrawable = shapeDrawable;
22          }
23       }
24    
25       public AsyncColorDrawable(Context context, BitmapLoadAndDisplayTask bitmapWorkerTask, int color) {
26          super(context, bitmapWorkerTask);
27          mDrawable = new ColorDrawable(color);
28       }
29    }

以上只是简单的一个继承,实现了一个背景是灰色的默认显示,如加载前的默认显示和加载失败之后的显示。

 1 private class BitmapLoadAndDisplayTask extends AsyncTask<Object, Integer, Bitmap> {
 2       
 3       @Override
 4       protected Bitmap doInBackground(Object... params) {
 5          dataString = (String) params[0];
 6          Bitmap bitmap = null;
 7    
 8          synchronized (mPauseWorkLock) {
 9             while (mPauseWork && !isCancelled()) {
10                try {
11                   mPauseWorkLock.wait();
12                } catch (InterruptedException e) {
13                }
14             }
15          }
16          
17          if (bitmap == null && !isCancelled() && !mExitTasksEarly) {
18             if (displayConfig.getLoading()) {
19                bitmap = processBitmap(dataString, displayConfig, new ProgressCallBack() {
20                   @Override
21                   public void callBack(long count, long current) {
22                      int progress = (int) (current * 100 / count);
23                      publishProgress(progress);
24                   }
25                });
26             } else {
27                bitmap = processBitmap(dataString, displayConfig, null);
28             }
29          }
30    
31          if (bitmap != null) {
32             mImageCache.addToMemoryCache(dataString, bitmap);
33          }
34          return bitmap;
35       }
36    
37       @Override
38       protected void onProgressUpdate(Integer... values) {
39          int progress = values[0];
40          final View[] imageViews = getAttachedImageView();
41          for (View imageView : imageViews) {
42             if (imageView instanceof ImageView) {
43                Drawable drawable = ((ImageView) imageView).getDrawable();
44                if (drawable instanceof AsyncDrawable) {
45                   ((AsyncDrawable) drawable).setProgress(progress);
46                }
47             }
48          }
49       }
50    }

以上主要是看onProgressUpdate的更新方法。

那么还要修改http请求,使得加载图片有进度的显示

修改如下类:

1 net.tsz.afinal.bitmap.download.SimpleDownloader
 1 private byte[] getFromHttp(String urlString, ProgressCallBack callBack) {
 2       HttpURLConnection urlConnection = null;
 3       BufferedOutputStream out = null;
 4       FlushedInputStream in = null;
 5    
 6       try {
 7          final URL url = new URL(urlString);
 8          urlConnection = (HttpURLConnection) url.openConnection();
 9          in = new FlushedInputStream(new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE));
10          ByteArrayOutputStream baos = new ByteArrayOutputStream();
11          int b;
12          if (callBack == null) {
13             while ((b = in.read()) != -1) {
14                baos.write(b);
15             }
16          } else {
17             int len = urlConnection.getContentLength();
18             if (len == 0) {
19                return null;
20             }
21             final int progressSize;
22             if (len < PEOGRESS_SIZE * 5) {
23                progressSize = len / 5;
24             } else {
25                progressSize = PEOGRESS_SIZE;
26             }
27             long current = 0;
28             callBack.callBack(len, current);
29             int callLen = 0;
30             while ((b = in.read()) != -1) {
31                baos.write(b);
32                callLen++;
33                if (current == len || callLen > progressSize) {
34                   // SystemClock.sleep(500);
35                   current += callLen;
36                   callBack.callBack(len, current);
37                   callLen = 0;
38                }
39             }
40          }
41          return baos.toByteArray();
42       } catch (final IOException e) {
43          Log.e(TAG, "Error in downloadBitmap - " + urlString + " : " + e);
44       } finally {
45          if (urlConnection != null) {
46             urlConnection.disconnect();
47          }
48          try {
49             if (out != null) {
50                out.close();
51             }
52             if (in != null) {
53                in.close();
54             }
55          } catch (final IOException e) {
56          }
57       }
58       return null;
59    }

3、同一个链接,避免多次http请求

doDisplay方法中:

修改如下,主要是封装了一个createAsyncDrawable方法

 1 if (bitmap != null) {
 2       if (imageView instanceof ImageView) {
 3          ((ImageView) imageView).setImageBitmap(bitmap);
 4       } else {
 5          imageView.setBackgroundDrawable(new BitmapDrawable(bitmap));
 6       }
 7    }
 8    else {
 9       createAsyncDrawable(imageView, uri, displayConfig);
10    }

主要实现方式是:维持一个对AsyncDrawable对象的集合的缓存引用:mAsyncDrawablesCache

 1 private AsyncDrawable createAsyncDrawable(View imageView, String uri, BitmapDisplayConfig config) {
 2       AsyncDrawable asyncDrawable = mAsyncDrawablesCache.get(uri);
 3       if (asyncDrawable != null) {
 4          setImageDrawable(imageView, asyncDrawable);
 5          BitmapLoadAndDisplayTask task = asyncDrawable.getBitmapWorkerTask();
 6          task.putImageView(imageView);
 7          Log.i("setImage", "putImageView,imageView,url=" + task + "," + imageView + "," + uri);
 8          return asyncDrawable;
 9       }
10       BitmapLoadAndDisplayTask task = new BitmapLoadAndDisplayTask(imageView, config);
11       Log.i("setImage", "BitmapLoadAndDisplayTask,imageView,url=" + task + "," + imageView + "," + uri);
12       Bitmap bitmap = config.getLoadingBitmap();
13       asyncDrawable = bitmap == null ? new AsyncColorDrawable(imageView.getContext(), task, config.getLoadingRadius()) : new AsyncBitmapDrawable(
14             imageView.getContext(), task, bitmap);
15       asyncDrawable.roundProgressColor = config.getLoadingColor();
16       setImageDrawable(imageView, asyncDrawable);
17       mAsyncDrawablesCache.put(uri, asyncDrawable);
18       task.executeOnExecutor(bitmapLoadAndDisplayExecutor, uri);
19       return asyncDrawable;
20    }
1 private static final SoftMemoryCache<AsyncDrawable> mAsyncDrawablesCache = new SoftMemoryCache<AsyncDrawable>();

BitmapLoadAndDisplayTask 中又维持一个View的列表引用:

 1 private class BitmapLoadAndDisplayTask extends AsyncTask<Object, Integer, Bitmap> {
 2       private String dataString;
 3       private ArrayList<WeakReference<View>> imageViewReferences = new ArrayList<WeakReference<View>>();
 4       private final BitmapDisplayConfig displayConfig;
 5    
 6       public BitmapLoadAndDisplayTask(View imageView, BitmapDisplayConfig config) {
 7          // imageViewReference = new WeakReference<View>(imageView);
 8          imageViewReferences.add(new WeakReference<View>(imageView));
 9          displayConfig = config;
10       }
11    
12       public void putImageView(View imageView) {
13          imageViewReferences.add(new WeakReference<View>(imageView));
14       }
15        //省略...
16       @Override
17       protected void onProgressUpdate(Integer... values) {
18          int progress = values[0];
19          final View[] imageViews = getAttachedImageView();
20          for (View imageView : imageViews) {
21             if (imageView instanceof ImageView) {
22                Drawable drawable = ((ImageView) imageView).getDrawable();
23                if (drawable instanceof AsyncDrawable) {
24                   ((AsyncDrawable) drawable).setProgress(progress);
25                }
26             }
27          }
28       }
29    
30       @Override
31       protected void onPostExecute(Bitmap bitmap) {
32          mAsyncDrawablesCache.remove(dataString);
33          if (isCancelled() || mExitTasksEarly) {
34             bitmap = null;
35          }
36          // 判断线程和当前的imageview是否是匹配
37    
38          Log.i("setImage", "onPostExecute");
39          // final View imageView = getAttachedImageView();
40    
41          final View[] imageViews = getAttachedImageView();
42          for (View imageView : imageViews) {
43             if (bitmap != null && imageView != null) {
44                mConfig.displayer.loadCompletedisplay(imageView, bitmap, displayConfig);
45             } else if (bitmap == null && imageView != null) {
46                mConfig.displayer.loadFailDisplay(imageView, displayConfig.getLoadfailBitmap());
47             }
48          }
49       }
50    
51       // 获取线程匹配的imageView,防止出现闪动的现象
52       private View[] getAttachedImageView() {
53          View[] views = new View[imageViewReferences.size()];
54          int i = 0;
55          for (WeakReference<View> imageViewReference : imageViewReferences) {
56             final View imageView = imageViewReference.get();
57             if (imageView != null) {
58                final BitmapLoadAndDisplayTask bitmapWorkerTask = getBitmapTaskFromImageView(imageView);
59                Log.i("setImage", "getAttachedImageView,imageView,url=" + this + "," + imageView + "," + dataString);
60                if (this == bitmapWorkerTask) {
61                   views[i++] = imageView;
62                }
63             }
64          }
65          return views;
66       }
67    }

4、三级缓存

哪三级缓存:

  1. 根据url和图片大小,构造key,获取内存中的bitmap;如果为空,则 2;
  2. 根据 1 中构造的key,获取磁盘中的bitmap data缓存;如果为空,则 3;
  3. 根据url,获取磁盘中的bitmap data缓存;如果不为空,则 4 ;如果为空则加载http 图片,如 5 ;
  4. 根据加载的bitmap data,裁剪成需要的bitmap大小,并且添加进磁盘和内存缓存;
  5. 根据url进行http请求,返回bitmap data,把bitmap data储存进磁盘缓存,并且裁剪成需要的大小的bitmap data,然后分别添加进磁盘缓存和bitmap缓存。

修改如下类:

1 net.tsz.afinal.bitmap.core.BitmapProcess

主要方法:http请求成功之后:调用mCache.addToDiskCache()

 1 public Bitmap getBitmap(String url, BitmapDisplayConfig config, ProgressCallBack callBack) {
 2    
 3       Bitmap bitmap = getFromDisk(url, config);
 4    
 5       if (bitmap == null) {
 6          byte[] data = mDownloader.download(url, callBack);
 7          if (data != null && data.length > 0) {
 8             mCache.addToDiskCache(url, data);
 9             if (config != null)
10                bitmap = BitmapDecoder.decodeSampledBitmapFromByteArray(data, 0, data.length, config.getBitmapWidth(), config.getBitmapHeight());
11             else
12                bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
13          }
14       }
15       return bitmap;
16    }

主要方法:从磁盘缓存加载url对应的图片之后,进行裁剪,调用:BitmapDecoder.decodeSampledBitmapFromByteArray()方法:

 1 public Bitmap getFromDisk(String key, BitmapDisplayConfig config) {
 2       BytesBuffer buffer = sMicroThumbBufferPool.get();
 3       Bitmap b = null;
 4       try {
 5          boolean found = mCache.getImageData(key, buffer);
 6          if (found && buffer.length - buffer.offset > 0) {
 7             if (config != null) {
 8                b = BitmapDecoder.decodeSampledBitmapFromByteArray(buffer.data, buffer.offset, buffer.length, config.getBitmapWidth(),
 9                      config.getBitmapHeight());
10             } else {
11                b = BitmapFactory.decodeByteArray(buffer.data, buffer.offset, buffer.length);
12             }
13          }
14       } finally {
15          sMicroThumbBufferPool.recycle(buffer);
16       }
17       return b;
18    }

在以下内部类进行修改:

1 net.tsz.afinal.FinalBitmap.BitmapLoadAndDisplayTask

主要方法:把裁剪后的图片添加到内存缓存:

 1 @Override
 2    protected Bitmap doInBackground(Object... params) {
 3       dataString = (String) params[0];
 4       Bitmap bitmap = null;
 5    
 6       synchronized (mPauseWorkLock) {
 7          while (mPauseWork && !isCancelled()) {
 8             try {
 9                mPauseWorkLock.wait();
10             } catch (InterruptedException e) {
11             }
12          }
13       }
14       if (bitmap == null && !isCancelled() && !mExitTasksEarly) {
15          if (displayConfig.getLoading()) {
16             bitmap = processBitmap(dataString, displayConfig, new ProgressCallBack() {
17                @Override
18                public void callBack(long count, long current) {
19                   int progress = (int) (current * 100 / count);
20                   publishProgress(progress);
21                }
22             });
23          } else {
24             bitmap = processBitmap(dataString, displayConfig, null);
25          }
26       }
27    
28       if (bitmap != null) {
29          mImageCache.addToMemoryCache(dataString, bitmap);
30       }
31       return bitmap;
32    }
]]>
android实时语音方案 2014-02-14T20:30:00+00:00 Janseon http://janseon.github.io/android-speex-speech img

客户端(Android)

客户端需要使用AudioRecord、AudioTrack、Thread、UDP/TCP、Speex等知识和技术。

  • 说话(录制)端:
  1. 新线程Thread创建AudioRecord对象并启动录制,然后循环调用AudioRecord.read()方法,获得PCM数据;
  2. 再使用speex对PCM数据进行编码和压缩,生成spx数据包;
  3. 再将spx数据包压到另外一个线程的数据处理队列中;
  4. 在另外一个线程的run里面,循环从队列里面取出一个spx数据包,然后使用UDP或TCP打包传输到服务端;服务端会把这些数据包转发到另一个客户端。
  • 收听(播放)端:
  1. 新线程使用UDP或TCP接收服务端转发的数据包;
  2. 对接收到的数据包进行解包,生成spx数据包,并压到另外一个线程的数据处理队列中;
  3. 在另外一个线程的run里面循环从队列里面取出一个spx数据包并使用speex对spx数据包进行解码,生成PCM数据;
  4. 调用audioTrack.write()方法把PCM数据写到AudioTrack对象的缓冲区中,这样子AudioTrack对象就能对PCM数据进行播放;

服务端(语音转发服务器):

  1. 使用UDP或TCP协议对数据包进行转发,服务端支持多客户端的链接和消息的收发。
  2. 这个服务端是专门服务于实时语音的,与同一个app中的其他后台服务分离。
  3. 客户端呼叫和接收呼叫,这些操作都不属于这个语音转发服务器,而属于app中原来聊天的服务器管理的;
  4. 对端一旦接受了呼叫,那么这两个将要对话的客户端就会在这个语音转发服务器都建立了连接,这时候,这两个客户端就可以通过这个服务端的转发,实时地发送和接收语音了。

现在客户端已经实现了一个一边录制和speex编码,一边speex解码和播放的的demo。

]]>
Android项目开发注意事项 2013-12-25T20:30:00+00:00 Janseon http://janseon.github.io/android-note

  • 1、建议把项目一分为二,第一部分是作为Libarary的项目,第二部分是相应的App项目。

其中App项目引用Libarary的项目;Libarary项目作为一个公共的库,主要是一些工具类,或者自定义的一些控件,这些工具类和控件与App项目分离,可以供以后其他的项目使用。

Libarary的项目和App项目共存:

img

App的目录,把与本应用相关的内容放到这个项目中:

img

Libarary的项目:

img

好处:管理方便、逻辑和界面可以更好的分离、App的项目目录冗余减少、有利于积累一些公共的代码;

可以从引用的库中引用资源

img

如果使用这种方式来创建应用,那么在svn中也需要在对应的应用目录中创建这对应的两个目录。

  • 2、在项目中要用到的文本,尽量在xml里面定义,然后在java代码中引用。

在xml文件修改比在java中去寻找然后修改方便。

  • 3、要考虑到Activity和进程被杀掉的情况;除了正常退出Activity外,还有可能是:Activity因其他原因被杀,比如系统内存过低,系统配置变更,有异常等等,要考虑和测试这种情况,特别是Activity处理重要的数据时,做好的数据的保存。

  • 4、Activity间传数据,要用Intent传数据,避免用静态变量传递数据。

  • 5、不要用四大组件去实现接口;一是组件的对象都比较大,实现接口比较浪费,而且让代码更不易读和理解;另外更重要的是导致多方引用,可能会引发内存泄露。

浪费:引用时传入一个大对象,调用接口方法时也是调用这个大对象的方法;

易读性:使得对象的方法增多,增加复杂,并且没有明显表明实现的是哪个接口的方法;

内存泄露:一旦某个可能会常驻内存的对象引用了这个对象,就会造成内存泄漏;

  • 6、用getApplication()来取Context当参数;对于需要使用Context对象作为参数的函数,要使用getApplication()获取Context对象当参数,而不要使用this,除非你需要特定的组件实例!getApplication()返回的Context是属于Application的,它会在整个应用的生命周期内存在,远大于某个组件的生命周期,所以即使某个引用长期持有Context对象也不会引发内存泄露。

  • 7、Android程序员应该要学会使用draw9patch.bat工具进行.9格式图片的绘制。

  • 8、当线程空闲的时候,应该要让线程阻塞。

线程阻塞可以让更多的时间交回给主线程,这样子使得主线程绘制界面的时候更流畅;

img img

  • 9、wifi,3G,2G处理规范。

网速不一样;根据不同的网速做不同的超时处理;

  • 10、SDCard的情况

当有使用SDCard时,先检测是否存在,以及空间是否满了

  • 11、令牌失效的处理。

一般情况下,令牌失效会返回一个规定好的负的code值,如-200;

令牌失效一般处理为跳到重新登录的界面,进行登录;

  • 12、Json数据的解析,改为使用opt类的方法。

img

  • 13、如果一个View是使用了图片资源,那么最好不要设置wrap_content;

除非你的这个图在各种设备上都有对应的资源

img

  • 14、每个应用务必做好本地缓存,支持离线浏览。

  • 15、尽量要支持离线操作。

断网的情况下,也可以发表状态、上传图片等,并且可以立即显示上传后的效果;等到下次有网络连接时,再在后台进行http请求。

  • 16、有需要的,应该要实现上传队列。

在后台进行图片的上传操作,这些操作对用户的其他的操作没有任何影响;

  • 17、处理应用的重要异常和错误,将异常和错误以某种形式发送给服务端。

本地做好错误的log日志;

调用服务器接口,发送错误信息;

以邮件形式发送服务器;

  • 18、刚打开应用程序就会进行检测版本的更新。
]]>
Android经验总结2013.12 2013-12-24T20:30:00+00:00 Janseon http://janseon.github.io/android-Summary-2013

一、Android学习过程

1.认识:

  • 认识选择开发android的原因,认识开发android的优势、魅力和前景。

2.入门:

  • 如果对java语言还不熟悉的,那就需要花些日子来学习java;弄清楚开发android应用的一个简单的过程和需要掌握的知识技术;

3.熟练:

  • 要想熟练,那就多交流、多实践、看官方文档、逛论坛、写一些简单的自定义控件和小项目……
  • 一般初学者需要掌握的基本内容:各基本组件的使用;各基本控件的布局和使用;异步操作、数据存储、图片的缓存机制、http网络服务、android项目开发的规范;

4.项目开发:

  • 开发一个完整的比较简单的android项目,如微信导航等;

5.积累:

  • 在项目开发的过程中,加强自己模块化的思想,积累更多属于自己的模块,建立自己的一些代码库;

6.提升:

  • 可以找系统自带的开源项目或者是一些开发者的开源大项目来研究,或者反编译一些常用的应用来研究,总结开发的思路、开发布局和开发框架;

7.进阶:

  • 深入研究和理解android的SDK实现的源代码;
  • 这个阶段是探索为什么的阶段,例如ListView控件是怎样实现的,为什么这样实现,有什么优缺点,还能怎样优化等;如View的测量和绘制过程,哪些方法起着至关重要的作用等;如进程间的通信机制、组件之间的通信机制等;又如一个应用是怎么运行起来的,运行过程是怎样的,为什么这样运行等等;

8.创造:

  • 自己去实现一些复杂的组件或者控件;只要你想得到界面效果,就能创造和实现;
  • 如自定义类似于TabActivity的组件、自己实现一个ListView、ScrollView、Gallery等较复杂但有趣的控件、善于使用ViewGroup的滚动属性和Touch事件,定义出各种滚动界面、善于使用View的一些Draw方法绘制出各种你想要的画面……

9.高级:

  • 就是所谓的高级内容:Android的内核框架、JNI、,openGL ES控件编程、Android移植编程……

10.扩展:

  • 理解JAVA相关的机制:虚拟机、JNI、垃圾回收、各种强弱引用的机制、进程线程的通信机制等;
  • 深刻理解一些网络协议,如http、socket以及其他一些常用的网络协议;
  • 应该至少要会一种服务端的语言,能够完整、快速地开发一个app应用,包括客户端和服务端;
  • 可以了解其他操作系统的一些开发细节,如IOS、Windows Phone等,了解这些操作系统的一些开发的原理、甚至可以借鉴它们的一些开发中的一些方便的东西和一些优化的原理;
  • 了解更多用户体验相关的设计;学会自己设计;

11.……

二、学习方法

  • 网上找文档、资料、视频学习;
  • 找一本内容丰富,带有一些小项目的书籍来阅读,如《Google Android SDK开发范例大全(第2版)》、《疯狂Android讲义》等;
  • 有选择地阅读Android官方的帮助文档;
  • 经常逛论坛:安卓巴士、eoe移动开发者社区、中国手机开发者联盟、开源中国的安卓开发专区;
  • 多交流、多实践,其中多实践是必须的,可以根据书中的一些例子自己进行实践;
  • 找一些开源的大项目来研究,,如wordpress for android,新浪(QQ)微博android客户端;如:http://www.oschina.net/project/tag/189/android?lang=0&os=189&sort=time&p=4
  • 反编译一些常用的应用来研究,如mobileQQ、微信等;
  • 查看android的SDK源代码,理解android系统控件的实现和优化方法;
  • ……

本人是一个先大局再细节的人,在学习上也是这样,我会首先大体上了解android的整个知识框架,花1-3天时间去学习,可以在网上找一些ppt、或者一些总结性的pdf文档来看,也可以找一些视频来看。当整体上有一个比较深的理解后就可以一步一步的深入细节去学习,这时候就可以看一些比较好的书籍。

都说我不只是看书,其实不仅在哪方面,学习的方法是多样的。当然,看书是一种很好的入门方法,但是一般书籍涵盖的都是一些基础知识,掌握了基础知识,只是初步的入门而已;要想学到更多知识,要想能够没难度地开发应用,那就需要其他的学习方法;本人总结了以上的一些学习方法,都是一些循序渐进的方法(仅供参考)。

三、开发环境

  • DK+ADK+IDE(eclipse)+ADT,建议使用Android Developers官网提供的ADT Bundle,这个是对ADK+IDE(eclipse)+ADT的软件的捆绑,并且是最新的ADK、IDE(eclipse)和ADT,并且IDE(eclipse)都是比较精简的,整个开发环境运行、自动构建、编译起来比各自去下载DK、IDE(eclipse)、ADT部署的开发环境各方面都快。
  • Android Studio,这个开发环境还是在测试阶段,期待正式版的发布,那时候就可以使用单一的、专用的开发环境了。
  • VirtualBox+android-x86,使用VirtualBox虚拟机安装android-x86系统,可以很快速地对android应用进行调试、测试。如果要让android-x86支持.so库,那就需要把C/C++代码通过NDK和JNI技术编译成x86平台的.so库就可以了。

四、开发难点

  1. 屏幕分辨率的兼容 图形资源对于不同分辨率的适配,在不同尺寸屏幕上使用不同的界面布局以优化操作体验。暂时的解决方案就是提供480px宽的和720px宽的两套图。
  2. 处理器、图形处理器规则的兼容 对不同性能的处理器都保证流畅度,3D程序对于不同厂商的图形处理器都能正确渲染等。现在还没有很好地考虑到这个问题,因为android设备太多,暂时只能测试到主流的一些机型。当然网上也有提供云测试这样的平台,大家可以尝试使用测试。
  3. 不同系统版本的兼容 有些在高版本中被引入的Android API在低版本中不被支持。如果要在低版本中使用,需要采用其他的实现方法进行兼容。
  4. 界面性能问题 界面的流畅度和操作性能直接影响用户的体验,所以就需要:理解View的绘制原理,在绘制方法中减少耗时操作;开启新的线程去做复杂处理,在UI线程更新界面;减少布局的嵌套、合理使用和去掉布局背景;Adapter中使用Holder;适当裁剪Bitmap。
  5. OOM问题 避免内存泄漏,主要是Context的泄漏;图片的二级缓存;合理使用软弱引用;裁剪或缩小图片;动态释放一些页面的内容;学会使用内存分析工具:MAT。
  6. 耗电和发热问题 后台线程或服务,要根据情况进入睡眠、阻塞或者退出状态。
  7. 保持程序的扩展性和架构的弹性问题 多交流、多看别人的优秀代码,学会总结。

五、Android实现功能时应该注意的事

  • 善于使用已有的代码:系统已提供的API、自己以前项目的库、别人项目的库;
  • 针对一个功能模块,先实现基本的功能和界面,对于界面、逻辑、功能细节的调整放到后面慢慢做;

六、注意事项

  • 请参考资料库的《Android项目开发注意事项》 ……
  • 一款应用只应该有一个签名,并且要保管好签名文件,策划和程序各一份保存;
  • 要区分好version Name和version Code,version Name 表示版本名称,是字符串,而version Code 表示版本号,是整型数字;version Code每次发布的时候都应该递增。

七、开源项目

  • 系统自带的开源项目:系统相机、系统图库、联系人等……
  • 一些比较好的完整开源项目:OS china、wordpress for android、Sipdroid、饭否、Zirco-browser、com.shejiaomao.maobo(社交猫)……
  • 一些比较好的开源控件、组件:android-wheel、android-swipelistview、OneXListview、actionbarsherlock、slidingmenu、android-viewbadger、wei.mark.standout……
  • 一些比较好的开源框架:ThinkAndroid、Afinal、cfuture-androidkit 、ormlite和Hessian……

八、一些建议

  • 要非常熟悉Android开发工具,这样子你才能在开发中灵活、方便、高效;
  • 对于一些比较私密的key-value键值对,善用md5对key进行加密,使用加密算法对value进行加密;
  • 好好研究SDK的一些重要控件的源码,学会对源码进行复制、裁剪和修改;
  • 要好好地理解touch事件的分派;
  • 学会使用draw9patch.bat工具;
  • 有空可以有目的性的反编译一些大型的项目,研究它的一些控件,或者框架的实现原理;
  • 要自觉去了解学习一门服务端语言,了解服务端的原理和机制;

PS:这个文档提供的只是一个学习的提纲,希望对项学习android的人有所帮助,如果想了解关于android开发的一些技术细节和技术心得,可以查看其他分享的博客。

]]>
Android 界面性能优化方案 2013-11-08T20:30:00+00:00 Janseon http://janseon.github.io/android-performance 优化的原因
  • 用户体验
  • 用户体验
  • 用户体验
  • 用户体验
  • ……

几个概念

优化原则

  • 不要堵塞UI线程
  • 开启新的线程去做复杂处理,在UI线程更新界面; img

  • getView()、onDraw()、scroll类的方法等运行要简洁快速 img

  • 更简单地布局:少嵌套、适当使用权重布局和相对布局 img

  • Adapter优化:重用View和避免findViewById() img

  • 避免显示的布局里重复使用同一个背景 img img

  • 不显示的View,就隐藏不显示
  • 在有些情况下Activity的背景可以设置为空 > getWindow().setBackgroundDrawable (null); android:windowBackground=”@null” img

  • 纯色的背景比图片背景更高效 img

  • 美术资源要求:
  1. 纯色的背景,只需提供背景的颜色值;
  2. 纹理背景尽量提供尽量小的一小块图片,而开发人员会根据这个
  3. 可以拉伸的背景,尽量提供尽量小的一小块图片,而开发人员会根据这个小图片拉伸背景。
  4. 需要进行矢量拉伸的图片要做.9.png格式处理。

还可以参考以下的文章:

]]>
Android OOM的解决方案 2013-11-04T20:30:00+00:00 Janseon http://janseon.github.io/android-oom 造成OOM的原因

img —–P103

图片过大 图片过多 页面过多 内存泄漏

解决的终极原则:让更少的Bitmap驻留在内存

  • 二级缓存: img img

  • 图片做软、弱引用 img

相关文章-强、软、弱、和虚: 1 2

  • 加载缩小的图片 img

  • 动态释放内存 img

]]>
Activity的WindowManager 2013-10-24T20:30:00+00:00 Janseon http://janseon.github.io/android-window-manager 使用Activity的WindowManager可以生成真正浮动的View,这种浮动就像是Toast的那种弹出浮动。我们可以根据需要自己进行自定义,一个用法例子如下,这个例子是自定义Toast的一段代码:

img

同样需要设置布局参数、拥有addView()、removeView()方法

]]>
Activity的ContentView添加删除 2013-10-23T20:30:00+00:00 Janseon http://janseon.github.io/android-contentview 在Activity中有几个setContentView()方法,可以设置Activity的页面内容,另外还有一个addContentView()方法,也可以设置Activity的页面内容。

但两者不一样的地方是的。

setContentView()方法方法会先清空以前设置的页面的所有内容:

img

PhoneWindow中的setContentView()方法如下:

img

而addContentView()方法不会清空以前设置的页面的所有内容,并且在原有的页面内容上添加ContentView:

img

PhoneWindow中的addContentView()方法如下:

img

于是乎,我们就可以使用addContentView()方法大作文章:

1、添加标题栏

img

其中contentView就是想要设置的页面内容,这个要需要先设置topMargin的大小与标题栏的高度一致;titleBar就是要设置的标题栏的内容;需要注意的是要先添加contentView,再添加titleBar,因为titleBar要覆盖在contentView上面。效果图如下,红色框那部分是titleBar,其余的是contentView:

img

2、添加底部控制栏

添加底部的控制栏与添加标题栏的原理是一样的,只是设置的gravity不一样:

img

3、添加浮动控件

你可以在任意位置添加一个控件或布局

img

4、移除添加的contentView

img

5、模拟弹出对话框

调用setFilledView()方法,然后再对contentView设置一些弹出动画

img

至于对话框的消失,可以调用4中的removeView()方法,然后设置一些动画就可以了。

]]>
点击编辑框外任意点,隐藏软键盘 2013-10-18T20:30:00+00:00 Janseon http://janseon.github.io/android-edit-hide 重写dispatchTouchEvent()方法:

img

其中editTouchEvent()方法的实现是:

img

通过down事件和move事件判断是否符合点击事件,其中move事件中如果移动的距离小于DY被认为是点击事件。在up方法中调用方法判断被点击的是否是一个EditText,如果不是,则以藏软键盘。

img

其中checkEditing()方法中调用了InputUtil.isEditing(getWindow().getDecorView(), event)方法,isEditing()方法会在DecorView中去寻找被点击的那个控件,并且判断这个控件是否为EditText:

img

]]>
Activity向右Touch退出 2013-09-26T20:30:00+00:00 Janseon http://janseon.github.io/android-touch-back 在有返回操作的当前页面(activity)中,触摸此页面左右移动,而此页面会跟随手势左右移动,当向右移动超过一半的时候,就会继续播放完向右移动的动画(直到整个页面消失),然后就关闭此页面(activity)。

img

要实现这样的效果,需要两个要点:

1、设置当前的Activity的Window Background为透明的:

可以用java代码在Activity中这样设置:

1 getWindow().setBackgroundDrawableResource(R.color.transparent);

也可以在xml文件中,创建一个style并且设置windowBackground属性为透明色,然后在mainfest文件中设置Activity的them属性中引用。

img

img

2、重写Activity的dispatchTouchEvent()方法:

dispatchTouchEvent()方法中调用了onTouchBack(ev)方法:

img

onTouchBack(ev)方法的实现是下面这样的,主要是调用了TouchBack类的onTouchBack()方法:

img

TouchBack类的onTouchBack()方法的实现是这样的:

img

在onDown()中,主要是做一些能否touch back的判断,以及做一些初始化的记录:

img

而checkMoveDirection(event)方法主要是判断是否达到touch back的条件,这个条件是:首先水平移动了一段距离;

也就是说如果首先向上或者向下移动了一段距离,那么就不会再满足touch back的条件。

这个很好说明,例如,如果在一个页面中有ListView,如果首先向上或者向下移动了一段距离,那么之后就一直是ListView的滚动事件,而不会执行touch back操作;相反,如果首先水平移动了一段距离,那么就会一直执行touch back操作而不会执行ListView的滚动事件了,直到手指离开屏幕。

其中判断Touch方向的一些方法已经封装在了Touch类里面。

img

真正移动这个页面的方法就是moveToX(float dx)方法,moveToX()方法通过设置view的padding从而达到view移动的效果。

img

最后,当手指离开这个页面的时候,就会调用smoothSwich()方法。在smoothSwich()方法中就是调用了前一篇文章中说到的自定义padding动画的那个Smooth类的一些方法进行移动动画的播放。

img

其中需要注意的是这些方法都属于TouchBack这个类的,而view属性是传入的Activity的DecorView。

img

实现Smooth中的ScroolToEndListener监听器,就可以在动画播放完之后做一些处理。

img

当页面向右退出后,就关闭Activity,当页面向左返回时,就设置View的背景为空。

img

另外,也需要实现页面的背景随着左右移动而改变透明度,其中在moveToX()方法中调用了Smooth.setPaddingLeft(view, (int) (paddingLeft + dx))方法,这个方法中就有随着padding的改变而改变透明度的一些设置:

img

]]>