0 notes
Android程序如何实现换肤?
安卓上换肤可以通过那些方式来实现呢?
源码下载地址:http://www.kuaipan.com.cn/file/id_80676665698552.htm
方式一:切换程序的语言版本。
原理:系统根据一定的规则去资源文件夹res下寻找资源。通过改变程序的配置(比如语言或者地区),就能加载不同资源文件夹下面的资源,从而实现简单的换肤。
实践:举例说明,我要通过中文(系统默认)和英文两个语言版本来实现换肤。首先增加两个资源文件夹drawable-en-mdpi和layout-en,如下图。当在默认的中文环境下,使用的是drawable-mdpi和layout下面的资源。切换至英文环境之后,程序则会使用drawable-en-mdpi和layout-en下面的资源,在这两个文件夹下找不到的时候才去使用默认的drawable-mdpi和layout下面的资源。

要实现语言环境的切换,同时不改变系统的语言环境,我们就要改变当前应用的语言,使用Resources实例的updateConfiguration方法即可。示例代码如下:
changeLanguage(Locale.ENGLISH);
private void changeLanguage(Locale newLocale) {
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
config.locale = newLocale;
resources.updateConfiguration(config, dm);
this.onCreate(null); // 用于立即刷新界面
}
但是这种方式有一些局限性:只能更换本apk内置的资源且受限于语言种数;需要重新create activity等方式才能刷新界面;可能影响程序其他依赖于语言的元素。这种方式属于一种投机取巧的方法,不建议使用。
总结:两步实现,第一步,增加不同语言版本的资源文件夹;第二步,程序内部切换语言。
方式二:安装主题apk。
原理:通过获取其他程序的context来获取皮肤资源。我们知道android程序中要获取drawable、layout等资源,都要通过context.getResources().getXXX的方式。关键就在这儿了,如果我们可以拿到其他程序的context,那么那个程序就可以作为皮肤程序来提供资源给主程序使用了。android中两个程序相互读取数据的条件是:两个程序的共享用户id相同,通过AndroidManifest.xml中的android:sharedUserId属性配置;两个程序签名相同。想要改变皮肤时,改变提供资源的context为皮肤程序的context,然后刷新即可。
实践:有一点要注意,要保证能正确获取到皮肤包中的资源,需要编译出来的皮肤包与主程序中的R.java文件一致,即资源对应要一致(主程序中有layout、color、drawable、value等多少类资源,皮肤包中也需要有相同数量的资源)。
Context skinContext = createPackageContext(skinPackageName, Context.CONTEXT_IGNORE_SECURITY); contentView.setBackgroundDrawable(skinContext.getResources().getDrawable(R.drawable.gloal_background));
方式三:使用皮肤资源zip包。
原理:直接从文件(SD卡或者data目录)中读取资源文件并解码,然后设置给相关的控件。
实践:实现方式可以是直接一个包含皮肤资源(图片、控制布局的一些数值文本文件等)的压缩包,通常后缀名被命名为自己独有的名字,比如搜狗的sga,百度的bds等,使用时被解压拷贝到手机存储上的皮肤文件夹里面;也可以是包含在一个单独的apk安装包里面,安装应用皮肤后皮肤压缩包(一般放在皮肤apk的asset目录下)被解压拷贝到data目录下以供使用。
这种方式需要注意的一个地方就是内存管理的问题。一般是new一些bitmap或者BitmapDrawable之类的对象出来,在不再使用的时候要注意释放。一个思路是参照系统Resources类的管理方式,详细实现方式后面再研究研究。
0 notes
Android中的SharedPreferences陷阱
将保存SharedPreferences的xml文件删除了,能够彻底删除对应的SharedPreferences吗?
一次开发过程中,一个功能是需要将程序缓存清除掉,包括SharedPreferences文件。
第一次做的方式是,把相关的文件删除。但是发现有问题:程序退出后,再次进入程序仍然能够读取到应该被删除掉的SharedPreferences的值,但是DDMS查看要删除的pref文件确实都不在了。
为什么文件都不在了还能够取到值呢?
换了另一种清除SharedPreferences的方式:使用SharedPreferences.Editor的commit方法。结果证明,这样做是能够起作用的,调用后不退出程序都马上生效。
既然这样,第一个反应就是取SharedPreferences是首先内存缓存中取的。那为什么重启程序都还能去得到呢?
就进入源代码中看了一下,有几下几个地方可以了解了解:
取SharedPreferences实际上是在ContextImpl这个类中完成的。
1、context.getSharedPreferences(pref_name, mode)的流程:
- A 在sSharedPrefs这个map(同步的)中以pref_name为键取SharedPreferencesImp对象sp。如果sp不为空并且对应的pref文件未被异常修改,就返回这个对象。否则进入B。
- B 如果sp为空,重新生成一个SharedPreferenceImp对象并且加入到sSharedPrefs这个map中。
- C 同步的:从pref文件中解析出map对象并用之替换SharedPreferenceImp对象中原有的存放pref键值对的mMap成员对象。如果pref文件解析异常导致map为null,就保持原有对象而不替换。 如果备份的pref文件(…pref_name.xml.bak)存在,就使用备份文件。
- D 返回SharedPreferenceImp对象sp。 注意:sSharedPrefs在程序中是静态的:private static final HashMap sSharedPrefs = new HashMap(); 如果退出了程序但Context没有被清掉,那么下次进入程序仍然可能取到本应被删除掉的值。
2、从SharedPreference中取值getString(String key, String defValue): 从SharedPreferencesImp对象的mMap成员对象中根据key取出相应的对象v。如果取得的对象v为空,返回默认对象defValue;否则,返回对象v。
3、commit过程:
- A 在内存中提交,即用要提交的map去刷新已有的mMap对象。如果map对象中某个键的值指向editer对象自身,就代表要移除这个键值对。
- B 将步骤A返回的MemoryCommitResult对象加入到写入本地的队列中,写入本地文件。这一步目前是在同一个线程中做的,因为性能表现得很良好。在写入文件前,如果同名文件已经存在,则会原文件重命名为备份文件名,如果写入成功,才删除bak备份文件。
- C 通知SharedPreferences的监听状态改变了。返回提交是否内存成功的状态。
4、EditorImpl内部类: 内部有一个Map成员对象mModified,用来保存将要提交的pref键值。 apply方法与commit方法的区别:前者先提交到内存中,再异步写到文件,并且不需要返回写入成功与否的状态;后者同步写入内存和文件。
5、MemoryCommitResult内部类: 用来存放Editor提交到内存的返回状态,包括是否有键值改变、将要写入文件中的map对象,写入文件成功与否等。
总结一下:要想及时并安全清除SharedPreferences一定要使用Editor去clear并commit,不要直接暴力地删除其xml文件。
测试用的源代码(GBK编码):http://www.kuaipan.cn/file/id_80676665698551.htm
0 notes
Java中单例模式的最佳实现——单元素的枚举类型
Inspired by Effective Java.
Singleton模式是在编程实践中应用最广泛的几种设计模式之一。以前知道的,实现单例的方法有两种(下面的A、B)。刚刚在读《Effective Java的时候》学到一种新的更好的方法(E):单元素的枚举类型。同时通过网上资料也知道了其他两种方法(C、D)。最后一种在Java中从1.5版本开始支持,其他语言在验证后说明。
A.饿汉式(类加载的时候就创建实例)。
代码如下:
public class MaYun {
public static final Mayun instance = new Mayun(); //静态的final的MaYun
private MaYun() {
//MaYun诞生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
Call:MaYun.instance.splitAlipay();
Feature:可以通过反射机制攻击;线程安全[多个类加载器除外]。
A+.饿汉变种[推荐]
public class MaYun {
private static Mayun instance = new Mayun();
private static getInstance() {
return instance;
}
private MaYun() {
//MaYun诞生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
A++.饿汉变种(类初始化的时候实例化instance):
public class MaYun {
private MaYun instance = null;
static {
instance = new MaYun();
}
private MaYun() {
//MaYun诞生要做的事情
}
public static MaYun getInstance() {
return this.instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
B.懒汉式。
代码如下:
public class MaYun {
private static MaYun instance = null;
private MaYun() {
//MaYun诞生要做的事情
}
public static MaYun getInstance() {
if (instance == null) {
instance = new MaYun();
}
return instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
Call:MaYun.getInstance().splitAlipay();
Feature:延时加载;线程不安全,多线程下不能正常工作;需要额外的工作(Serializable、transient、readResolve())来实现序列化。
B+.懒汉式变种。
public class MaYun {
private static MaYun instance = null;
private MaYun() {
//MaYun诞生要做的事情
}
public static synchronized MaYun getInstance() {
if (instance == null) {
instance = new MaYun();
}
return instance;
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
Feature:线程安全;效率比较低,因为需要线程同步的时候比较少。
C.静态内部类[推荐]。
代码如下:
public class MaYun {
private static class SigletonHolder {
private static final instance = new MaYun();
}
public static final getInstance() {
return SigletonHolder.instance;
}
private MaYun() {
//MaYun诞生要做的事情
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
Call:MaYun.getInstance().splitAlipay();
Feature:线程安全;延迟加载。
D.双重校验锁[不推荐]。
代码如下:
public class MaYun {
private volatile static MaYun instance;
private MaYun (){}
public static MaYun getInstance() {
if (instance == null) {
synchronized (MaYun.class) {
if (instance == null) {
instance = new MaYun();
}
}
}
return instance;
}
}
Feature:jdk1.5之后才能正常达到单例效果。
E.编写一个包含单个元素的枚举类型[极推荐]。
代码如下:
public enum MaYun {
himself; //定义一个枚举的元素,就代表MaYun的一个实例
private String anotherField;
MaYun() {
//MaYun诞生要做的事情
//这个方法也可以去掉。将构造时候需要做的事情放在instance赋值的时候:
/** himself = MaYun() {
* //MaYun诞生要做的事情
* }
**/
}
public void splitAlipay() {
System.out.println(“Alipay是我的啦!看你丫Yahoo绿眉绿眼的望着。。。”);
}
}
Call:MaYun.himself.splitAlipay();
Feature:从Java1.5开始支持;无偿提供序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。
总之,五类:懒汉,恶汉,双重校验锁,静态内部类,枚举。
- 恶汉:因为加载类的时候就创建实例,所以线程安全(多个ClassLoader存在时例外)。缺点是不能延时加载。
- 懒汉:需要加锁才能实现多线程同步,但是效率会降低。优点是延时加载。
- 双重校验锁:麻烦,在当前Java内存模型中不一定都管用,某些平台和编译器甚至是错误的,因为instance = new MaYun()这种代码在不同编译器上的行为和实现方式不可预知。
- 静态内部类:延迟加载,减少内存开销。因为用到的时候才加载,避免了静态field在单例类加载时即进入到堆内存的permanent代而永远得不到回收的缺点(大多数垃圾回收算法是这样)。
- 枚举:很好,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是失去了类的一些特性,没有延迟加载,用的人也太少了~~
以后多推广推广单元素枚举这种更好的单例实现方式。在项目中的代码开始修改实施。
0 notes
一个Android程序中新手引导功能实现方式的变迁
最近做的一个项目中,引入了首次进入程序时的显示新手引导的功能,全屏显示,可以手指拖动左右翻页。效果类似于淘宝Android客户端3.0版本中新手引导的效果。
下面说说这个需要全屏的新手引导都经历了一些什么变化吧:
1、第一阶段:加在首页的PopupWindow上面
private void addFeatureGuide() {
LayoutInflater layoutInflater = LayoutInflater.from(this);
LinearLayout popContentView = (LinearLayout) layoutInflater.inflate(R.layout.popup, null);
PopupWindow popupWindow = new PopupWindow(popContentView, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
popupWindow.showAtLocation(this.findViewById(R.id.main_root), Gravity.CENTER, 0, 0);
}
在onResume的时候调用addFeatureGuide()方法。
这样应该OK了吧?不!Run的时候报错了:
FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to resume activity {com.example/com.example.MyActivity}: android.view.WindowManager$BadTokenException: Unable to add window — token null is not valid; is your activity running?
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3128)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3143)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2684)
at android.app.ActivityThread.access$2300(ActivityThread.java:125)
从错误日志中得出,在activity显示出来之前,拿不到activity将要显示在它上面的window的token。没有了window token,PopupWindow这个寄生的对象也就加不上去了。
那好吧,既然这样,我就在activity显示出来之后再加popupWindow上去吧。先这样试试:
mHandler.sendEmptyMessageDelayed(MSG_WHAT_SHOWPOP, 1000);
这样Activity将在1秒之后(先假设activity在resume一秒钟之后显示出来了)接收到消息,将popupWindow加上去。 结果,暂时是能够把PopupWindow显示出来。
问题:PopupWindow加上去的时延无法判断。显示太晚,会把首页内容看到之后一段时间才看得到新手引导;太早,同样会出现拿不到window token的问题,导致程序崩溃。所以这种方法不可取。
2、第二阶段:作为一个单独的Activity,在Welcome之后首页之前显示。
新手引导作为一个独立的Activity的contentView。Welcome页面完了之后,跳到新手引导的Activity;在用户左右滑动翻到新手引导的最后一页的时候,跳到首页MainActivity。
问题:进入首页需要的时间太久。首页MainActivity是个庞然大物,进入的时候要显示N多界面元素,还要启动几个线程。用户在新手引导滑动完了之后,需要停滞几秒钟才能看到首页。不能忍受!
3、第三阶段:加在导航栏里面
导航栏是一个利用WindowManager实现的东西(感觉很心虚)。导航栏是一个独立于Activity之外的东西,在欢迎页面的时候就会去实例化。因此,如果我把新手引导加在导航栏上面,那么,就能在首页显示出来之前把新手导航显示出来,并且在新手导航消失之后马上看到首页(仅仅把新手引导的view remove掉就是)。
问题:导航栏的功能不包括新手引导,不应该把多余的东西加入到导航栏上面。
4、第四阶段:加在WindowManager上面
想了一下,为什么前面要把新手引导加到导航栏上面呢?其实是项利用导航栏的某些特性,比如独立于activity,可以在覆盖住整个屏幕。那么,我想要的其实不是导航栏,而是实现导航栏所使用的WindowManager。
既然如此,我就把新手引导放WindowManager里面,实现的地方跟导航栏独立开来,和导航栏是同一个级别的东西。
在首页MainActivity create的时候,我就把新手引导的视图生成,然后加到WindowManger上面。
问题:
A、在新手引导要消失的时候,需要把新手引导的view remove掉,然后让导航栏显示出来。导航栏出来得比较慢,首页下部会显示白色很久然后导航栏才显示。
B、由于新手引导要求全屏,首页又非全屏,在锁屏之后:按下手机解锁键,有状态栏显示;滑动解锁之后,需要重新设置window flag,即使马上调用新手导航view的requestLayout方法,也会出现界面显示出来后突然向上跳一下的效果,体验不好。
C、而且,使用WindowManager这种更偏底层的元素,可能会引发一些未知的风险。
5、第五阶段:作为首页Activity的一个view
最后只能这样了,前面的几种尝试,都存在很多缺陷。只能用最原始的方式了。
首页在create的时候,判断是否要显示新手引导。如果要显示,就把新手引导的view生成,然后加到activity里面。
这样做也有一些不好的地方。比如:
首页上界面元素多,新手引导在滑动翻页的时候布局改变的时候,首页控件树中的其他view也会计算,流畅度不够好;
物流、旺旺等后台提示都仍旧会其作用;
在处理onKeyDown,onTrackballEvent等事件的时候,需要判断是否新手引导存在来做处理,增加了复杂度。
虽然最终的方案从功能上来说是最满足需求一个,但是还不完美。
0 notes
对Android Drawable Resources的研究
最近一次在android开发时想在GridView被按下的时候改变item的图片。可以通过xml配置selector背景选择器来达到,但是有100个item我是不是就要写100个xml呢?肯定不能够这样!就想到通过java代码来实现selector的效果。于是,找到了StateListDrawable这个类,进而进出对Drawable Resources的深入研究。
> 官方链接:http://developer.android.com/guide/topics/resources/drawable-resource.html
首先从字面上理解,绘图资源泛指可以画在屏幕上的东西。
绘图资源可以分为以下几类:
1、位图文件(BitmapDrawable):位图图像文件,支持jpg、png、gif
2、九片式图片(点9)文件(NinePatchDrawable):可以拉伸的.9.png图片文件
3、图层列表(LayerDrawable):一组图片,,index越大的画在最顶层
4、状态列表(StateListDrawable):不同的状态(正常、按下、获得焦点等)对应不同的资源
5、等级列表(LevelListDrawable):一组绘图,每张绘图制定了不同的等级,只有符合设定的等级的绘图才显示
6、过渡绘图(TransitionDrawable):内部可以实现两张图片的渐隐渐现切换
7、插入绘图(InsetDrawable):使用另一个绘图资源,同时指定了其边距
8、裁剪绘图(ClipDrawable):可以从原资源上切割一部分显示
9、缩放绘图(ScaleDrawable):对另一个资源进行缩放
10、形状(ShapeDrawable):包含颜色和渐变的几何图形
11、动画(AnimationDrawable)
12、颜色(Color):颜色也可以当做绘图资源使用
位图:可以直接使用包含扩展名的文件名或者R引用作为资源ID。使用xml位图来定义一个指向原位图的引用,可以增加一些属性(比如抗锯齿antialias,抖动处理dither),使用方法和位图图片文件一样。比如:
<?xml version=”1.0″ encoding=”utf-8″?>
点9:可以对图片进行伸缩来适用View尺寸的改变。与位图类似,点九也可以创建对应的xml引用文件。比如
<?xml version=”1.0″ encoding=”utf-8″?>
图层列表:按照index的顺序来绘制。item必须是的子类,默认情况下回进行缩放来适应。xml中的定义方式如下:
`<?xml version=”1.0″ encoding=”utf-8″?>
`
状态列表:为每个资源item制定不同的状态。在匹配的时候,第一个与制定状态匹配的item被使用到,不一定是最优的,而是第一个。因此,一需要把默认选项放在最后一个位置。定义示例:
`<?xml version=”1.0″ encoding=”utf-8″?>
<!– pressed –> <!– focused –> <!– hovered –> <!– default –> `
级别列表:示例:
`<?xml version=”1.0″ encoding=”utf-8″?>
`
过渡绘图:只支持最多2个drawable。例如
`<?xml version=”1.0″ encoding=”utf-8″?>
`
插入绘图:示例:
<?xml version=”1.0″ encoding=”utf-8″?>
裁剪绘图:通过设置level来决定裁剪的大小。默认的是0(可见区域为0),全部裁剪的level是10000。定义的示例:
<?xml version=”1.0″ encoding=”utf-8″?>
缩放绘图:定义如下:
<?xml version=”1.0″ encoding=”utf-8″?>
几何形状绘图:根节点为,包含的属性为shape(rectangle矩形|oval椭圆|line线条|ring环形)。当shape属性为ring时还有innerRadius、innerRadiusRatio、thickness、thicknessRatio、useLevel。它支持以下图形的组合:
圆角,在矩形的时候生效。注意bottomLeftRadius是右下角,bottomRightRadius是左下角。
渐变。包含属性:angle,渐变的方向角度,必须是45的倍数,默认为0,即从左至右,逆时针计算角度;centerX,x的渐变中心位置,0~1.0;centerY,y方向的渐变中心位置;startColor、centerColor、endColor分别是渐变的开始、中间、结束颜色;type,渐变的类型(linear线性|radial中心放射性|sweep扇形);gradientRadius,渐变半径,中心放射类型的渐变时生效;useLevel,是否作为等级绘图使用。
内边距。left、top、right、bottom四个方向。
大小尺寸。height、width。
填充颜色。color。
描边。包含属性:width,描边的粗细;color,描边的颜色;dashGap,dashWidth,两个一起使用,使用虚线描边,分别指虚线每条线 的间距和长度。
定义如下:
`<?xml version=”1.0″ encoding=”utf-8″?>
`