Jul 16, 2012
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下面的资源。 http://jcccn-wordpress.stor.sinaapp.com/uploads/2012/06/skin-test-1.png

要实现语言环境的切换,同时不改变系统的语言环境,我们就要改变当前应用的语言,使用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类的管理方式,详细实现方式后面再研究研究。

Jul 14, 2012
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

Jul 14, 2012
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代而永远得不到回收的缺点(大多数垃圾回收算法是这样)。
  • 枚举:很好,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是失去了类的一些特性,没有延迟加载,用的人也太少了~~

以后多推广推广单元素枚举这种更好的单例实现方式。在项目中的代码开始修改实施。

Jul 14, 2012
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等事件的时候,需要判断是否新手引导存在来做处理,增加了复杂度。

虽然最终的方案从功能上来说是最满足需求一个,但是还不完美。

Jul 14, 2012
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″?>

`

插入绘图:示例:

&lt;?xml version=”1.0″ encoding=”utf-8″?&gt;

裁剪绘图:通过设置level来决定裁剪的大小。默认的是0(可见区域为0),全部裁剪的level是10000。定义的示例:

&lt;?xml version=”1.0″ encoding=”utf-8″?&gt;

缩放绘图:定义如下:

&lt;?xml version=”1.0″ encoding=”utf-8″?&gt;

几何形状绘图:根节点为,包含的属性为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″?>

`

Navigate
« To the past Page 1 of 2
About
jcccn is involving Subscribe via RSS.