ANDROID SUPPORT兼容包详解

Support Library

我们都知道Android一些SDK比较分裂,为此google官方提供了Android Support Library package 系列的包来保证高版本sdk开发的向下兼容性, 所以你可能经常看到v4,v7,v13这些数字,首先我们就来理清楚这些数字的含义,以及它们之间的区别。

support-v4
用在API lever 4(即Android 1.6)或者更高版本之上。它包含了相对更多的内容,而且用的更为广泛,例如:Fragment,NotificationCompat,LoadBroadcastManager,ViewPager,PageTabStrip,Loader,FileProvider 等

Gradle引用方法:

compile ‘com.android.support:support-v4:21.0.3’

support-v7
这个包是为了考虑API level 7(即Android 2.1)及以上版本而设计的,但是v7是要依赖v4这个包的,v7支持了Action Bar以及一些Theme的兼容。

Gradle引用方法:

compile ‘com.android.support:appcompat-v7:21.0.3’
support-v13
这个包的设计是为了API level 13(即Android 3.2)及更高版本的,一般我们都不常用,平板开发中能用到,这里就不过多介绍了。

Theme

回到知乎上的这个问题,我们来介绍下各种Theme的概念。

Hoho Theme
在4.0之前Android可以说是没有设计可言的,在4.0之后推出了Android Design,从此Android在设计上有了很大的改善,而在程序实现上相应的就是Holo风格,所以你看到有类似 Theme.Holo.Light、 Theme.Holo.Light.DarkActionBar 就是4.0的设计风格,但是为了让4.0之前的版本也能有这种风格怎么办呢?这个时候就不得不引用v7包了,所以对应的就有 Theme.AppCompat.Light、 Theme.AppCompat.Light.DarkActionBar,如果你的程序最小支持的版本是4.0,那么可以不用考虑v7的兼容。

Material Design Theme
今年的5.0版本,Android推出了Material Design的概念,这是在设计上Android的又一大突破。对应的程序实现上就有 Theme.Material.Light、 Theme.Material.Light.DarkActionBar等,但是这种风格只能应用在在5.0版本的手机,如果在5.0之前应用Material Design该怎么办呢?同样的引用appcompat-v7包,这个时候的Theme.AppCompat.Light、 Theme.AppCompat.Light.DarkActionBar就是相对应兼容的Material Design的Theme。

注意事项

gradle引用appcompat-v7包的时候就不需要引用v4了,因为v7里默认包含了v4包;
compile ‘com.android.support:appcompat-v7:21.0.3’ 中的21代表API level 21推出的兼容包,所以如果你引用的是21之前的版本,则默认这些Theme.AppCompat.Light是Holo风格的,从21开始的版本默认是Material风格
使用appcompat之后,你的所有的Activity应该继承自ActionBarActivity,而ActionBarActivity继承自FragmentActivity,所以放心的使用Fragment;

Android之Lru算法

Lru:

LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,LRU缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉,比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最大缓存10000条,那怎么确定删除哪条过期数据呢,采用LRU算法实现的话就是将最老的数据删掉。

1.)初始化MemoryCache

这里内存缓存的是Drawable 而不是Bitmap 理由是Drawable相对Bitmap来说有很大的内存优势
int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取系统分配给应用的总内存大小
int mCacheSize = maxMemory / 8;//设置图片内存缓存占用八分之一
mMemoryCache = new LruCache(mCacheSize) {
//必须重写此方法,来测量Bitmap的大小
@Override
protected int sizeOf(String key, Drawable value) {
  if (value instanceof BitmapDrawable) {
    Bitmap bitmap = ((BitmapDrawable) value).getBitmap();
    return bitmap == null ? 0 : bitmap.getByteCount();
   }
   return super.sizeOf(key, value);
}
};

2.)添加一个Drawable到内存缓存
/**

 * 添加Drawable到内存缓存
 *
 * @param key
 * @param drawable
 */
private void addDrawableToMemoryCache(String key, Drawable drawable) {
    if (getDrawableFromMemCache(key) == null && drawable != null) {
        mMemoryCache.put(key, drawable);
    }
}

3.)从内存缓存中获取一个Drawable

/**
 * 从内存缓存中获取一个Drawable
 *
 * @param key
 * @return
 */
public Drawable getDrawableFromMemCache(String key) {
    return mMemoryCache.get(key);
}

4.)从内存缓存中移除一个Drawable

/**

 * 从内存缓存中移除
 *
 * @param key
 */
public void removeCacheFromMemory(String key) {
    mMemoryCache.remove(key);
}

5.)清空内存缓存

/**
 * 清理内存缓存
 */
public void cleanMemoryCCache() {
    mMemoryCache.evictAll();
}

其实Lru缓存机制本质上就是存储在一个LinkedHashMap存储,为了保障插入的数据顺序,方便清理。

基于DiskLruCache实现磁盘缓存:

DiskLruCache类并不是谷歌官方实现,需要自行下载,下载地址:https://github.com/JakeWharton/DiskLruCache

1.)初始化DiskLruCache

File cacheDir = context.getCacheDir();//指定的是数据的缓存地址
 long diskCacheSize = 1024 * 1024 * 30;//最多可以缓存多少字节的数据
 int appVersion = DiskLruUtils.getAppVersion(context);//指定当前应用程序的版本号
 int valueCount = 1;//指定同一个key可以对应多少个缓存文件
 try {
     mDiskCache = DiskLruCache.open(cacheDir, appVersion, valueCount, diskCacheSize);
 } catch (Exception ex) {
 }

2.)写入一个文件到磁盘缓存

/**
 * 添加Bitmap到磁盘缓存
 *
 * @param key
 * @param value
 */
private void addBitmapToDiskCache(String key, byte[] value) {
    OutputStream out = null;
    try {
        DiskLruCache.Editor editor = mDiskCache.edit(key);
        if (editor != null) {
            out = editor.newOutputStream(0);
            if (value != null && value.length > 0) {
                out.write(value);
                out.flush();
                editor.commit();
            } else {
                editor.abort();
            }
        }
        mDiskCache.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        DiskLruUtils.closeQuietly(out);
    }
}

3.)从磁盘缓存中读取Drawable

/**
 * 从磁盘缓存中获取一个Drawable
 *
 * @param key
 * @return
 */
public Drawable getDrawableFromDiskCache(String key) {
    try {
        DiskLruCache.Snapshot snapShot = mDiskCache.get(key);
        if (snapShot != null) {
            InputStream is = snapShot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);
            //从磁盘中读取到之后 加入内存缓存
            addDrawableToMemoryCache(key, drawable);
            return drawable;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

4.)从磁盘缓存中移除

/**
 * 从磁盘缓存中移除
 *
 * @param key
 */
public void removeCacheFromDisk(String key) {
    try {
        mDiskCache.remove(key);
    } catch (Exception e) {
    }
}

5.)清空磁盘缓存

/**
 * 清理磁盘缓存
 */
public void cleanDiskCache() {
    try {
        mDiskCache.delete();
    } catch (Exception e) {
    }
}

图片下载过程:

接下来实例中用到了一点RxJava的知识有不了解RxJava的请自行了解一下。

1.)采用异步方式操作磁盘缓存和网络下载, 内存缓存可以在主线程中操作

public void disPlay(final ImageView imageView, String imageUrl) {
//生成唯一key
final String key = DiskLruUtils.hashKeyForDisk(imageUrl);
//先从内存中读取
Drawable drawableFromMemCache = getDrawableFromMemCache(key);
if (drawableFromMemCache != null) {
imageView.setImageDrawable(drawableFromMemCache);
return;
}
Observable.just(imageUrl)
.map(new Func1() {
@Override
public Drawable call(String imageUrl) { // 参数类型 String
//从磁盘中读取
Drawable drawableFromDiskCache = getDrawableFromDiskCache(key);
if (drawableFromDiskCache != null) {
return drawableFromDiskCache;
}
//网络下载
return download(imageUrl); // 返回类型 Drawable
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1() {
@Override
public void call(Drawable drawable) { // 参数类型 Drawable
imageView.setImageDrawable(drawable);
}
});
}

2.)下载图片过程以及处理

private Drawable download(String imageUrl) {
HttpURLConnection urlConnection = null;
ByteArrayOutputStream bos = null;
InputStream ins = null;
try {
final URL url = new URL(imageUrl);
urlConnection = (HttpURLConnection) url.openConnection();
ins = urlConnection.getInputStream();
bos = new ByteArrayOutputStream();
int b;
while ((b = ins.read()) != -1) {
bos.write(b);
}
bos.flush();
byte[] bytes = bos.toByteArray();
Bitmap bitmap = DiskLruUtils.bytes2Bitmap(bytes);
String key = DiskLruUtils.hashKeyForDisk(imageUrl);
Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);
//加入内存缓存
addDrawableToMemoryCache(key, drawable);
//加入磁盘缓存
addBitmapToDiskCache(key, bytes);
return drawable;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
DiskLruUtils.closeQuietly(bos);
DiskLruUtils.closeQuietly(ins);
}
return null;
}

Android应用防止被截图或者录屏

Activity创建时设置WindowManager.LayoutParams.FLAG_SECURE属性,该属性能防止屏幕被截图和录制。以下为修复代码示例:

1
2
3
4
5
6
7
8
public class DemoActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
setContentView(R.layout.main);
}
}

即在Activity onCreate方法中,setContenView调用前设置getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);若是所有Activity需要设置该属性,那么可以在BaseActivity中设置,然后其他Activity继承即可

Android Studio --“Cannot resolve symbol” 解决办法

Android Studio 无法识别同一个 package 里的其他类,将其显示为红色,但是 compile 没有问题。鼠标放上去后显示 “Cannot resolve symbol XXX”,重启 Android Studio,重新 sync gradle,Clean build 都没有用。

多半是因为 Android Studio 之前发生了错误,某些 setting 出了问题。解决方法如下:
点击菜单中的 “File” -> “Sync Project with Gradle Files”语法就会正确的高亮了。
点击菜单中的 “File” -> “Invalidate Caches / Restart”,然后点击对话框中的 “Invalidate and Restart”,清空 cache 并且重启。语法就会正确的高亮了。

Android之Glide缓存

GlideModule使用
  GlideModule 是一个抽象方法,全局改变 Glide 行为的一个方式,通过全局GlideModule 配置Glide,用GlideBuilder设置选项,用Glide注册ModelLoader等。

  1. 自定义一个GlideModule
    public class MyGlideModule implements GlideModule {
    @Override public void applyOptions (Context context, GlideBuilder builder){

      }
    }
    @Override public void registerComponents(Context context, Glide glide){

      }
    }

  2. < mainfest …>
      < application …>
         < meta-date
         android:name=”com.mypackage.MyGlideModule”
         android:value=”GlideModule” />
       < / application>
     < / manifest>

  3. 添加混淆处理
      -keepnames class com.mypackage.MyGlideModule
      # or more generally:
      #-keep public class * implements com.bumptech.glide.module.GlideModule

  4. 多个GlideModule冲突问题
      GlideModule不能指定调用顺序,所以应该避免不同的GlideModule之间有冲突的选项设置,可以考虑将所有的设置都放到一个GlideModule里面,或者排除掉某个manifest文件的某个Module,代码如下:
    < meta-data android:name=”com.mypackage.MyGlideModule” tools:node=”remove” / >

GlideBuilder设置选项:

  1. 设置Glide内存缓存大小
      int maxMemory = (int)Runtime.getRuntime().maxMemory();//获取系统分配给应用的总内存大小
    int memoryCacheSize = maxMemory / 8 ;//设置图片内存缓存占用八分之一
    //设置缓存大小
    builder.setMemoryCache(new LruResourceCache(memoryCacheSize));

 获取默认的内存使用计算函数
 MemorySizeCalculator calculator = new MemorySizeCalcylator(context);
 int defauleMemoryCacheSize = calculator.getMemoryCacheSize();
 int defaultBitmapPoolSize = calculator.getBitmapPoolSize();  

  1. 设置Glide磁盘缓存大小
      File cacheDir = context.getExternalCacheDir();//指定的是数据缓存地址
      int disCacheSize = 1024 1024 30 ;//最多可以缓存多少字节的数据
      //设置磁盘缓存大小
      builder.setDiskCache(new DiskLruCacheFactory(cacheDir.getPath(),”glide”,diskCacheSize));
    也可以通过如下两种方式
    //存放在data/data/xxxx/cache/
    builder.setDiskCache(new InternalCacheDiskCacheFactory(context, “glide”, diskCacheSize));
    //存放在外置文件浏览器
    builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, “glide”, diskCacheSize));

  2. 设置图片解码格式
    //设置图片解码格式
    builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);

  3. 设置缓存内存大小
    //设置BitmapPool缓存内存大小
    builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));

  1. 设置一个用来检索cache中没有的Resource的ExecutorService
    为了使缩略图请求正确工作,实现类必须把请求根据Priority优先级排好序。
    builder.setDiskCacheService(ExecutorService service);
    builder.setResizeService(ExecutorService service);

使用ModelLoader自定义数据源
例如我们使用了七牛云存储,要根据不同的要求请求不同尺寸不同质量的图片,这时我们就可以使用自定义数据源

  1. 定义处理URL接口

public interface IDataModel {

  String buildDataModelUrl (int width , int height );

}

  1. 实现处理URL接口

    public class JpgDataModel implements IDataModel {

      private String dataModelUrl ;

      public JpgDataModel (String dataModelUrl){
      
         this.dataModelUrl = dataModelUrl;

  }

  @Override
  public String buildDataModelUrl(int width , int height ){

     return String.format(
     “%s?imageView2/1/w/%d/h/%d/format/jpg”,
     dataModelUrl, width, height);
     }   
  }

public class WebpDataModel implements IDataModel {

  private String dataModelUrl ;

  public WebpDataModel (String dataModelUrl){
  
     this.dataModelUrl = dataModelUrl;

  }

  @Override
  public String buildDataModelUrl(int width , int height ){

     return String.format(
     “%s?imageView2/1/w/%d/h/%d/format/webp”,
     dataModelUrl, width, height);
     }   
  }

  1. 实现ModelLoader

    public class MyDataLoader extends BaseGlideUrlLoader {
    public MyDataLoader(Context context) {

     super(context);

    }

    public MyDataLoader(ModelLoader urlLoader) {

     super(urlLoader, null);

    }

    @Override
    protected String getUrl(IDataModel model, int width, int height) {
     
      return model.buildDataModelUrl(width, height);

    }

    /* /
    public static class Factory implements ModelLoaderFactory {
      @Override
      public ModelLoader build(Context context, GenericLoaderFactory factories) {
      
      return new MyDataLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class));
     
       }
      @Override
      public void teardown() {
      }
     }
    }

  2. 根据不同的要求采用不同的策略加载图片

      //加载jpg图片
      Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);
      //加载webp图片
      Glide.with(this).using(new MyDataLoader(this)).load(new WebpDataModel(imageUrl)).into(imageView);

  3. 如何跳过.using()

 public class MyGlideModule implements GlideModule {
  
   @Override
   public void registerComponents(Context context, Glide glide) {
    glide.register(IDataModel.class, InputStream.class, new MyUrlLoader.Factory());
 
  }
 }

  上面的实现跳过using()

  //加载jpg图片
  Glide.with(this).load(new JpgDataModel(imageUrl)).into(imageView);
  //加载webp图片
  Glide.with(this).load(new WebpDataModel(imageUrl)).into(imageView);

  使用signature()实现自定义cacheKey:
  Glide 以 url、viewwidth、viewheight、屏幕的分辨率等做为联合key,官方api中没有提供删除图片缓存的函数,官方提供了signature()方法,将一个附加的数据加入到缓存key当中,多媒体存储数据,可用MediaStoreSignature类作为标识符,会将文件的修改时间、mimeType等信息作为cacheKey的一部分,通过改变key来实现图片失效相当于软删除。

 1.使用StringSignature

 Glide.with(this).load(yourFileDataModel).signature(new StringSignature(“1.0.0”)).into(imageView);

 2.使用MediaStoreSignature

 Glide.with(this) .load(mediaStoreUri).signature(new MediaStoreSignature(mimeType, dateModified, orientation)).into(view);

 3.使用自定义Signature

 public class IntegerVersionSignature implements Key {
   private int currentVersion;
   public IntegerVersionSignature(int currentVersion) {
     this.currentVersion = currentVersion;
  }
 @Override
 public boolean equals(Object o) {
   if (o instanceof IntegerVersionSignature) {
    IntegerVersionSignature other = (IntegerVersionSignature) o;
    return currentVersion = other.currentVersion;
   }
   return false;
 }
@Override
public int hashCode() {
 return currentVersion;
 }
 @Override
 public void updateDiskCacheKey(MessageDigest md) {
 
  messageDigest.update(ByteBuffer.allocate(Integer.SIZE)
.putInt(signature).array());

 }
}