HappyLifeLife.com
HappyLifeLife.com
HappyLifeLife.com 登录 HappyLifeLife.com 注册 HappyLifeLife.com
爱新闻 爱生活
爱分享 爱学习
爱读书 爱探索
爱音乐 爱宇宙
爱电影 爱地球
爱阅读 爱世界
爱运动 爱科技
爱学习

<< < - > >>
Android
Android开发
www.HappyLiveLife.com 收藏 www.happylivelife.com
HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com
编辑
https://blog.csdn.net/u011791526/article/details/76653264https://blog.csdn.net/qq_32666413/article/details/80534457Service设置成START_STICKY kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样通过 startForeground将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别​,除非在系统内存非常缺,否则此进程不会被 kill双进程Service: 让2个进程互相保护,其中一个Service被清理后,另外没被清理的进程可以立即重启进程QQ黑科技: 在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用 Android4.0系列的一个漏洞,已经确认可行用C编写守护进程(即子进程) : Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android->- Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以上的版本不可行)用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。联系厂商,加入白名单重用converView: 通过复用converview来减少不必要的view的创建,另外Infalte操作会把xml文件实例化成相应的View实例,属于IO操作,是耗时操作。减少findViewById()操作: 将xml文件中的元素封装成viewholder静态类,通过converview的setTag和getTag方法将view与相应的holder对象绑定在一起,避免不必要的findviewbyid操作避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动listview,会因为getview逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库glideItem的布局层次结构尽量简单,避免布局太深或者不必要的重绘尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。 由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现使用 RecycleView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecycleView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。task clearJar(type: Delete) { delete 'build/libs/GLibs.jar' delete 'libs/GLibs.jar'}task makeJar(type: Jar) { baseName 'GLibs' from('build/intermediates/classes/release/com/cabr/glibs') into('com/cabr/glibs/') exclude('BuildConfig.class', 'R.class') exclude { it.name.startsWith('R$'); }}makeJar.dependsOn(clearJar, build)def SDK_BASENAME = "GLibs";def SDK_VERSION = "_V1.0";def DestinationPath = "build";def zipFile = file('build/intermediates/bundles/default/classes.jar')task clearJar(type: Delete) { delete DestinationPath + SDK_BASENAME + SDK_VERSION + ".jar"}task makeJar(type: Jar) { from zipTree(zipFile) from fileTree(dir: 'src/main',includes: ['assets/']) exclude('BuildConfig.class', 'R.class') exclude { it.name.startsWith('R$'); } baseName = SDK_BASENAME + SDK_VERSION destinationDir = file(DestinationPath)}makeJar.dependsOn(clearJar, build)task makeJar(type: Copy) { delete 'build/libs/mysdk.jar' //设置拷贝的文件 from('build/intermediates/bundles/release/') //打进jar包后的文件目录 into('build/libs/') //将classes.jar放入build/libs/目录下 //include ,exclude参数来设置过滤 //(我们只关心classes.jar这个文件) include('classes.jar') //重命名 rename ('classes.jar', 'mysdk.jar')}makeJar.dependsOn(build)//在终端执行生成JAR包// gradlew makeJar关于 Gradle failed: already disposed module 的问题Android studio 中删除或者移除原有的 module 后,一般会跳出来这样一个弹窗,要解决这个问题很简单,就是进入 File -- Invalidate Cache/Restart ,然后会自动清理和重启,这样就不会报错了。SplashActivity的实现步骤由于在APP启动页面加载时,会有白屏闪过,用这种方法加载启动页面的图片会避免此问题,达到秒开的效果。1 在drawable文件夹下建立splash.xml. 2 在样式文件style.xml中为SplashActivity建立一个theme. 3 在AndroidManifest.xml中定义SplashActivity的theme为splash_theme. 4 为了保证启动速度,SplashActivity不需要实现setContentView()方法。如果你的SplashActivity设置了layout文件,那么其在app完全初始化完成后才会显示,带来一定的耗时。这里直接以theme作为SplashActivity展示的UI,减少了加载时间,能达到秒开的效果。使用该启动方式亦可以避免有些app点开时出现的白屏问题。 def listSubFile = { def resFolder = 'src/main/res/layout' def files = file(resFolder).listFiles() def folders = [] files.each { item -> folders.add(item.absolutePath) } folders.add(file(resFolder).parentFile.absolutePath) return folders } sourceSets { main { res.srcDirs = listSubFile() } }夜神浏览器连接将夜神安装目录下的adb.exe, nox_adb.exe分别重命名为adb.exe.bac, nox_adb.exe.bak将SDK下platforms中bin中的adb.exe路径加到环境变量Path中或者拷贝到夜神安装目录之后adb connect 127.0.0.1:52001或adb connect 127.0.0.1:62001即可连接成功现在由于GWF,google基本和咱们说咱见了,就给现在在做Android 或者想学习Android 的朋友带来了诸多的不便,最简单的就是Android SDK Manager 你无法更新了。现在这里有一个解决方案,如下。启动 Android SDK Manager ,打开主界面,依次选择「Tools」、「Options…」,弹出『Android SDK Manager - Settings』窗口;在『Android SDK Manager - Settings』窗口中,在「HTTP Proxy Server」和「HTTP Proxy Port」输入框内填入mirrors.neusoft.edu.cn和80,并且选中「Force https://… sources to be fetched using http://…」复选框。设置完成后单击「Close」按钮关闭『Android SDK Manager - Settings』,窗口返回到主界面;依次选择「Packages」、「Reload」。android studio 启动java.lang.RuntimeException: java.lang.IllegalArgumentException: Argument for @NotNull parameter 'name' of com/android/tools/idea/welcome/Platform. must not be null at com.intellij.idea.IdeaApplication.run(IdeaApplication.java:178) at com.intellij.idea.MainImpl$1$1$1.run(MainImpl.java:52) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:744) at java.awt.EventQueue.access$400(EventQueue.java:97) at java.awt.EventQueue$3.run(EventQueue.java:697) at java.awt.EventQueue$3.run(EventQueue.java:691) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75) at java.awt.EventQueue.dispatchEvent(EventQueue.java:714) at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:362) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)Caused by: java.lang.IllegalArgumentException: Argument for @NotNull parameter 'name' of com/android/tools/idea/welcome/Platform. must not be null at com.android.tools.idea.welcome.Platform.(Platform.java) at com.android.tools.idea.welcome.Platform.getLatestPlatform(Platform.java:72) at com.android.tools.idea.welcome.Platform.createSubtree(Platform.java:89) at com.android.tools.idea.welcome.InstallComponentsPath.createComponentTree(InstallComponentsPath.java:81) at com.android.tools.idea.welcome.InstallComponentsPath.init(InstallComponentsPath.java:215) at com.android.tools.idea.wizard.DynamicWizardPath.attachToWizard(DynamicWizardPath.java:97) at com.android.tools.idea.wizard.DynamicWizard.addPath(DynamicWizard.java:233) at com.android.tools.idea.welcome.FirstRunWizard.init(FirstRunWizard.java:75) at com.android.tools.idea.welcome.FirstRunWizardHost.setupWizard(FirstRunWizardHost.java:100) at com.android.tools.idea.welcome.FirstRunWizardHost.getWelcomePanel(FirstRunWizardHost.java:92) at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame.(WelcomeFrame.java:68) at com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame.showNow(WelcomeFrame.java:173) at com.intellij.idea.IdeaApplication$IdeStarter.main(IdeaApplication.java:302) at com.intellij.idea.IdeaApplication.run(IdeaApplication.java:172) ... 16 more1)进入刚安装的Android Studio目录下的bin目录。找到idea.properties文件,用文本编辑器打开。2)在idea.properties文件末尾添加一行: disable.android.first.run=true ,然后保存文件。3)关闭Android Studio后重新启动,便可进入界面。public class DrawView extends View { public DrawView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); / 方法 说明 drawRect 绘制矩形 drawCircle 绘制圆形 drawOval 绘制椭圆 drawPath 绘制任意多边形 drawLine 绘制直线 drawPoin 绘制点 / // 创建画笔 Paint p = new Paint(); p.setColor(Color.RED);// 设置红色 canvas.drawText("画圆:", 10, 20, p);// 画文本 canvas.drawCircle(60, 20, 10, p);// 小圆 p.setAntiAlias(true);// 设置画笔的锯齿效果。 true是去除,大家一看效果就明白了 canvas.drawCircle(120, 20, 20, p);// 大圆 canvas.drawText("画线及弧线:", 10, 60, p); p.setColor(Color.GREEN);// 设置绿色 canvas.drawLine(60, 40, 100, 40, p);// 画线 canvas.drawLine(110, 40, 190, 80, p);// 斜线 //画笑脸弧线 p.setStyle(Paint.Style.STROKE);//设置空心 RectF oval1=new RectF(150,20,180,40); canvas.drawArc(oval1, 180, 180, false, p);//小弧形 oval1.set(190, 20, 220, 40); canvas.drawArc(oval1, 180, 180, false, p);//小弧形 oval1.set(160, 30, 210, 60); canvas.drawArc(oval1, 0, 180, false, p);//小弧形 canvas.drawText("画矩形:", 10, 80, p); p.setColor(Color.GRAY);// 设置灰色 p.setStyle(Paint.Style.FILL);//设置填满 canvas.drawRect(60, 60, 80, 80, p);// 正方形 canvas.drawRect(60, 90, 160, 100, p);// 长方形 canvas.drawText("画扇形和椭圆:", 10, 120, p); / 设置渐变色 这个正方形的颜色是改变的 / Shader mShader = new LinearGradient(0, 0, 100, 100, new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.LTGRAY }, null, Shader.TileMode.REPEAT); // 一个材质,打造出一个线性梯度沿著一条线。 p.setShader(mShader); // p.setColor(Color.BLUE); RectF oval2 = new RectF(60, 100, 200, 240);// 设置个新的长方形,扫描测量 canvas.drawArc(oval2, 200, 130, true, p); // 画弧,第一个参数是RectF:该类是第二个参数是角度的开始,第三个参数是多少度,第四个参数是真的时候画扇形,是假的时候画弧线 //画椭圆,把oval改一下 oval2.set(210,100,250,130); canvas.drawOval(oval2, p); canvas.drawText("画三角形:", 10, 200, p); // 绘制这个三角形,你可以绘制任意多边形 Path path = new Path(); path.moveTo(80, 200);// 此点为多边形的起点 path.lineTo(120, 250); path.lineTo(80, 250); path.close(); // 使这些点构成封闭的多边形 canvas.drawPath(path, p); // 你可以绘制很多任意多边形,比如下面画六连形 p.reset();//重置 p.setColor(Color.LTGRAY); p.setStyle(Paint.Style.STROKE);//设置空心 Path path1=new Path(); path1.moveTo(180, 200); path1.lineTo(200, 200); path1.lineTo(210, 210); path1.lineTo(200, 220); path1.lineTo(180, 220); path1.lineTo(170, 210); path1.close();//封闭 canvas.drawPath(path1, p); / Path类封装复合(多轮廓几何图形的路径 由直线段、二次曲线,和三次方曲线,也可画以油画。drawPath(路径、油漆),要么已填充的或抚摸 (基于油漆的风格),或者可以用于剪断或画画的文本在路径。 / //画圆角矩形 p.setStyle(Paint.Style.FILL);//充满 p.setColor(Color.LTGRAY); p.setAntiAlias(true);// 设置画笔的锯齿效果 canvas.drawText("画圆角矩形:", 10, 260, p); RectF oval3 = new RectF(80, 260, 200, 300);// 设置个新的长方形 canvas.drawRoundRect(oval3, 20, 15, p);//第二个参数是x半径,第三个参数是y半径 //画贝塞尔曲线 canvas.drawText("画贝塞尔曲线:", 10, 310, p); p.reset(); p.setStyle(Paint.Style.STROKE); p.setColor(Color.GREEN); Path path2=new Path(); path2.moveTo(100, 320);//设置Path的起点 path2.quadTo(150, 310, 170, 400); //设置贝塞尔曲线的控制点坐标和终点坐标 canvas.drawPath(path2, p);//画出贝塞尔曲线 //画点 p.setStyle(Paint.Style.FILL); canvas.drawText("画点:", 10, 390, p); canvas.drawPoint(60, 390, p);//画一个点 canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//画多个点 //画图片,就是贴图 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); canvas.drawBitmap(bitmap, 250,360, p); }}

Fragment

Fragment是Android 3.0以后的东西,为了在低版本中使用Fragment就要用到android-support-v4.jar兼容包,而FragmentActivity就是这个兼容包里面的,它提供了操作Fragment的一些方法,其功能跟3.0及以后的版本的Activity的功能一样。
下面是API中的原话:
FragmentActivity is a special activity provided in the Support Library to handle fragments on system versions older than API level 11. If the lowest system version you support is API level 11 or higher, then you can use a regular Activity.
主要区别如下:
1、FragmentActivity 继承自Activity,用来解决Android 3.0之前无法使用Fragment的问题,所以在使用的时候需要导入android-support-v4.jar兼容包,同时继承 FragmentActivity,这样在Activity中就能嵌入Fragment来实现你想要的布局效果。
2、当然Android 3.0之后你就可以直接继承自Activity,并且在其中嵌入使用Fragment。
3、获得FragmentManager的方式也不同
Android 3.0以下:getSupportFragmentManager()
Android 3.0以上:getFragmentManager()
1、Fragment的产生与介绍
Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。
2、Fragment的生命周期
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。
可以看到Fragment比Activity多了几个额外的生命周期回调方法:
onAttach(Activity)
当Fragment与Activity发生关联时调用。
onCreateView(LayoutInflater, ViewGroup,Bundle)
创建该Fragment的视图
onActivityCreated(Bundle)
当Activity的onCreate方法返回时调用
onDestoryView()
与onCreateView想对应,当该Fragment的视图被移除时调用
onDetach()
与onAttach相对应,当Fragment与Activity关联被取消时调用
注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,
3、静态的使用Fragment
嘿嘿,终于到使用的时刻了~~
这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中。步骤:
1、继承Fragment,重写onCreateView决定Fragemnt的布局
2、在Activity中声明此Fragment,就当和普通的View一样
程序安装完出现了两个AP图标,AndroidManifest.xml中一定是有两个
<category
android:name="android.intent.category.LAUNCHER"
只保留启动的第一个activity
Android应用如何实现换肤功能
国内有很多的软件都支持皮肤定制,这也是与国外软件重大不同之一,国外用户注重社交、邮件等功能,国内用户则重视音乐、小说、皮肤等功能。
软件换肤从功能上可以划分三种:
1) 软件内置多个皮肤,不可由用户增加或修改;
最低的自由度,软件实现相对于后两种最容易。
2) 官方提供皮肤供下载,用户可以使用下载的皮肤;
用户可选择下载自己喜欢的皮肤,有些玩家会破解皮肤的定制方法,自己做皮肤使用,或者传到网上给大家用。
3) 官方提供皮肤制作工具或方法,用户可自制皮肤。
这种方式使用户有参与感,自由度较高。用户可根据自己的喜好定制软件的皮肤。有些软件官网提供皮肤定制的工具或者方法,我建议最好有可视化带向导的工具。用户只要自己找一些图片、修改文字的字体替换就可以了。用户可以上传自制的皮肤,提供其他用户下载,还可以赚得一些虚拟货币或者奖品什么的。这种一般都是打包为.zip格式的。扩展名可由各公司自定义,有制作工具的话直接导出来最方便。
首先我们要弄清楚换肤的定义,软件皮肤包括图标、字体、布局、交互风格等,换肤就是换掉皮肤包括的部分或所有资源。
前面提到的三种皮肤,从软件实现上来看,它们的本质区别是皮肤是否内置到应用程序中。对于内置的实现比较简单,只要在开发应用的过程中设计几套皮肤供用户选择。
皮肤一般含有多个文件,例如图片、配置等文件,分散的文件不利于传输和使用,最好打包。打包的格式一般选择zip格式。这里分两种情况,一种是apk,例如AdwLauncher,它的桌面皮肤格式是一个apk;另一种是自定义扩展名,例如墨迹天气皮肤扩展名是mja,搜狗输入法的皮肤扩展名是sga,它们的文件格式实际上都是zip。
下面分别讲解。
一.apk格式
现在的问题变成了一个应用如何读取另一个apk中的资源。
在android系统中,apk之间可以相互读取数据的条件是:有同样的签名,并且AndroidManifest.xml文件中配置的android:sharedUserId属性值相同,那么两个apk运行在同一个进程中,可以互相访问任意数据。
方法如下:
1) 应用程序和皮肤程序的AndroidManifest.xml中配置
例如: android:sharedUserId="org.yuchen"
2) 文件与应用apk中对同一功能的皮肤文件名要一致
例如:应用程序的背景图片路径:/SkinDemo/res/drawable-hdpi/bg.png
那么皮肤apk中的背景图片文件路径也应该是:
CustomSkin/res/drawable-hdpi/bg.png
3)访问资源的方法
[java] view plain copy print?
Context context = createPackageContext("com.yuchen.customskin", Context.CONTEXT_IGNORE_SECURITY);
获取到org.yuchen.customskin对应的Context,通过返回的context对象就可以访问到org.yuchen.customskin中的任何资源。
例如:应用apk要获得皮肤apk中的bg.png,
[java] view plain copy print?
Drawable drawable = context.getResources().getDrawable(R.drawable.bg);
这样就得到了图片的引用,其他xml资源文件的获取方式也是类似的。
二.自定义扩展名的zip格式的皮肤
技术点在于如何去读取zip文件中的资源以及皮肤文件存放策略。
方案:如果软件每次启动都去读取SD卡上的皮肤文件,速度会比较慢。较好的做法是提供一个皮肤设置的界面,用户选择了哪一个皮肤,就把那个皮肤文件解压缩到”/data/data/[package name]/skin”路径下,这样不需要跨存储器读取,速度较快,而且不需要每次都去zip压缩包中读取,不依赖SD卡中的文件,即使皮肤压缩包文件被删除了也没有关系。
实现方法:
1. 在软件的帮助或者官网的帮助中提示用户将皮肤文件拷贝到SD卡指定路径下。
2. 在软件中提供皮肤设置界面。可以在菜单或者在设置中。
3. 加载指定路径下的皮肤文件,读取其中的缩略图,在皮肤设置界面中显示,将用户选中的皮肤文件解压缩到”/data/data/[package name]/skin”路径下。
4. 软件中优先读取”/data/data/[package name]/skin/”路径下的资源。如果没有则使用apk中的资源。

Android设置Activity背景为透明style

方法一:
通过Theme.Translucent
@android:style/Theme.Translucent
@android:style/Theme.Translucent.NoTitleBar
@android:style/Theme.Translucent.NoTitleBar.Fullscreen
只需要在Manifest中需要透明的Activity内设置theme为以上任意一个就可以了
[java] view plain copy
<activity
android:name="com.vixtel.simulate.MainApp"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
方法二:
自定义style,就像自定义Dialog的style一样,在res-values-color.xml中添加透明颜色值:
[java] view plain copy
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<color name="transparent">#0000</color>
</resources>
在res-values-styles.xml中添加如下:
[java] view plain copy
<style name="myTransparent">
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>
在Manifest中中需要透明的Activity内设置theme为我们自定义的即可
[java] view plain copy
android:theme="@style/myTransparent"
看得见背景下的所有东西可以却都操作无效。
AppCompatActivity Activity区别
一个有 ActionBar
安卓点击事件的四种写法
1、直接在添加监听
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = (Button) findViewById(R.id.loginbtn);
MyOnclickListener ml = new MyOnclickListener();
btn.setOnClickListener(ml);
}
class MyOnclickListener implements OnClickListener{
@Override
public void onClick(View v) {
Toast.makeText(MainActivity1.this, "点击了按钮", 5).show();
}
}
2、利用匿名内部类
Button btn = (Button) findViewById(R.id.loginbtn);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity2.this, "点击了按钮", 5).show();
}
});
3、activity实现onclicklistener接口
public class MainActivity extends Activity implements OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn =(Button)findViewById(R.id.loginbtn);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Toast.makeText(this, "点击了按钮", 5).show();
}
}
4、利用xml中onclick属性
public void loginclick(View v){
Toast.makeText(this, "点击了按钮", 5).show();
}
<Button
android:id="@+id/loginbtn"
android:onClick="loginclick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loginbtn" />

EventBus使用详解

EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
基本使用
(1)自定义一个类,可以是空类,比如:
public class AnyEventType {
public AnyEventType(){}
}
(2)在要接收消息的页面注册:
eventBus.register(this);
(3)发送消息
eventBus.post(new AnyEventType event);
(4)接受消息的页面实现(共有四个函数,各功能不同,这是其中之一,可以选择性的实现,这里先实现一个):
public void onEvent(AnyEventType event) {}
(5)解除注册
eventBus.unregister(this);
1、基本框架搭建
想必大家从一个Activity跳转到第二个Activity的程序应该都会写,这里先稍稍把两个Activity跳转的代码建起来。后面再添加EventBus相关的玩意。
MainActivity布局(activity_main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_try"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="btn_bty"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</LinearLayout>
新建一个Activity,SecondActivity布局(activity_second.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.harvic.try_eventbus_1.SecondActivity" >
<Button
android:id="@+id/btn_first_event"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="First Event"/>
</LinearLayout>
MainActivity.java (点击btn跳转到第二个Activity)
public class MainActivity extends Activity {
Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn_try);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),
SecondActivity.class);
startActivity(intent);
}
});
}
}
使用EventBus
2、新建一个类FirstEvent
package com.harvic.other;
public class FirstEvent {
private String mMsg;
public FirstEvent(String msg) {
mMsg = msg;
}
public String getMsg(){
return mMsg;
}
}
构造时传进去一个字符串,然后可以通过getMsg()获取出来。
3、在要接收消息的页面注册EventBus:
在上面的GIF图片的演示中,大家也可以看到,我们是要在MainActivity中接收发过来的消息的,所以我们在MainActivity中注册消息。
通过我们会在OnCreate()函数中注册EventBus,在OnDestroy()函数中反注册。所以整体的注册与反注册的代码如下:
package com.example.tryeventbus_simple;
import com.harvic.other.FirstEvent;
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
Button btn;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
btn = (Button) findViewById(R.id.btn_try);
tv = (TextView)findViewById(R.id.tv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),
SecondActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onDestroy(){
super.onDestroy();
EventBus.getDefault().unregister(this);//反注册EventBus
}
}
4、发送消息
发送消息是使用EventBus中的Post方法来实现发送的,发送过去的是我们新建的类的实例!
EventBus.getDefault().post(new FirstEvent("FirstEvent btn clicked"));
完整的SecondActivity.java的代码如下:
package com.example.tryeventbus_simple;
import com.harvic.other.FirstEvent;
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class SecondActivity extends Activity {
private Button btn_FirstEvent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
btn_FirstEvent = (Button) findViewById(R.id.btn_first_event);
btn_FirstEvent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(
new FirstEvent("FirstEvent btn clicked"));
}
});
}
}
5、接收消息
接收消息时,我们使用EventBus中最常用的onEventMainThread()函数来接收消息,具体为什么用这个,我们下篇再讲,这里先给大家一个初步认识,要先能把EventBus用起来先。
在MainActivity中重写onEventMainThread(FirstEvent event),参数就是我们自己定义的类:
在收到Event实例后,我们将其中携带的消息取出,一方面Toast出去,一方面传到TextView中;
public void onEventMainThread(FirstEvent event) {
String msg = "onEventMainThread收到了消息:" + event.getMsg();
Log.d("harvic", msg);
tv.setText(msg);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
完整的MainActiviy代码如下:
package com.example.tryeventbus_simple;
import com.harvic.other.FirstEvent;
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
Button btn;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
btn = (Button) findViewById(R.id.btn_try);
tv = (TextView)findViewById(R.id.tv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),
SecondActivity.class);
startActivity(intent);
}
});
}
public void onEventMainThread(FirstEvent event) {
String msg = "onEventMainThread收到了消息:" + event.getMsg();
Log.d("harvic", msg);
tv.setText(msg);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
@Override
protected void onDestroy(){
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
1、onEvent
2、onEventMainThread
3、onEventBackgroundThread
4、onEventAsync
这四种订阅函数都是使用onEvent开头的,它们的功能稍有不同,在介绍不同之前先介绍两个概念:
告知观察者事件发生时通过EventBus.post函数实现,这个过程叫做事件的发布,观察者被告知事件发生叫做事件的接收,是通过下面的订阅函数实现的。
onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.
新建一个类:
package com.harvic.other;
public class FirstEvent {
private String mMsg;
public FirstEvent(String msg) {
mMsg = msg;
}
public String getMsg(){
return mMsg;
}
}
发送时:
EventBus.getDefault().post(new FirstEvent("FirstEvent btn clicked"));
接收时:
public void onEventMainThread(FirstEvent event) {
……
}
2、实例
新建三个类:FirstEvent、SecondEvent、ThirdEvent,在第二个Activity中发送请求,在MainActivity中接收这三个类的实例,接收时的代码为:
public void onEventMainThread(FirstEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEventMainThread(SecondEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEvent(ThirdEvent event) {
Log.d("harvic", "OnEvent收到了消息:" + event.getMsg());
}
在发送FirstEvent时,在MainActiviy中虽然有三个函数,但只有第一个onEventMainThread函数的接收参数是FirstEvent,所以会传到它这来接收。所以这里识别调用EventBus中四个函数中哪个函数,是通过参数中的实例来决定的。
1、三个类
package com.harvic.other;
public class FirstEvent {
private String mMsg;
public FirstEvent(String msg) {
mMsg = msg;
}
public String getMsg(){
return mMsg;
}
}
package com.harvic.other;
public class SecondEvent{
private String mMsg;
public SecondEvent(String msg) {
mMsg = "MainEvent:"+msg;
}
public String getMsg(){
return mMsg;
}
}
package com.harvic.other;
public class ThirdEvent {
private String mMsg;
public ThirdEvent(String msg) {
mMsg = msg;
}
public String getMsg(){
return mMsg;
}
}
2、发送
然后在SecondActivity中新建三个按钮,分别发送不同的类的实例,代码如下:
package com.harvic.tryeventbus2;
import com.harvic.other.FirstEvent;
import com.harvic.other.SecondEvent;
import com.harvic.other.ThirdEvent;
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class SecondActivity extends Activity {
private Button btn_FirstEvent, btn_SecondEvent, btn_ThirdEvent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
btn_FirstEvent = (Button) findViewById(R.id.btn_first_event);
btn_SecondEvent = (Button) findViewById(R.id.btn_second_event);
btn_ThirdEvent = (Button) findViewById(R.id.btn_third_event);
btn_FirstEvent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(
new FirstEvent("FirstEvent btn clicked"));
}
});
btn_SecondEvent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(
new SecondEvent("SecondEvent btn clicked"));
}
});
btn_ThirdEvent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(
new ThirdEvent("ThirdEvent btn clicked"));
}
});
}
}
3、接收
在MainActivity中,除了注册与注册,我们利用onEventMainThread(FirstEvent event)来接收来自FirstEvent的消息,使用onEventMainThread(SecondEvent event)接收来自SecondEvent 实例的消息,使用onEvent(ThirdEvent event) 来接收ThirdEvent 实例的消息。
package com.harvic.tryeventbus2;
import com.harvic.other.FirstEvent;
import com.harvic.other.SecondEvent;
import com.harvic.other.ThirdEvent;
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
Button btn;
TextView tv;
EventBus eventBus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
btn = (Button) findViewById(R.id.btn_try);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),
SecondActivity.class);
startActivity(intent);
}
});
}
public void onEventMainThread(FirstEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEventMainThread(SecondEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEvent(ThirdEvent event) {
Log.d("harvic", "OnEvent收到了消息:" + event.getMsg());
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
到这里,代码就结束 了,从上面的代码,我们可以看到,EventBus是怎么接收消息的,是根据参数中类的实例的类型的判定的,所以当如果我们在接收时,同一个类的实例参数有两个函数来接收会怎样?答案是,这两个函数都会执行,下面实验一下:
在MainActivity中接收时,我们在接收SecondEvent时,在上面onEventMainThread基础上另加一个onEventBackgroundThread和onEventAsync,即下面的代码:
public void onEventMainThread(SecondEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEventBackgroundThread(SecondEvent event){
Log.d("harvic", "onEventBackground收到了消息:" + event.getMsg());
}
public void onEventAsync(SecondEvent event){
Log.d("harvic", "onEventAsync收到了消息:" + event.getMsg());
}
完整的代码在这里:
package com.harvic.tryeventbus2;
import com.harvic.other.FirstEvent;
import com.harvic.other.SecondEvent;
import com.harvic.other.ThirdEvent;
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
Button btn;
TextView tv;
EventBus eventBus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
btn = (Button) findViewById(R.id.btn_try);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(),
SecondActivity.class);
startActivity(intent);
}
});
}
public void onEventMainThread(FirstEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEventMainThread(SecondEvent event) {
Log.d("harvic", "onEventMainThread收到了消息:" + event.getMsg());
}
public void onEventBackgroundThread(SecondEvent event){
Log.d("harvic", "onEventBackground收到了消息:" + event.getMsg());
}
public void onEventAsync(SecondEvent event){
Log.d("harvic", "onEventAsync收到了消息:" + event.getMsg());
}
public void onEvent(ThirdEvent event) {
Log.d("harvic", "OnEvent收到了消息:" + event.getMsg());
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
当发送SecondEvent实例的消息过来的时候,这三个函数会同时接收到并各自执行

dp px区别

dp是虚拟像素,在不同的像素密度的设备上会自动适配,比如:
在320x480分辨率,像素密度为160,1dp=1px
在480x800分辨率,像素密度为240,1dp=1.5px
计算公式:1dp*像素密度/160 = 实际像素数
px(像素):屏幕上的点.
dp(与密度无关的像素):一种基于屏幕密度的抽象单位.在每英寸160点的显示器上,1dp = 1px.

Android 更新UI的两种方法——handler和runOnUiThread()

在Android开发过程中,常需要更新界面的UI。而更新UI是要主线程来更新的,即UI线程更新。如果在主线线程之外的线程中直接更新页面显示常会报错。抛出异常:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
只有原始创建这个视图层次(view hierachy)的线程才能修改它的视图(view)
方法一:
在Activity.onCreate(Bundle savedInstanceState)中创建一个Handler类的实例, 在这个Handler实例的handleMessage回调函数中调用更新界面显示的函数。
界面:
[html]
<span style="font-size:14px;">public class MainActivity extends Activity {
private EditText UITxt;
private Button updateUIBtn;
private UIHandler UIhandler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UITxt = (EditText)findViewById(R.id.ui_txt);
updateUIBtn = (Button)findViewById(R.id.update_ui_btn);
updateUIBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
UIhandler = new UIHandler();
UIThread thread = new UIThread();
thread.start();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
private class UIHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
String color = bundle.getString("color");
UITxt.setText(color);
}
}
private class UIThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
Bundle bundle = new Bundle();
bundle.putString("color", "黄色");
msg.setData(bundle);
MainActivity.this.UIhandler.sendMessage(msg);
}
}
}</span>
更新后:www.2cto.com
方法二:利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程
[html]
FusionField.currentActivity.runOnUiThread(new Runnable()
{
public void run()
{
Toast.makeText(getApplicationContext(), , "Update My UI",
Toast.LENGTH_LONG).show();
}
});

drawerLayout

drawerLayout是Support Library包中实现了侧滑菜单效果的控件,可以说drawerLayout是因为第三方控件如MenuDrawer等的出现之后,google借鉴而出现的产物。drawerLayout分为侧边菜单和主内容区两部分,侧边菜单可以根据手势展开与隐藏(drawerLayout自身特性),主内容区的内容可以随着菜单的点击而变化(这需要使用者自己实现)。
drawerLayout的使用很方便,使用drawerLayout的要点如下:
1.drawerLayout其实是一个布局控件,跟LinearLayout等控件是一种东西,但是drawerLayout带有滑动的功能。只要按照drawerLayout的规定布局方式写完布局,就能有侧滑的效果。
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
The drawer is given a fixed width in dp and extends the full height of
the container. A solid background is used for contrast
with the content view. -->
<ListView
android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
有两点要注意:主内容区的布局代码要放在侧滑菜单布局的前面,这可以帮助DrawerLayout判断谁是侧滑菜单,谁是主内容区;侧滑菜单的部分的布局(这里是ListView)可以设置layout_gravity属性,他表示侧滑菜单是在左边还是右边。
2.drawerLayout左侧菜单(或者右侧)的展开与隐藏可以被DrawerLayout.DrawerListener的实现监听到,这样你就可以在菜单展开与隐藏反生的时刻做一些希望做的事情,比如更新actionbar菜单等。如果你的activity有actionbar的话,还是建议你用ActionBarDrawerToggle来监听,ActionBarDrawerToggle实现了DrawerListener,所以他能做DrawerListener可以做的任何事情,同时他还能将drawerLayout的展开和隐藏与actionbar的app 图标关联起来,当展开与隐藏的时候图标有一定的平移效果,点击图标的时候还能展开或者隐藏菜单。
mDrawerToggle = new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */
) {
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
3.何为侧边菜单。
侧边菜单其实只是一个普通的View,一般里面装的是ListView,看起来就像菜单,他完全可以是一个button,textView等等。虽然称为菜单,但跟Activity的菜单形式是两码事,Activity的菜单只需要在资源文件中定义好,就能按照固定的形式显示出来。而drawerLayout的侧边菜单显示成什么样完全是取决于你自己,同样点击事件也完全由你自己去写。如下代码所示我们的侧边菜单是一个ListView显示的:
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
R.layout.drawer_list_item, mPlanetTitles));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
Fragment fragment = new PlanetFragment();
Bundle args = new Bundle();
args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
fragment.setArguments(args);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
mDrawerList.setItemChecked(position, true);
setTitle(mPlanetTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
4.在代码中主动展开与隐藏侧边菜单。
在点击侧边菜单选项的时候我们往往需要隐藏菜单来显示整个菜单对应的内容。DrawerLayout.closeDrawer方法用于隐藏侧边菜单,DrawerLayout.openDrawer方法用于展开侧边菜单(参见第3点中的代码部分)
5.如何在菜单展开或者隐藏的时候更新activity的menu
上面的的第2点讲到DrawerLayout.DrawerListener监听展开与隐藏事件,在监听的回调方法中我们用invalidateOptionsMenu通知activity重绘menu,然后activity就有机会在onPrepareOptionsMenu方法中更新menu元素的显示与隐藏
代码:
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
6.如何让app图标点击的时候能够展开或者隐藏侧边菜单。
一般的想法是在activity的onOptionsItemSelected方法中判断点击事件是否来自于app图标,然后用DrawerLayout.closeDrawer和DrawerLayout.openDrawer来隐藏与展开(参见第4点:在代码中主动展开与隐藏侧边菜单)。但是drawerLayout提供了更优雅的方式:使用ActionBarDrawerToggle的onOptionsItemSelected方法。该方法activity的onOptionsItemSelected方法中根据传递进来的menu item做了上面我们在“一般想法”中提到的事情。用官方的说法是”ActionBarDrawerTogglewill take care of this”。我们只需这样做就ok了:
Activity中:
@Override
public booleanonOptionsItemSelected(MenuItem item) {
if(mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
……….//处理其他菜单点击事件
returnsuper.onOptionsItemSelected(item);
}
如果不仔细阅读官方文档,估计我们很难看出(mDrawerToggle.onOptionsItemSelected(item)在这里的作用。这也是我刚开始最疑惑的地方。
7. drawerLayout与Fragment是什么关系?
我们看到很多使用drawerLayout的代码中都同时使用了Fragment,这会造成误解,以为使用drawerLayout必须用到Fragment,其实这是错误的,使用Fragment是因为在侧滑菜单被点击的时候,主内容区如果内容比较复杂,用Fragment去填充会更容易,如果你的主内容区只是一个简单的字符串,只想在不同菜单点击的时候更新一下字符串的内容,我觉得没必要用Fragment。不过官方的例子其实中,Fragment所做的就是更新字符串内容这么简单。
最后我们来看看一个完整的drawerLayout的例子,来源于官方网站的demo,代码中反映了上述我们提到的所有要点:
Activity:
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.navigationdrawerexample;
import java.util.Locale;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.SearchManager;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
* This example illustrates a common usage of the DrawerLayout widget
* in the Android support library.
* <p/>
* <p>When a navigation (left) drawer is present, the host activity should detect presses of
* the action bar's Up affordance as a signal to open and close the navigation drawer. The
* ActionBarDrawerToggle facilitates this behavior.
* Items within the drawer should fall into one of two categories:</p>
* <p/>
* <ul>
* <li><strong>View switches</strong>. A view switch follows the same basic policies as
* list or tab navigation in that a view switch does not create navigation history.
* This pattern should only be used at the root activity of a task, leaving some form
* of Up navigation active for activities further down the navigation hierarchy.</li>
* <li><strong>Selective Up</strong>. The drawer allows the user to choose an alternate
* parent for Up navigation. This allows a user to jump across an app's navigation
* hierarchy at will. The application should treat this as it treats Up navigation from
* a different task, replacing the current task stack using TaskStackBuilder or similar.
* This is the only form of navigation drawer that should be used outside of the root
* activity of a task.</li>
* </ul>
* <p/>
* <p>Right side drawers should be used for actions, not navigation. This follows the pattern
* established by the Action Bar that navigation should be to the left and actions to the right.
* An action should be an operation performed on the current contents of the window,
* for example enabling or disabling a data overlay on top of the current content.</p>
*/
public class MainActivity extends Activity {
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private ActionBarDrawerToggle mDrawerToggle;
private CharSequence mDrawerTitle;
private CharSequence mTitle;
private String[] mPlanetTitles;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTitle = mDrawerTitle = getTitle();
mPlanetTitles = getResources().getStringArray(R.array.planets_array);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
R.layout.drawer_list_item, mPlanetTitles));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
mDrawerToggle = new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */
) {
public void onDrawerClosed(View view) {
getActionBar().setTitle(mTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(mDrawerTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
mDrawerLayout.setDrawerListener(mDrawerToggle);
if (savedInstanceState == null) {
selectItem(0);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
switch(item.getItemId()) {
case R.id.action_websearch:
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private class DrawerItemClickListener implements ListView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
Fragment fragment = new PlanetFragment();
Bundle args = new Bundle();
args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
fragment.setArguments(args);
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
mDrawerList.setItemChecked(position, true);
setTitle(mPlanetTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
@Override
public void setTitle(CharSequence title) {
mTitle = title;
getActionBar().setTitle(mTitle);
}
* When using the ActionBarDrawerToggle, you must call it during
* onPostCreate() and onConfigurationChanged()...
*/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
* Fragment that appears in the "content_frame", shows a planet
*/
public static class PlanetFragment extends Fragment {
public static final String ARG_PLANET_NUMBER = "planet_number";
public PlanetFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_planet, container, false);
int i = getArguments().getInt(ARG_PLANET_NUMBER);
String planet = getResources().getStringArray(R.array.planets_array)[i];
int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),
"drawable", getActivity().getPackageName());
((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);
getActivity().setTitle(planet);
return rootView;
}
}
}
Xml
activity_main.xml
<!--
Copyright 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
The drawer is given a fixed width in dp and extends the full height of
the container. A solid background is used for contrast
with the content view. -->
<ListView
android:id="@+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#111"/>
fragment_planet.xml
<!--
Copyright 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:gravity="center"
android:padding="32dp" />
drawer_list_item.xml
<!--
Copyright 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textColor="#fff"
android:background="?android:attr/activatedBackgroundIndicator"
android:minHeight="?android:attr/listPreferredItemHeightSmall"/>

onNewIntent调用时机

在IntentActivity中重写下列方法:onCreate onStart onRestart onResume onPause onStop onDestroy onNewIntent
一、其他应用发Intent,执行下列方法:
I/@@@philn(12410): onCreate
I/@@@philn(12410): onStart
I/@@@philn(12410): onResume
发Intent的方法:
Uri uri = Uri.parse("philn://blog.163.com");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
二、接收Intent声明:
复制代码
<activity android:name=".IntentActivity" android:launchMode="singleTask"
android:label="@string/testname">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="philn"/>
</intent-filter>
</activity>
复制代码
三、如果IntentActivity处于任务栈的顶端,也就是说之前打开过的Activity,现在处于
I/@@@philn(12410): onPause
I/@@@philn(12410): onStop 状态的话
其他应用再发送Intent的话,执行顺序为:
I/@@@philn(12410): onNewIntent
I/@@@philn(12410): onRestart
I/@@@philn(12410): onStart
I/@@@philn(12410): onResume
在Android应用程序开发的时候,从一个Activity启动另一个Activity并传递一些数据到新的Activity上非常简单,但是当您需要让后台运行的Activity回到前台并传递一些数据可能就会存在一点点小问题。
首先,在默认情况下,当您通过Intent启到一个Activity的时候,就算已经存在一个相同的正在运行的Activity,系统都会创建一个新的Activity实例并显示出来。为了不让Activity实例化多次,我们需要通过在AndroidManifest.xml配置activity的加载方式(launchMode)以实现单任务模式,如下所示:
launchMode为singleTask的时候,通过Intent启到一个Activity,如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法,而是调用onNewIntent方法,如下所示:
复制代码
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);//must store the new intent unless getIntent() will return the old one
processExtraData();
}
复制代码
不要忘记,系统可能会随时杀掉后台运行的Activity,如果这一切发生,那么系统就会调用onCreate方法,而不调用onNewIntent方法,一个好的解决方法就是在onCreate和onNewIntent方法中调用同一个处理数据的方法,如下所示:
复制代码
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
processExtraData();
}
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);//must store the new intent unless getIntent() will return the old one
processExtraData()
}
private void processExtraData(){
Intent intent = getIntent();
}

图片缩放旋转合并

public static Drawable resizeImage(Bitmap bitmap, int w, int h)
{
Bitmap BitmapOrg = bitmap;
int width = BitmapOrg.getWidth();
int height = BitmapOrg.getHeight();
int newWidth = w;
int newHeight = h;
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width,
height, matrix, true);
return new BitmapDrawable(resizedBitmap);
}
public static Drawable resizeImage2(String path,
int width,int height)
{
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//不加载bitmap到内存中
BitmapFactory.decodeFile(path,options);
int outWidth = options.outWidth;
int outHeight = options.outHeight;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inSampleSize = 1;
if (outWidth != 0 && outHeight != 0 && width != 0 && height != 0)
{
int sampleSize=(outWidth/width+outHeight/height)/2;
Log.d(tag, "sampleSize = " + sampleSize);
options.inSampleSize = sampleSize;
}
options.inJustDecodeBounds = false;
return new BitmapDrawable(BitmapFactory.decodeFile(path, options));
}
private Bitmap mergeBitmap(Bitmap firstBitmap, Bitmap secondBitmap) {
Bitmap bitmap = Bitmap.createBitmap(firstBitmap.getWidth(), firstBitmap.getHeight(),firstBitmap.getConfig());
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(firstBitmap, new Matrix(), null);
canvas.drawBitmap(secondBitmap, 0, 0, null);
return bitmap;
}
Bitmap bitmap1;
Bitmap bitmap2;
Bitmap bitmap3 = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
Canvas canvas = new Canvas(bitmap3);
canvas.drawBitmap(bitmap1, new Matrix(), null);
canvas.drawBitmap(bitmap2, 120, 350, null); //120、350为bitmap2写入点的x、y坐标
FileOutputStream out = new FileOutputStream(path+File.separator+"image3.png");
bitmap3.compress(Bitmap.CompressFormat.PNG, 90, out);
ImageView设置Bitmap
ImageView mImageView = (ImageView)findViewById(R.id.item_image);
mImageView.setImageBitmap(Bitmap);

ButterKnife详解

项目地址:https://github.com/JakeWharton/butterknife
这个开源库可以让我们从大量的findViewById()和setonclicktListener()解放出来,其对性能的影响微乎其微(查看过Butter Knife的源码,其自定义注解的实现都是限定为RetentionPolicy.CLASS,也就是到编译出.class文件为止有效,在运行时不额外消耗性能,其是通过java注解自动生成java代码的形式来完成工作),其也有一个明显的缺点,那就是代码的可读性差一些
解放控件对象实例化
也就是 findViewById(),一直以来的做法都是一个个定义,然后在 setContentView() 或 inflate() 之后一一来findViewById()进行实例化,而使用 ButterKnife,你只需要在代码中 使用注解方式进行对象申明,然后在 setContentView() 或 inflate() 之后调用一句话,那么申明的所有对象自动创建出来。
@InjectView(R.id.ok_btn) //控件对应的ID
Button mBtn;
@InjectView(R.id.title_text)
TextView mTitleTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
ButterKnife.inject(this);
mTitleTextView.setText("test");
}
Fragment的和adapter里也可以用,不过调用时要多加一个root view参数。
Fragegment使用时记得同时继承onDestroyView,并在其中将ButterKnife.reset
public class FancyFragment extends Fragment {
@InjectView(R.id.button1) Button button1;
@InjectView(R.id.button2) Button button2;
@Override View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.inject(this, view);
return view;
}
}
@Override void onDestroyView() {
super.onDestroyView();
ButterKnife.reset(this);
}
还可以实例化控件数组,注解多一个s,也就是 InjectViews
@InjectViews({ R.id.first_name, R.id.middle_name, R.id.last_name }) List<EditText> nameViews;
解放监听添加
如下,可以直接为 R.id.submit这个控件添加OnClickListener为submit函数,流弊啊。。。
@OnClick(R.id.submit)
public void submit() {
}
还可以批量为多个控件添加为同一个响应函数:
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}
class ExampleActivity extends Activity {
@FindView(R.id.user) EditText username;
@FindView(R.id.pass) EditText password;
@OnClick(R.id.submit) void submit() {
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
}
}

APK res path

public Drawable getAPKDrawable(String filePath) {
Drawable dr = null;
if (filePath != null) {
String PATH_PackageParser = "android.content.pm.PackageParser";
String PATH_AssetManager = "android.content.res.AssetManager";
try {
Class pkgParserCls = Class.forName(PATH_PackageParser);
Class[] typeArgs = new Class[1];
typeArgs[0] = String.class;
Constructor pkgParserCt = pkgParserCls.getConstructor(typeArgs);
Object[] valueArgs = new Object[1];
valueArgs[0] = filePath;
Object pkgParser = pkgParserCt.newInstance(valueArgs);
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
typeArgs = new Class[4];
typeArgs[0] = File.class;
typeArgs[1] = String.class;
typeArgs[2] = DisplayMetrics.class;
typeArgs[3] = Integer.TYPE;
Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage", typeArgs);
valueArgs = new Object[4];
valueArgs[0] = new File(filePath);
valueArgs[1] = filePath;
valueArgs[2] = metrics;
valueArgs[3] = 0;
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, valueArgs);
Field appInfoFld = pkgParserPkg.getClass().getDeclaredField("applicationInfo");
ApplicationInfo info = (ApplicationInfo) appInfoFld.get(pkgParserPkg);
Class assetMagCls = Class.forName(PATH_AssetManager);
Constructor assetMagCt = assetMagCls.getConstructor((Class[]) null);
Object assetMag = assetMagCt.newInstance((Object[]) null);
typeArgs = new Class[1];
typeArgs[0] = String.class;
Method assetMag_addAssetPathMtd = assetMagCls.getDeclaredMethod("addAssetPath", typeArgs);
valueArgs = new Object[1];
valueArgs[0] = filePath;
assetMag_addAssetPathMtd.invoke(assetMag, valueArgs);
Resources res = ManagerActivity.this.getResources();
typeArgs = new Class[3];
typeArgs[0] = assetMag.getClass();
typeArgs[1] = res.getDisplayMetrics().getClass();
typeArgs[2] = res.getConfiguration().getClass();
Constructor resCt = Resources.class.getConstructor(typeArgs);
valueArgs = new Object[3];
valueArgs[0] = assetMag;
valueArgs[1] = res.getDisplayMetrics();
valueArgs[2] = res.getConfiguration();
res = (Resources) resCt.newInstance(valueArgs);
CharSequence label = null;
if (info.labelRes != 0) {
label = res.getText(info.labelRes);
}
if (info.icon != 0) {
dr = res.getDrawable(info.icon);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return dr;
}

Scaletype

ImageView的Scaletype决定了图片在View上显示时的样子
如进行何种比例的缩放,及显示图片的整体还是部分,等等。
设置的方式包括:
1. 在layout xml中定义Android:scaleType="CENTER"
2. 或在代码中调用imageView.setScaleType(ImageView.ScaleType.CENTER);
1. SetScaleType(ImageView.ScaleType.CENTER);
按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示
2. SetScaleType(ImageView.ScaleType.CENTER_CROP);
按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)
3. setScaleType(ImageView.ScaleType.CENTER_INSIDE);
将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽
4. setScaleType(ImageView.ScaleType.FIT_CENTER);
把图片按比例扩大/缩小到View的宽度,居中显示
5. FIT_START, FIT_END在图片缩放效果上与FIT_CENTER一样,只是显示的位置不同,FIT_START是置于顶部,FIT_CENTER居中,FIT_END置于底部。
在此就不给出示例了。
6. FIT_XY
不按比例缩放图片,目标是把图片塞满整个View。

No enclosing instance of type E is accessible.

Must qualify the allocation with an enclosing instance of type E(e.g. x.new A() where x is an instance of XXX).
静态方法不能直接调用动态方法。只有将某个内部类修饰为静态类,然后才能够在静态类中调用该类的成员变量与成员方法。可以去掉静态方法修饰或将类public class改为public static class

版本检测

public static UpdataInfo getUpdataInfo(InputStream is) throws Exception{
XmlPullParser parser = Xml.newPullParser();
parser.setInput(is, "utf-8");//设置解析的数据源
int type = parser.getEventType();
UpdataInfo info = new UpdataInfo();//实体
while(type != XmlPullParser.END_DOCUMENT ){
switch (type) {
case XmlPullParser.START_TAG:
if("version".equals(parser.getName())){
info.setVersion(parser.nextText()); //获取版本号
}else if ("url".equals(parser.getName())){
info.setUrl(parser.nextText()); //获取要升级的APK文件
}else if ("description".equals(parser.getName())){
info.setDescription(parser.nextText()); //获取该文件的信息
}
break;
}
type = parser.next();
}
return info;
}
public static File getFileFromServer(String path, ProgressDialog pd) throws Exception{
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
pd.setMax(conn.getContentLength());
InputStream is = conn.getInputStream();
File file = new File(Environment.getExternalStorageDirectory(), "updata.apk");
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024];
int len ;
int total=0;
while((len =bis.read(buffer))!=-1){
fos.write(buffer, 0, len);
total+= len;
pd.setProgress(total);
}
fos.close();
bis.close();
is.close();
return file;
}
else{
return null;
}
}
public class CheckVersionTask implements Runnable{
public void run() {
try {
String path = getResources().getString(R.string.serverurl);
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
InputStream is =conn.getInputStream();
info = UpdataInfoParser.getUpdataInfo(is);
if(info.getVersion().equals(versionname)){
Log.i(TAG,"版本号相同无需升级");
LoginMain();
}else{
Log.i(TAG,"版本号不同 ,提示用户升级 ");
Message msg = new Message();
msg.what = UPDATA_CLIENT;
handler.sendMessage(msg);
}
} catch (Exception e) {
Message msg = new Message();
msg.what = GET_UNDATAINFO_ERROR;
handler.sendMessage(msg);
e.printStackTrace();
}
}
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case UPDATA_CLIENT:
showUpdataDialog();
break;
case GET_UNDATAINFO_ERROR:
Toast.makeText(getApplicationContext(), "获取服务器更新信息失败", 1).show();
LoginMain();
break;
case DOWN_ERROR:
Toast.makeText(getApplicationContext(), "下载新版本失败", 1).show();
LoginMain();
break;
}
}
};
*
* 弹出对话框通知用户更新程序
*
* 弹出对话框的步骤:
* 1.创建alertDialog的builder.
* 2.要给builder设置属性, 对话框的内容,样式,按钮
* 3.通过builder 创建一个对话框
* 4.对话框show()出来
*/
protected void showUpdataDialog() {
AlertDialog.Builder builer = new Builder(this) ;
builer.setTitle("版本升级");
builer.setMessage(info.getDescription());
builer.setPositiveButton("确定", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG,"下载apk,更新");
downLoadApk();
}
});
builer.setNegativeButton("取消", new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
LoginMain();
}
});
AlertDialog dialog = builer.create();
dialog.show();
}
* 从服务器中下载APK
*/
protected void downLoadApk() {
final ProgressDialog pd; //进度条对话框
pd = new ProgressDialog(this);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage("正在下载更新");
pd.show();
new Thread(){
@Override
public void run() {
try {
File file = DownLoadManager.getFileFromServer(info.getUrl(), pd);
sleep(3000);
installApk(file);
pd.dismiss(); //结束掉进度条对话框
} catch (Exception e) {
Message msg = new Message();
msg.what = DOWN_ERROR;
handler.sendMessage(msg);
e.printStackTrace();
}
}}.start();
}
protected void installApk(File file) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
}
* 进入程序的主界面
*/
private void LoginMain(){
Intent intent = new Intent(this,MainActivity.class);
startActivity(intent);
this.finish();
}
public class UpdataInfo {
private String version;
private String url;
private String description;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
2.0 http://192.168.1.187:8080/mobilesafe.apk 检测到最新版本,请及时更新!

Android 分享功能

* 分享图片和文字内容
*
* @param dlgTitle
* 分享对话框标题
* @param subject
* 主题
* @param content
* 分享内容(文字)
* @param uri
* 图片资源URI
*/
private void shareImg(String dlgTitle, String subject, String content,
Uri uri) {
if (uri == null) {
return;
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, uri);
if (subject != null && !"".equals(subject)) {
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
}
if (content != null && !"".equals(content)) {
intent.putExtra(Intent.EXTRA_TEXT, content);
}
if (dlgTitle != null && !"".equals(dlgTitle)) { // 自定义标题
startActivity(Intent.createChooser(intent, dlgTitle));
} else { // 系统默认标题
startActivity(intent);
}
}

Activity生命周期

android.intent.category.LAUNCHER 决定应用程序是否显示在程序列表里
Activity主要的三种状态:
      Running(运行):在屏幕前台(位于当前任务堆栈的顶部)
      Paused(暂停):失去焦点但仍然对用户可见(覆盖Activity可能是透明或未完全遮挡)
      Stopped(停止):完全被另一个Activity覆盖
  二、事件方法链
    2.1  进入Activity
      onCreate -> onStart -> onResume
    2.2  BACK键
      onPause -> onStop -> onDestroy
    2.3  HOME键
      Home键退出:onPause -> onStop
      Home键回来:onRestart -> onStart -> onResume
    2.4  休眠/恢复
      休眠:  onPause
      恢复:  onResume
    2.5  旋转屏幕
      未设置android:configChanges:
        onPause -> onStop -> onDestory -> onCreate -> onStart -> onResume
      设置了android:configChanges="orientation|keyboardHidden":
    不会触发生命周期方法,参见文章这里。
    2.6  来电
      来电,显示来电界面:
        onPause -> onStop
      关闭电话界面,重新回到当前Activity:
        onRestart -> onStart -> onResume
    2.7  其他Activity
      进入下一个Activity:
        onPause -> onStop
      从其他Activity返回至当前Acitivity:
        onRestart -> onStart -> onResume
  三、与Activity生命周期结合的应用场景
    3.1  与广播(Broadcast)结合
      在onResume注册广播(registerLinstener),在onPause注销广播(unregisterLinstener)。 例如:
      做"摇一摇"功能(传感器)、监听网络变化,就可以在onResume中注册监听,在onPause里注销掉,已节省资源提高效率。
    3.2  与服务(Service)结合
      在onStart绑定服务(bindService),在onStop中取消绑定(unbindService)。 例如:
      需要通过Service定时更新UI上的数据,而Activity的可见周期在onStart与onStop之间,那么就可以再onStart时启动服务,在onStop时停止服务。为了节约系统资源,除了提高用户体验以外,开发人员应尽可能的优化程序。
    3.3  与Cursor结合
      使用managedQuery让Activity帮你管理Cursor的生命周期,不用自己去close。但也有一些问题,补充两篇文章:这里1、 这里2。
    3.4  释放资源
      可以在onDestory中释放一些资源。比如可以在onDestory时调用MediaPlayer的release。
  四、注意
    4.1  所有Activity生命周期方法的实现都必须先调用其父类版本。
    4.2  由于Activity经常会暂停和恢复之间切换,所以onResume和onPause这两个方法应当是轻量级的。
    4.3  在系统再某种紧急情况下需要回收内存,onStop、onDestory可能不会被调用,因此需要在onPause中把需要长期保存的数据保存起来。

Broadcast receivers介绍

Broadcast receivers 是对广播接收和回应的组件。系统会发出许多广播,比如时区的改变、电量过低,图片被选中等。应用也可生成广播,比如通知其他设备一些数据已经下载完成并且可以被使用。应用程序可以有多个Broadcast receivers 去接收和回应重要的广播。
所有的receivers 都必须继承BroadcastReceiver基类。Broadcast receivers 没有用户界面。但是可以启动一个界面作为对广播的回应,或者使用NotificationManager提示用户。
NotificationManager可以通过多种方式提示用户,比如闪烁背光灯,震动设备,播放提示音等等。手机状态条中会一直存在一个提示图标,用户可以打开它查看提示信息。

Broadcast receivers生命周期

一个Broadcast receiver有唯一的回调方法:
void onReceive(ContextcurContext, Intent broadcastMsg)
当接收到广播消息,android会调用onReceive()方法,同时传递Intent对象。Broadcast receiver只在执行onReceive()方法是活动状态,当方法执行结束会进入非活动状态。
当一个进程中含有活动状态的Broadcast receiver,系统会保护它不被结束。但是进程中只有非活动状态的组件,当其他进程需求内存使,它可能会系统在任何时间结束。
当对广播的响应是一个耗时的操作需要在独立的线程中完成。换句话说,如果onReceive()产生了线程,那么整个进程包括新创建的线程都会认为是非活动状态(除非应用还有其他活动状态的组件),进入随时被结束的危险状态。解决的办法是开启一个Service完成所有工作,这样做系统会认为应用仍然处在活动状态。
特别需要注意的是你不能在onReceive()方法中显示一个dialog或者bind一个service。对于前者,你应该使用Notification API。对于后者,你可以使用Context.startService()。

BroadcastReceiver类

它是个基类用来接收sendBroadcast()发出的intent。你可以使用两种方式注册一个BroadcastReceiver
1、通过Context.registerReceiver()动态的注册一个该类的实例,
2、在AndroidManifest.xml中实现<receiver>标签静态的注册。
你可以在Activiy.onResume()中注册,在Activity.onPause移除注册,或者在Activity.onStart()中注册,在Activity.onStop()中移除注册。不要在Activity.onSaveInstanceState()中国移除注册,因为用户在返回历史栈时它不会调用。
在AndroidManifest中注册BroadcastReceiver和在代码中注册BroadcastReceiver效果都是一样的,该BroadcastReceiver都会生效.
但是区别在于在代码中用registerReceiver方法注册后,若对其进行注册的Context对象"销毁"了或者调用了unregisterReceiver方法,注册的BroadcastReceiver也就失效了.
而在AndroidManifest中注册,只要安装的应用没有被删除,BroadcastReceiver一直都有效.
接收的两种主要广播:
1.正常广播(以Context,sendBroadcast发送的)是完全异步的。所有接收者在接收时没有指定的顺序,通常是同事接收到广播的。它是高效的,但是意味着接受者不能使用返回的结果。
2.含有顺序的广播(以Context,sendOrderedBroadcast发送的)是每次发送给一个接受者的。每次接受者顺序接收。它可以传递结果给下一个接受者,或者它可以废除这个广播不传给任何接受者。这些顺序接受者可以使用android:priority属性去匹配intent-filter,相同优先级的接受者会以随意的顺序接收。
注意:尽管发送和接受广播使用intent,但是intent广播机制与使用intent启动Activity(Context.startActivity())是完全不同的。你无法接受或捕获用于启动Activity的intent。使用intent开启Activity是一种改变用户正在执行对象的显示操作,而广播intent通常是用户无法意识到的后台操作。

Android中Activity启动模式详解

  在Android中每个界面都是一个Activity,切换界面操作其实是多个不同Activity之间的实例化操作。在Android中Activity的启动模式决定了Activity的启动运行方式。
  Android总Activity的启动模式分为四种:
Activity启动模式设置:
Activity的四种启动模式:
. standard
模式启动模式,每次激活Activity时都会创建Activity,并放入任务栈中。
. singleTop
如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。
. singleTask
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。
. singleInstance
在一个新栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
  其中standard是系统默认的启动模式。
  下面通过实例来演示standard的运行机制:
private TextView text_show;
private Button btn_mode;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text_show = (TextView) this.findViewById(R.id.text_show);
text_show.setText(this.toString());
btn_mode = (Button) this.findViewById(R.id.btn_mode);
}
    //按钮单击事件
public void LaunchStandard(View v){
startActivity(new Intent(this,MainActivity.class));
text_show.setText(this.toString());
}
这种Standard模式是每次都会创建新的Activity对象,当点击返回按钮时,他会将栈顶(当前Activity)消灭,然后跳到下一层,例如如果现在Activity是44ed8c50,那么当我们点击返回时Activity会变为44f28a48,不过此时在这个Activity中再次点击按钮创建对象时,它会另外创建新的Activity对象,这种模式可能大多数情况下不是我们需要的,因为对系统性能的消耗过大。
  下面我们介绍两种能使用当前栈中Activity的启动模式:
  2. singleTop
    从上面的解释中即可知道,在每次使用新的Activity时会自动检测栈顶的当前Activity是否是需要引用的Activity,如果是则直接引用此Activity,而不会创建新的Activity。
    我们在刚才的界面中加入一个"启动singletop模式"按钮,当点击时出现我们创建的singletop中,在Activity singletop中有一个按钮,启动singletop模式,表示启动当前Activity,由于我们在清单文件中配置Activity的启动模式为singleTop,因此此时不会再创建而是利用当前栈顶的singleTop Activity:
    <activity
android:name=".SingleTopActivity"
android:label="@string/singletop"
android:launchMode="singleTop" >
</activity>
    界面初始化:
      
    点击"启动singleTop模式"按钮:
          
  我们分析它的运行机制,可知,当程序运行到此时,栈中的数据形式为:
    
    当我们在上面界面中点击"启动singleTop模式"按钮时,由于此Activity设置的启动模式为singleTop,因此它首先会检测当前栈顶是否为我们要请求的Activity对象,经验证成立,因此它不会创建新的Activity,而是引用当前栈顶的Activity。
      
    虽然它不会创建新的Activity对象,不过它每次回调用onNewIntent()方法:
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Toast.makeText(this, new Date().toString(), 1).show();
}
    我们为此方法编写代码输出当前日期,则在每次点击上面按钮时会输出当前日期。
  3.singleTask
    此启动模式和singleTop在名字上即可看出区别,即singleTop每次只检测当前栈顶的Activity是否是我们需要请求创建的,而singleTask则会检测栈中全部的Activity对象,从上向下,如果检测到是我们所请求的则会消灭此Activity对象上面的对象,直接把检测到的我们需要的Activity置为栈顶。
    我们创建一个SingleTaskActivity,此界面中包含一个启动MainActivity和启动SingleTaskActivity按钮。
  初始化:
    
  点击"启动singleTask模式"按钮:
    
  在此界面中点击第二个按钮"启动singleTask模式"按钮,根据定义会检测当前栈中是否有此Activity对象,因此显示的还是当前的Activity,不会重新创建;
  再点击"启动Standard模式"按钮,由于MainActivity的启动模式为standard,所以在此会重新创建一个MainActivity对象:
    
  此时栈中数据格式为:
    
  当在上面界面中点击"启动singleTask模式"按钮时,由于检测到当期栈中第二个为我们要创建的Activity,会将最上面的MainActivity消灭,然后将SingleTaskActivity设置为栈顶:
    
  4.SingleInstance
    此启动模式和我们使用的浏览器工作原理类似,我们都知道在多个程序中访问浏览器时,如果当前浏览器没有打开,则打开浏览器,否则会在当前打开的浏览器中访问。此模式会节省大量的系统资源,因为他能保证要请求的Activity对象在当前的栈中只存在一个。
    
    上面即为Android中的四种启动模式,我们在开发Android项目时会经常使用到,巧妙设置Activity的启动模式会节省系统开销和程序运行效率。

Activity启动界面

示例

SplashActivity.java代码:
package com.app.splashactivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.Window;
import android.view.WindowManager;
public class SplashActivity extends Activity {
private final int SPLASH_DISPLAY_LENGHT = 6000; // 延迟六秒
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); //去掉标题
getWindow().setFlags(WindowManager.LayoutParams.TYPE_STATUS_BAR, WindowManager.LayoutParams.TYPE_STATUS_BAR); //全屏
setContentView(R.layout.splash);
new Handler().postDelayed(new Runnable() {
public void run() {
Intent mainIntent = new Intent(SplashActivity.this,
MainActivity.class);
SplashActivity.this.startActivity(mainIntent);
SplashActivity.this.finish();
}
}, SPLASH_DISPLAY_LENGHT);
}
}
AndroidManifest代码:
注意在AndroidManifest设置SplashActivity为初始启动Activity。

Android PopupWindow的用法

androidlayoutbuttonbtencodingclass
一个PopupWindow能显示任意的View。 PopupWindow是漂浮在其他当前Activity之上显示的容器。
demo代码如下:
[java] view plaincopy
public class TestPopupWindow extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bt_disPW = (Button) findViewById(R.id.disPW);
bt_disPW.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.d("lxy", "at TestActivity button click event");
LinearLayout mLayout = new LinearLayout(TestPopupWindow.this);
TextView tv = new TextView(TestPopupWindow.this);
tv.setTextColor(Color.BLACK);
tv.setText("test popup window");
mLayout.addView(tv);
PopupWindow ppw = new PopupWindow(mLayout, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
ppw.setBackgroundDrawable(new ColorDrawable(Color.GRAY));// 设置背景
ppw.setFocusable(true);
ppw.getBackground().setAlpha(60);
ppw.showAtLocation(findViewById(R.id.disPW), Gravity.CENTER_VERTICAL, 0, 0);
}
});
}
private Button bt_disPW;
}
main.xml
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#FF5555FF"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
<Button
android:text="DispaleyPopupWindow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/disPW"></Button>
</LinearLayout>

Android Activity 后台运行方案

1. 重新定义返回键, Home键,直接显示HOME,使应用隐藏
public boolean onKeyDown(int keyCode,KeyEvent event){
switch(keyCode){
case KeyEvent.KEYCODE_HOME:;
case KeyEvent.KEYCODE_BACK:
{
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
return true;
}
return super.onKeyDown(keyCode, event);
}
2. 将后台操作放在一服务中并启动

1. Android 隐藏应用方法

(1) 显示Home方法
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
(2) 屏蔽Keycode_Back,让它不结束(finish())Activity,直接显示HOME界面。
PackageManager pm = getPackageManager();
ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME), 0);
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
ActivityInfo ai = homeInfo.activityInfo;
Intent startIntent = new Intent(Intent.ACTION_MAIN);
startIntent.addCategory(Intent.CATEGORY_LAUNCHER);
startIntent.setComponent(new ComponentName(ai.packageName, ai.name));
startActivitySafely(startIntent);
return true;
} else
return super.onKeyDown(keyCode, event);
}
void startActivitySafely(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.unabletoopensoftware, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(this, R.string.unabletoopensoftware, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Launcher does not have the permission to launch. Make sure to create a MAIN intent-filterfor the corresponding activity, "+
"or use the exported attribute for this activity.");
}
}

Android 自定义实现Home键和返回键操作

按 返回键 分别执行: onPause() onStop() onDestory() 方法
按 Home键 则只执行: onPause() onStop() 方法
1.实现home键
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 注意,这个地方最重要,关于解释,自己google吧
intent.addCategory(Intent.CATEGORY_HOME);
this.startActivity(intent);
2.实现返回键
1)监听返回键动作
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
AlertDialog.Builder mDialog = new AlertDialog.Builder(
locResource.this);
mDialog.setTitle("操作提示");
mDialog.setMessage("确定退出吗?");
mDialog.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
});
mDialog.setNegativeButton("取消", null);
mDialog.show();
}
return super.onKeyDown(keyCode, event);
}
2)自己写按钮实现方法
AlertDialog.Builder mDialog = new AlertDialog.Builder(mainActivity.this);
mDialog.setTitle("退出");
mDialog.setMessage("确定要退出吗?");
mDialog.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
});
mDialog.setNegativeButton("取消", null);
mDialog.show();

屏蔽返回键和home键

(1) Activity屏蔽返回键

Java代码
public boolean onKeyDown(int keyCode,KeyEvent event){
switch(keyCode){
case KeyEvent.KEYCODE_HOME:return true;
case KeyEvent.KEYCODE_BACK:return true;
case KeyEvent.KEYCODE_CALL:return true;
case KeyEvent.KEYCODE_SYM: return true;
case KeyEvent.KEYCODE_VOLUME_DOWN: return true;
case KeyEvent.KEYCODE_VOLUME_UP: return true;
case KeyEvent.KEYCODE_STAR: return true;
}
return super.onKeyDown(keyCode, event);
}

(2) Activity屏蔽home键

Java代码
public void onAttachedToWindow() {
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
super.onAttachedToWindow();
}

(3) Activity屏蔽其他键,重写onKeyDown

Java代码
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.i(TAG,"keycode="+keyCode + " isBan="+isBan);
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
Log.i(TAG,"KEYCODE_BACK");
return true;
}
return super.onKeyDown(keyCode, event);
}
在这里屏蔽Home键是捕捉不到的,因为大家的权限一般是User,所以是无效的。
Android处理Home键等系统级按键是有一定的处理的。
查看一下源码 \frameworks\policies\base\phone\com\Android\internal\policy\impl\PhoneWindowManager.java #1092
Java代码
if (code == KeyEvent.KEYCODE_HOME) {
WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
if (attrs != null) {
final int type = attrs.type;
if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
|| type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
return false;
}
final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
for (int i=0; i<typeCount; i++) {
if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
return true;
}
}
}
通过源码,我们不难发现两个的参数 WindowManager.LayoutParams.TYPE_KEYGUARD和
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
借鉴于此,重写onAttachedToWindow,以实现屏蔽Home键
public void onAttachedToWindow() {
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
super.onAttachedToWindow();
}

Dialog 屏蔽按键方法

如果在Activity弹出dialog,在Activity设置以上方法是没办法屏蔽的,原理是一样的,只是地方不一样而已。
Java代码
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.mydailog);
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
dialog.show();
dialog.setOnKeyListener(new Android.content.DialogInterface.OnKeyListener(){
@Override
public boolean onKey(DialogInterface dialog, int keyCode,KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
Log.i(TAG,"KEYCODE_BACK");
return true;
}
return false;
}
});
这样运行后,出错如下:
Error代码
1.10-18 13:27:06.380: ERROR/AndroidRuntime(4684): Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRoot$W@2b046d68 -- permission denied for this window type
其实,只需要把dialog.getWindow().setType的位置放在show后面就可以了
正确代码
dialog.show();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);

总结

1:) 在以上用WindowManager.LayoutParams.TYPE_KEYGUARD的地方改用WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG 效果一样
2:) 在源码里其实是这样调用的
Java代码
final AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(null)
.setMessage(message)
.setNeutralButton(R.string.ok, null)
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.show();
但我们如果这样调用就会出现之前的那个error:permission denied for this window type
3:)ProgressDialog 默认屏蔽 Back键,Dialog,AlertDialog则需setOnKeyListener
4:)屏蔽Home键,在页面的某个地方,例如一个Button的onClick里,去设置setType也可以,如:
Java代码
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
}
});
但前提是重载Activity的onAttachedToWindow(),哪怕只是一个空实现,然后返回父类方法。
Java代码
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
}
5:) 常用按健
Java代码
switch (keyCode) {
case KeyEvent.KEYCODE_HOME:
Log.i(TAG,"KEYCODE_HOME");
return true;
case KeyEvent.KEYCODE_BACK:
Log.i(TAG,"KEYCODE_BACK");
return true;
case KeyEvent.KEYCODE_CALL:
Log.i(TAG,"KEYCODE_CALL");
return true;
case KeyEvent.KEYCODE_SYM:
Log.i(TAG,"KEYCODE_SYM");
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
Log.i(TAG,"KEYCODE_VOLUME_DOWN");
return true;
case KeyEvent.KEYCODE_VOLUME_UP:
Log.i(TAG,"KEYCODE_VOLUME_UP");
return true;
case KeyEvent.KEYCODE_STAR:
Log.i(TAG,"KEYCODE_STAR");
return true;
}

Android程序退出方法

在Android中退出程序比较麻烦,尤其是在多个Activity的程序中,
在2.2之前可以采用如下代码退出程序:
Java代码
ActivityManager am = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
am.restartPackage(getPackageName());
ActivityManager am = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
am.restartPackage(getPackageName());
此种方法是一种最方便和最简单的退出程序的办法,但是在2.2和2.2之后就不能用了,要退出程序有以下3种办法:
(1) 采用content view栈:如果程序是多界面,但是又没有强制要求每一个界面一个Activity,可以将每个界面设计为一个View,
在界面切换时,只需要调用Activity的setContentView方法设置Activity的Contentview,这样退出程序只需要将这一个Activity退出 就可以了,但是需要设计一个栈来管理content view。
(2) 可以自定义一个Activity的栈,在程序退出时将栈中的所有的Activity进行finish
这两种方法的精髓之处都是需要自己设计一个栈用来管理界面或者Activity,这样程序就比较复杂一些。
(3) 第3种方法就是,先让程序到Home界面,然后再将process杀死:代码如下:
Java代码
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
android.os.Process.killProcess(Process.myPid());
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
android.os.Process.killProcess(Process.myPid());4.还有一种就是使用方法是使用Android的Broadcast机制。在所有的Activity中注册退出程序的消息,当收到消息时调用finish方法。 然后再有退出程序功能的Activity上广播关闭消息。代码如下:
Java代码
package com.kingtone.activity;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
* 所有Activity的父类,用来注册退出程序的广播事件,
* 并且对收到的退出程序事件进行处理
* @author Administrator
*
*/
public class CommonActivity extends Activity {
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
@Override
public void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(GlobalVarable.EXIT_ACTION);
this.registerReceiver(this.broadcastReceiver, filter);
}
}
package com.kingtone.activity;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
* 所有Activity的父类,用来注册退出程序的广播事件,
* 并且对收到的退出程序事件进行处理
* @author Administrator
*
*/
public class CommonActivity extends Activity {
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
@Override
public void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(GlobalVarable.EXIT_ACTION);
this.registerReceiver(this.broadcastReceiver, filter);
}
}
在需要退出程序的Activity(CommonActivity的子类)中,退出程序代码如下:
Java代码
Intent intent = new Intent();
intent.setAction(GlobalVarable.EXIT_ACTION); // 退出动作
this.sendBroadcast(intent);// 发送广播
super.finish();
System.exit(0);

finish, onDestroy, exit区别

(1) Activity.finish()

Call this when your activity is done and should be closed.
在你的activity动作完成的时候,或者Activity需要关闭的时候,调用此方法。
当你调用此方法的时候,系统只是将最上面的Activity移出了栈,并没有及时的调用onDestory()方法,其占用的资源也没有被及时释放。
因为移出了栈,所以当你点击手机上面的“back”按键的时候,也不会再找到这个Activity。
finish函数仅仅把当前Activity退出了,但是并没有释放他的资源。安卓系统自己决定何时从内存中释放应用程序。当系统没有可用内存到时候,会按照优先级,释放部分应用。

Activity.onDestory()

the system is temporarily destroying this instance of the activity to save space.
系统销毁了这个Activity的实例在内存中占据的空间。
在Activity的生命周期中,onDestory()方法是他生命的最后一步,资源空间等就被回收了。当重新进入此Activity的时候,必须重新创建,执行onCreate()方法。

System.exit(0)和android.os.Process.killProcess(android.os.Process.myPid())

这是退出整个应用程序,是针对整个Application的,将整个进程直接Kill掉,并释放资源。

强制退出应用释放资源方法

finish是Activity的类,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理;当调用System.exit(0)时,杀死了整个进程,这时候活动所占的资源也会被释放。
在开发android应用时,常常通过按返回键(即keyCode == KeyEvent.KEYCODE_BACK)就能关闭程序,其实大多情况下该应用还在任务里运行着,其实这不是我们想要的结果。我们可以这样做,当用户点击自定义的退出按钮或返回键时(需要捕获动作),我们在onDestroy()里强制退出应用,或直接杀死进程。
退出对话框示例
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
new AlertDialog.Builder(this)
.setIcon(R.drawable.services)
.setTitle(R.string.prompt)
.setMessage(R.string.quit_desc)
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
finish();
}
}).show();
return true;
}else{
return super.onKeyDown(keyCode, event);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
System.exit(0);
}

背景设置

设置透明背景方法

1. setTheme(Android.R.style.Theme_Dialog);
背景是黑色。
若在Activity重写onAttachedToWindow
Java代码
public void onAttachedToWindow() {
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
super.onAttachedToWindow();
}
出来的效果,就是透明背景的dialog了,前提是需要实现屏蔽Home键。

方法一:

只要在配置文件内activity属性配置内加上
android:theme="@android:style/Theme.Translucent"
就好了。
这样就调用了android的透明样式!

方法二:

先在res/values下建colors.xml文件,写入:
  
  <?xmlversion="1.0"encoding="UTF-8"?>
  
  <resources>
  
  <colorname="transparent">#9000</color>
  
  </resources>
  
  这个值设定了整个界面的透明度,为了看得见效果,现在设为透明度为56%(9/16)左右。
  
  再在res/values/下建styles.xml,设置程序的风格
  
  <?xmlversion="1.0"encoding="utf-8"?>
  
  <resources>
  
  <stylename="Transparent">
  
  <itemname="android:windowBackground">@color/transparent</item>
  
  <itemname="android:windowIsTranslucent">true</item>
  
  <itemname="android:windowAnimationStyle">@+android:style/Animation.Translucent</item>
  
  </style>
  
  </resources>
  
  最后一步,把这个styles.xml用在相应的Activity上。即在AndroidManifest.xml中的任意<activity>标签中添加
  
  android:theme="@style/transparent"
  
  如果想设置所有的activity都使用这个风格,可以把这句标签语句添加在<application>中。
  
整个界面都被蒙上一层半透明了。最后可以把背景色#9000换成#0000,运行程序后,就全透明了,看得见背景下的所有东西但操作无效。

WindowManager.LayoutParams详解

WindowManager.LayoutParams 是 WindowManager 接口的嵌套类;继承于 ViewGroup.LayoutParams 。
它的内容十分丰富。其实WindowManager.java的主要内容就是由这个类定义构成。下面来分析一下这个类:
定义
public static class WindowManager.LayoutParams extends ViewGroup.LayoutParams implements Parcelable
继承关系
java.lang.Object
Android.view.ViewGroup.LayoutParams
Android.view.WindowManager.LayoutParams
继承来的属性与常量
从 ViewManager.LayoutParams 继承来的属性:
Android:layout_height
Specifies the basic height of the view.
Android:layout_width
Specifies the basic width of the view.
从 ViewManager.LayoutParams继承的常量:
FILL_PARENT
WRAP_CONTENT
MATCH_PARENT
两个变量:
width
height
属性及可用的常量定义
1. public int x;
如果忽略gravity属性,那么它表示窗口的绝对X位置。
什么是gravity属性呢?简单地说,就是窗口如何停靠。
当设置了 Gravity.LEFT 或 Gravity.RIGHT 之后,x值就表示到特定边的距离。
2. public int y;-
如果忽略gravity属性,那么它表示窗口的绝对Y位置。
当设置了 Gravity.TOP 或 Gravity.BOTTOM 之后,y值就表示到特定边的距离。
3. public float horizontalWeight;
public float verticalWeight;
在纵/横向上,为关联的view预留了多少扩展空间(像素)。如果是0,那么此view不能被拉伸。
其他情况下,扩展空间(像素)将被widget所均分。
4. public int type;
窗口类型。
有3种主要类型:
Applicationwindows:
取值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之间。
是通常的、顶层的应用程序窗口。必须将 token 设置成 activity 的 token 。
Sub_windows:
取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间。
与顶层窗口相关联,token 必须设置为它所附着的宿主窗口的 token。
Systemwindows:
取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之间。
用于特定的系统功能。它不能用于应用程序,使用时需要特殊权限。
下面定义了 type 的取值:
应用程序窗口。
public static final int FIRST_APPLICATION_WINDOW = 1;
所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。
public static final int TYPE_BASE_APPLICATION =1;
普通应用功能程序窗口。token必须设置为Activity的token,以指出该窗口属谁。
public static final int TYPE_APPLICATION = 2;
用于应用程序启动时所显示的窗口。应用本身不要使用这种类型。
它用于让系统显示些信息,直到应用程序可以开启自己的窗口。
public static final int TYPE_APPLICATION_STARTING = 3;
应用程序窗口结束。
public static final int LAST_APPLICATION_WINDOW = 99;
子窗口。子窗口的Z序和坐标空间都依赖于他们的宿主窗口。
public static final int FIRST_SUB_WINDOW = 1000;
面板窗口,显示于宿主窗口上层。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
媒体窗口,例如视频。显示于宿主窗口下层。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
应用程序窗口的子面板。显示于所有面板窗口的上层。(GUI的一般规律,越“子”越靠上)
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW +2;
对话框。类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口。
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW +3;
媒体信息。显示在媒体层和程序窗口之间,需要实现透明(半透明)效果。(例如显示字幕)
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW +4;
子窗口结束。( End of types of sub-windows )
public static final int LAST_SUB_WINDOW = 1999;
系统窗口。非应用程序创建。
public static final int FIRST_SYSTEM_WINDOW = 2000;
状态栏。只能有一个状态栏;它位于屏幕顶端,其他窗口都位于它下方。
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
搜索栏。只能有一个搜索栏;它位于屏幕上方。
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
电话窗口。它用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
系统提示。它总是出现在应用程序窗口之上。
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW +3;
锁屏窗口。
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW +4;
信息窗口。用于显示toast。
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW +5;
系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏。
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW +6;
电话优先,当锁屏时显示。此窗口不能获得输入焦点,否则影响锁屏。
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW +7;
系统对话框。(例如音量调节框)。
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW +8;
锁屏时显示的对话框。
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW +9;
系统内部错误提示,显示于所有内容之上。
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW +10;
内部输入法窗口,显示于普通UI之上。应用程序可重新布局以免被此窗口覆盖。
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW +11;
内部输入法对话框,显示于当前输入法窗口之上。
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW +12;
墙纸窗口。
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW +13;
状态栏的滑动面板。
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW +14;
系统窗口结束。
public static final int LAST_SYSTEM_WINDOW = 2999;
5. public int memoryType;
指出窗口所使用的内存缓冲类型。默认为 NORMAL 。
下面定义了 memoryType 的取值:
窗口缓冲位于主内存。
public static final int MEMORY_TYPE_NORMAL = 0;
窗口缓冲位于可以被DMA访问,或者硬件加速的内存区域。
public static final int MEMORY_TYPE_HARDWARE = 1;
窗口缓冲位于可被图形加速器访问的区域。
public static final int MEMORY_TYPE_GPU = 2;
窗口缓冲不拥有自己的缓冲区,不能被锁定。缓冲区由本地方法提供。
public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;
6. public int flags;
行为选项/旗标,默认为 none .
下面定义了 flags 的取值:
窗口之后的内容变暗。
public static final int FLAG_DIM_BEHIND = 0x00000002;
窗口之后的内容变模糊。
public static final int FLAG_BLUR_BEHIND = 0x00000004;
不许获得焦点。
不能获得按键输入焦点,所以不能向它发送按键或按钮事件。那些时间将发送给它后面的可以获得焦点的窗口。此选项还会设置FLAG_NOT_TOUCH_MODAL选项。设置此选项,意味着窗口不能与软输入法进行交互,所以它的Z序独立于任何活动的输入法(换句话说,它可以全屏显示,如果需要的话,可覆盖输入法窗口)。要修改这一行为,可参考FLAG_ALT_FOCUSALBE_IM选项。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
不接受触摸屏事件。
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
当窗口可以获得焦点(没有设置 FLAG_NOT_FOCUSALBE 选项)时,仍然将窗口范围之外的点设备事件(鼠标、触摸屏)发送给后面的窗口处理。否则它将独占所有的点设备事件,而不管它们是不是发生在窗口范围内。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
如果设置了这个标志,当设备休眠时,点击触摸屏,设备将收到这个第一触摸事件。
通常第一触摸事件被系统所消耗,用户不会看到他们点击屏幕有什么反应。
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
当此窗口为用户可见时,保持设备常开,并保持亮度不变。
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
窗口占满整个屏幕,忽略周围的装饰边框(例如状态栏)。此窗口需考虑到装饰边框的内容。
public static final int FLAG_LAYOUT_IN_SCREEN =0x00000100;
允许窗口扩展到屏幕之外。
public static final int FLAG_LAYOUT_NO_LIMITS =0x00000200;
窗口显示时,隐藏所有的屏幕装饰(例如状态条)。使窗口占用整个显示区域。
public static final int FLAG_FULLSCREEN = 0x00000400;
此选项将覆盖FLAG_FULLSCREEN选项,并强制屏幕装饰(如状态条)弹出。
public static final int FLAG_FORCE_NOT_FULLSCREEN =0x00000800;
抖动。指 对半透明的显示方法。又称“点透”。图形处理较差的设备往往用“点透”替代Alpha混合。
public static final int FLAG_DITHER = 0x00001000;
不允许屏幕截图。
public static final int FLAG_SECURE = 0x00002000;
一种特殊模式,布局参数用于指示显示比例。
public static final int FLAG_SCALED = 0x00004000;
当屏幕有可能贴着脸时,这一选项可防止面颊对屏幕造成误操作。
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
当请求布局时,你的窗口可能出现在状态栏的上面或下面,从而造成遮挡。当设置这一选项后,窗口管理器将确保窗口内容不会被装饰条(状态栏)盖住。
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
反转FLAG_NOT_FOCUSABLE选项。
如果同时设置了FLAG_NOT_FOCUSABLE选项和本选项,窗口将能够与输入法交互,允许输入法窗口覆盖;
如果FLAG_NOT_FOCUSABLE没有设置而设置了本选项,窗口不能与输入法交互,可以覆盖输入法窗口。
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
如果你设置了FLAG_NOT_TOUCH_MODAL,那么当触屏事件发生在窗口之外事,可以通过设置此标志接收到一个MotionEvent.ACTION_OUTSIDE事件。注意,你不会收到完整的down/move/up事件,只有第一次down事件时可以收到ACTION_OUTSIDE。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
当屏幕锁定时,窗口可以被看到。这使得应用程序窗口优先于锁屏界面。可配合FLAG_KEEP_SCREEN_ON选项点亮屏幕并直接显示在锁屏界面之前。可使用FLAG_DISMISS_KEYGUARD选项直接解除非加锁的锁屏状态。此选项只用于最顶层的全屏幕窗口。
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
请求系统墙纸显示在你的窗口后面。窗口必须是半透明的。
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
窗口一旦显示出来,系统将点亮屏幕,正如用户唤醒设备那样。
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
解除锁屏。只有锁屏界面不是加密的才能解锁。如果锁屏界面是加密的,那么用户解锁之后才能看到此窗口,除非设置了FLAG_SHOW_WHEN_LOCKED选项。
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
锁屏界面淡出时,继续运行它的动画。
public static final int FLAG_KEEP_SURFACE_WHILE_ANIMATING =0x10000000;
以原始尺寸显示窗口。用于在兼容模式下运行程序。
public static final int FLAG_COMPATIBLE_WINDOW = 0x20000000;
用于系统对话框。设置此选项的窗口将无条件获得焦点。
public static final int FLAG_SYSTEM_ERROR = 0x40000000;
7. public int softInputMode;
软输入法模式选项:
以下选项与 softInputMode 有关:
软输入区域是否可见。
public static final int SOFT_INPUT_MASK_STATE = 0x0f;
未指定状态。
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
不要修改软输入法区域的状态。
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
隐藏输入法区域(当用户进入窗口时)。
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
当窗口获得焦点时,隐藏输入法区域。
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
显示输入法区域(当用户进入窗口时)。
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
当窗口获得焦点时,显示输入法区域。
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
窗口应当主动调整,以适应软输入窗口。
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
未指定状态,系统将根据窗口内容尝试选择一个输入法样式。
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
当输入法显示时,允许窗口重新计算尺寸,使内容不被输入法所覆盖。
不可与SOFT_INPUT_ADJUSP_PAN混合使用,如果两个都没有设置,系统将根据窗口内容自动设置一个选项。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
输入法显示时平移窗口。它不需要处理尺寸变化,框架能够移动窗口以确保输入焦点可见。
不可与SOFT_INPUT_ADJUST_RESIZE混合使用;如果两个都没设置,系统将根据窗口内容自动设置一个选项。
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
当用户转至此窗口时,由系统自动设置,所以你不要设置它。
当窗口显示之后该标志自动清除。
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
8. public int gravity;
gravity 属性。什么是gravity属性呢?简单地说,就是窗口如何停靠。
9. public float horizontalMargin;
水平边距,容器与widget之间的距离,占容器宽度的百分率。
10. public float verticalMargin;
纵向边距。
11. public int format;
期望的位图格式。默认为不透明。参考Android.graphics.PixelFormat。
12. public int windowAnimations;
窗口所使用的动画设置。它必须是一个系统资源而不是应用程序资源,因为窗口管理器不能访问应用程序。
13. public float alpha = 1.0f;
整个窗口的半透明值,1.0表示不透明,0.0表示全透明。
14. public float dimAmount = 1.0f;
当FLAG_DIM_BEHIND设置后生效。该变量指示后面的窗口变暗的程度。
1.0表示完全不透明,0.0表示没有变暗。
15. public float screenBrightness = -1.0f;
用来覆盖用户设置的屏幕亮度。表示应用用户设置的屏幕亮度。
从0到1调整亮度从暗到最亮发生变化。
16. public IBinder token = null;
窗口的标示符。( Identifier for this window. This will usually be filled in for you. )
17. public String packageName = null;
此窗口所在的包名。
18. public int screenOrientation =ActivityInfo.SCREEN_ORIENTATION_ b UNSPECIFIED;
屏幕方向,参见Android.content.pm.ActivityInfo#screenOrientation。
19. 在兼容模式下,备份/恢复参数所使用的内部缓冲区。
public int[] mCompatibilityParamsBackup = null;

Android实现程序前后台切换效果

先看下Android中Activities和Task的基础知识。
  我们都知道,一个Activity 可以启动另一个Activity,即使这个Activity是定义在别一个应用程序里的,比如说,想要给用户展示一个地图的信息,现在已经有一个Activity可以做这件事情,那么现在你的Activity需要做的就是将请求信息放进一个Intent对象里,并且将这个Intent对象传递给startActivity(),那么地图就可显示出来了,但用户按下Back键之后,你的Activity又重新出现在屏幕上。
  对用户来讲,显示地图的Activity和你的Activity好像在一个应用程序中的,虽然是他们是定义在其他的应用程序中并且运行在那个应有进程中。Android将你的Activity和借用的那个Activity被放进一个Task中以维持用户的体验。那么Task是以栈的形式组织起来一组相互关联的Activity,栈中底部的Activity就是开辟这个Task的,通常是用户在应用程序启动器中选择的Activity。栈的顶部的Activity是当前正在运行的Activity--用户正在交互操作的Activity。
  当一个Activity启动另一个Activity时,新启动的Activity被压进栈中,成为正在运行的Activity。旧的Activity仍然在栈中。当用户按下BACK键之后,正在运行的Activity弹出栈,旧的Activity恢复成为运行的Activity。栈中包含对象,因此如果一个任务中开启了同一个Activity子类的的多个对象——例如,多个地图浏览器——则栈对每一个实例都有一个单独的入口。栈中的Activity不会被重新排序,只会被、弹出。Task是一组Activity实例组成的栈,不是在manifest文件里的某个类或是元素,所以无法设定一个Task的属性而不管它的Activity,一个Task的所有属性值是在底部的Activity里设置的,这就需要用于Affinity。关于Affinity这里不再详述,大家可以查询文档。
  一个Task里的所有Activity作为一个整体运转。整个Task(整个Activity堆栈)可以被推到前台或被推到后台。假设一个正在运行的Task中有四个Activity——正在运行的Activity下面有三个Activity,这时用户按下HOME键,回到应有程序启动器然后运行新的应用程序(实际上是运行了一个新的Task),那么当前的Task就退到了后台,新开启的应用程序的root Activity此时就显示出来了,一段时间后,用户又回到应用程序器,又重新选择了之前的那个应用程序(先前的那个Task),那么先前的那个Task此时又回到了前台了,当用户按下BACK键时,屏幕不是显示刚刚离开的那个新开启的那个应用程序的Activity,而是被除回到前台的那个Task的栈顶Activity,将这个Task的下一个Activity显示出来。 上述便是Activity和Task一般的行为,但是这个行为的几乎所有方面都是可以修改的。Activity和Task的关系,以及Task中Activity的行为,是受启动该Activity的Intent对象的标识和在manifest文件中的Activity的<Activity>元素的属性共同影响的。
  以上是关于Activity和Task的描述。
  在开发Android项目时,用户难免会进行程序切换,在切换过程中,程序将进入后台运行,需要用时再通过任务管理器或是重新点击程序或是通过点击信息通知栏中的图标返回原来的界面。这种效果类似于腾讯QQ的效果,打开QQ后显示主界面,在使用其他的程序时,QQ将以图标的形式显示在信息通知栏里,如果再用到QQ时再点击信息通知栏中的图标显示QQ主界面。
对于这种效果一般的做法是在Activity中的onStop()方法中编写相应代码,因为当Activity进入后台时将会调用onStop()方法,我们可以在onStop()方法以Notification形式显示程序图标及信息,其中代码如下所示:
  @Override
protected void onStop() {
  super.onStop();
  Log.v("BACKGROUND", "程序进入后台");
  showNotification();
}
  以上的showNotification()方法就是Notification。
  然后点击信息通知栏的Notification后再返回到原来的Activity。
  当然,我们也可以捕捉HOME键,在用户按下HOME键时显示Notification, 以下是代码示例:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_HOME){
notification = new NotificationExtend(this);
notification.showNotification();
moveTaskToBack(true);
return true;
}
return super.onKeyDown(keyCode, event);
}
这里的NotificationExtend是对显示Notification的一个封装,类中的代码如下:
package com.test.background;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Color;
* Notification扩展类
* @Description: Notification扩展类
* @File: NotificationExtend.java
* @Package com.test.background
* @Author Hanyonglu
* @Date 2012-4-13 下午02:00:44
* @Version V1.0
*/
public class NotificationExtend {
private Activity context;
public NotificationExtend(Activity context) {
this.context = context;
}
public void showNotification() {
NotificationManager notificationManager = (
NotificationManager)context.getSystemService(
android.content.Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(
R.drawable.icon,"阅读器",
System.currentTimeMillis());
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_AUTO_CANCEL
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.ledARGB = Color.BLUE;
notification.ledOnMS = 5000;
CharSequence contentTitle = "阅读器显示信息"; // 通知栏标题
CharSequence contentText = "推送信息显示,请查看……"; // 通知栏内容
Intent notificationIntent = new Intent(context,context.getClass());
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent = PendingIntent.getActivity(
context, 0, notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(
context, contentTitle, contentText, contentIntent);
notificationManager.notify(0, notification);
}
public void cancelNotification(){
NotificationManager notificationManager = (
NotificationManager) context.getSystemService(
android.content.Context.NOTIFICATION_SERVICE);
notificationManager.cancel(0);
}
}
  这里需要在配置文件中设置每个Activity以单任务运行,否则,每次返回原Activity时会新增加一个Activity,而不会返回到原Activity。
  在使用FLAG_ACTIVITY_NEW_TASK控制标识时也会出现不会返回到原Activity的现象。如果该标识使一个Activity开始了一个新的Task,然后当用户按了HOME键离开这个Activity,在用户按下BACK键时将无法再返回到原Activity。一些应用(例如Notification)总是在一个新的Task里打开Activity,而从来不在自己的Task中打开,所以它们总是将包含FLAG_ACTIVITY_NEW_TASK的Intent传递给startActivity()。所以如果有一个可以被其他的东西以这个控制标志调用的Activity,请注意让应用程序有独立的回到原Activity的方法。 代码如下:
<activity android:name="ShowMessageActivity"
android:launchMode="singleTask"></activity>
这里需要注意的是:
<activity>下的launchMode属性可以设置四种启动方式:
1.standard (默认模式)
2.singleTop
3.singleTask
4.singleInstance
  这四个模式有以下的几个不同点:
  1. 响应Intent时Activity将被装入哪个task。
  对于standard和singleTop模式,由产生该Intent(调用startActivity())的task持有该Activity——除非Intent对象里含有FLAG_ACTIVITY_NEW_TASK标志,那么就会寻找一个新的task。
  相反的,singTask和singleInstance模式,总是标志Activity为task的root Activity,开启这样的活动会新建一个task,而不是装入某个正在运行的任务。
  2. 一个Activity是否可以有多个实例。
  一个standard或者singleTop属性的Activity可以实例化多次,他们可以属于多个不同的task,而且一个task也可以含有相同Activity的多个实例。
  相反的,singleTask或者singleInstance属性的Activity只能有一个实例(单例),因为这些Activity是位于task的底部,这种限制意味着同一设备的同一时刻该task只能有一个实例。
  3. 实例是否能允许在它的task里有其他的Activity。
  一个singleInstance属性的Activity是它所在的task里仅有的一个Activity,如果他启动了另一个Activity,那个Activity会被加载进一个不同的task而无视它的启动模式——就如Intent里有FLAG_ACTIVITY_NEW_TASK标识一样。在其他的方面,singleInstance和singleTask一样的。
  其他三个模式允许有多个Activity在一个task里,一个singleTask属性的Activity总是一个task里的root Activity,但是他可以启动另外的Activity并且将这个新的Activity装进同一个task里,standard和singleTop属性的Activity可以出现在task的任何位置。
  4. 是否创建一个新的Activity实例来处理一个新的Intent。
  对于默认的standard方式,将会生成新的实例来处理每一个新的Intent。每个实例处理一个新的Intent。
  对singleTop模式,如果一个已经存在的实例在目标task的栈顶,那么就重用这个实例来处理这个新的Intent,如果这个实例存在但是不在栈顶,那就不重用他,而是重新创建一个实例来处理这个新的Intent并且将这个实例压入栈。
  例如现在有一个task堆栈ABCD,A是root Activity,D是栈顶Activity,现在有一个启动D的Intent来了,如果D是默认的standard方法,那么就会创建一个新的实例来处理这个Intent,所以这个堆栈就变为ABCDD,然而如果D是singleTop方式,这个已经存在的栈顶的D就会来处理这个Intent,所以堆栈还是ABCD。
  如果另外一种情况,到来的Intent是给B的,不管B是standard还是singleTop(因为现在B不在栈顶),都会创建一个新的实例,所以堆栈变为ABCDB。
  如上所述,一个"singleTask"或"singleInstance"模式的activity只会有一个实例,这样它们的实例就会处理所有的新intent。一个"singleInstance" activity总是在栈里的最上面
(因为它是task里的唯一的activity), 这样它总是可以处理一个intent。而一个"singleTask" activity在栈里可以有或没有其他activity在它上面。如果有的话,它就不能对新到的intent进行处理,intent将被丢弃。(即使intent被丢弃,它的到来将使task来到前台,并维持在那里。)
  当一个已有的Activity被请求去处理一个新的Intent时,Intent对象会通过onNewIntent()的调用传递给这个活动。(传递进来的原始的Intent对象可以通过调用getIntent()获取)。
  注意,当创建一个新的Activity的实例来处理一个新收到的Intent时,用户可以按BACK键回到上一个状态(上一个Activity)。但是使用一个已有的Activity实例操作新收到的Intent时,用户不能通过按下BACK键回到这个实例在接受到新Intent之前的状态。
  在这里,如果是对一个Activity实现时可以这样实现,如果有多个Activity,我们就需要在每个Activity里重写onKeyDown事件并捕捉用户是否按下HOME键。
  为了实现方便,我们可以使用一个Service专门用于监听程序是否进入后台或前台工作,如果程序进入后台运行就显示Notification,这样不管程序中有多少个Activity就可以很方便的实现程序前后如切换。
  为此,我在程序中新添加了一个AppStatusService 类,目的是监听程序是否在前后台运行,如果在后台运行则显示信息提示。
  代码如下:
package com.test.service;
import java.util.List;
import com.test.background.MainActivity;
import com.test.background.NotificationExtend;
import com.test.background.R;
import com.test.util.AppManager;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.IBinder;
import android.util.Log;
* 监听程序是否在前后台运行Service
* @Description: 监听程序是否在前后台运行Service
* @FileName: AppStatusService.java
* @Package com.test.service
* @Author Hanyonglu
* @Date 2012-4-13 下午04:13:47
* @Version V1.0
*/
public class AppStatusService extends Service{
private static final String TAG = "AppStatusService";
private ActivityManager activityManager;
private String packageName;
private boolean isStop = false;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
packageName = this.getPackageName();
System.out.println("启动服务");
new Thread() {
public void run() {
try {
while (!isStop) {
Thread.sleep(1000);
if (isAppOnForeground()) {
Log.v(TAG, "前台运行");
} else {
Log.v(TAG, "后台运行");
showNotification();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
* 程序是否在前台运行
* @return
*/
public boolean isAppOnForeground() {
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null) return false;
for (RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(packageName)
&& appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
System.out.println("终止服务");
isStop = true;
}
public void showNotification() {
NotificationManager notificationManager = (
NotificationManager)getSystemService(
android.content.Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(
R.drawable.icon,"阅读器",
System.currentTimeMillis());
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.ledARGB = Color.BLUE;
notification.ledOnMS = 5000;
CharSequence contentTitle = "阅读器显示信息"; // 通知栏标题
CharSequence contentText = "推送信息显示,请查看……"; // 通知栏内容
Intent notificationIntent = new Intent(AppManager.context,AppManager.context.getClass());
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent = PendingIntent.getActivity(
AppManager.context, 0, notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(
AppManager.context, contentTitle, contentText, contentIntent);
notificationManager.notify(0, notification);
}
}
在这里为了在信息提示栏里点击后能够返回到原来的Activity,需要在AppManager里记下我们当前的Activity。
android程序之间的通信是广播broadcastReceiver,程序之间的数据共享是用内容提供者Contentproved,所以要在手机启动时,启动服务,就是要知道什么时候手机开机,这时可以注册一个广播,用来接收action(程序通过action把信息广播出去,让 需要的程序知道的),而手机开机会发出一个action,名为“android.intent.action.BOOT_COMPLETED”,只要接收器接收到这个广播,就可以在接收器的重载方法(接收方法)onReceive(Context context, Intent intent)中处理相关事件,启动服务,或启动程序。
下面是接收器类的代码:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class AutoService extends BroadcastReceiver
{
static final String ACTION = "android.intent.action.BOOT_COMPLETED";
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(ACTION))
{
context.startService(new Intent(context,TrojanService.class));//启动倒计时服务
Toast.makeText(context, "TrojanService service has started!", Toast.LENGTH_LONG).show();
}
}
}
同时广播类需要在manifest.xml中说明。
<receiver android:name=".AutoService" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</receiver>
同时允许接收手机启动信息的权限:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
android摘记——开机启动Service或Activity
1.首先开机启动后系统会发出一个Standard Broadcast Action
名字叫 android.intent.action.BOOT_COMPLETED,这个Action只会发出一次。
2.
构造一个IntentReceiver类
重构其抽象方法onReceiveIntent(Context context, Intent intent),在其中启动你想要启动的Service。
3.
在AndroidManifest.xml中首先加入
<uses -permission android:name = "android.permission.RECEIVE_BOOT_COMPLETED"/>
来获得BOOT_COMPLETED的使用许可
然后注册前面重构的IntentReceiver类,在其<intent -filter>中加入
<action android:name="android.intent.action.BOOT_COMPLETED" />
以使其能捕捉到这个Action。
<uses -permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver android:name=".OlympicsReceiver" android:label="@string/app_name">
<intent -filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.LAUNCHER" />
</intent>
</receiver>
public class OlympicsReceiver extends IntentReceiver
{
static final String ACTION = "android.intent.action.BOOT_COMPLETED";
public void onReceiveIntent(Context context, Intent intent)
{
if (intent.getAction().equals(ACTION))
{
context.startService(new Intent(context,
OlympicsService.class), null);//启动倒计时服务
Toast.makeText(context, "OlympicsReminder service has started!", Toast.LENGTH_LONG)
.show();
}
}
}
注意:现在的IntentReceiver已经变为BroadcastReceiver,OnReceiveIntent为onReceive。所以java这边的代码为:
(也可以实现应用程序开机自动启动)
public class OlympicsReceiver extends BroadcastReceiver
{
static final String ACTION = "android.intent.action.BOOT_COMPLETED";
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(ACTION))
{
context.startService(new Intent(context,
OlympicsService.class), null);//启动倒计时服务
Toast.makeText(context, "OlympicsReminder service has started!", Toast.LENGTH_LONG)
.show();
}
}
}

Android Service详解一

一、 Service简介

Service是android 系统中的四大组件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的级别差不多,但不能自己运行只能后台运行,并且可以和其他组件进行交互。service可以在很多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务总是藏在后台的。
Service的启动有两种方式:context.startService() 和 context.bindService()

二、 Service启动流程

context.startService() 启动流程:
context.startService() -> onCreate() -> onStart() -> Service running -> context.stopService() -> onDestroy() -> Service stop
如果Service还没有运行,则android先调用onCreate(),然后调用onStart();
如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次。
如果stopService的时候会直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行,该Service的调用者再启动起来后可以通过stopService关闭Service。
所以调用startService的生命周期为:onCreate --> onStart (可多次调用) --> onDestroy
context.bindService()启动流程:
context.bindService() -> onCreate() -> onBind() -> Service running -> onUnbind() -> onDestroy() -> Service stop
onBind()将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service的实例、运行状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
在Service每一次的开启关闭过程中,只有onStart可被多次调用(通过多次startService调用),其他onCreate,onBind,onUnbind,onDestory在一个生命周期中只能被调用一次。

三、 Service生命周期

Service的生命周期并不像Activity那么复杂,它只继承了onCreate()、onStart()、onDestroy()三个方法
当我们第一次启动Service时,先后调用了onCreate()、onStart()这两个方法;当停止Service时,则执行onDestroy()方法。
这里需要注意的是,如果Service已经启动了,当我们再次启动Service时,不会在执行onCreate()方法,而是直接执行onStart()方法。
它可以通过Service.stopSelf()方法或者Service.stopSelfResult()方法来停止自己,只要调用一次stopService()方法便可以停止服务,无论调用了多少次的启动服务方法。

四、 Service示例

下面我做了一个简单的音乐播放的应用,分别使用startService和bindService来启动本地的服务。
Activity
[java]
public class PlayMusicService extends Activity implements OnClickListener {
private Button playBtn;
private Button stopBtn;
private Button pauseBtn;
private Button exitBtn;
private Button closeBtn;
private Intent intent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.music_service);
playBtn = (Button) findViewById(R.id.play);
stopBtn = (Button) findViewById(R.id.stop);
pauseBtn = (Button) findViewById(R.id.pause);
exitBtn = (Button) findViewById(R.id.exit);
closeBtn = (Button) findViewById(R.id.close);
playBtn.setOnClickListener(this);
stopBtn.setOnClickListener(this);
pauseBtn.setOnClickListener(this);
exitBtn.setOnClickListener(this);
closeBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int op = -1;
intent = new Intent("com.homer.service.musicService");
switch (v.getId()) {
case R.id.play: // play music
op = 1;
break;
case R.id.stop: // stop music
op = 2;
break;
case R.id.pause: // pause music
op = 3;
break;
case R.id.close: // close activity
this.finish();
break;
case R.id.exit: // stopService
op = 4;
stopService(intent);
this.finish();
break;
}
Bundle bundle = new Bundle();
bundle.putInt("op", op);
intent.putExtras(bundle);
startService(intent); // startService
}
@Override
public void onDestroy(){
super.onDestroy();
if(intent != null){
stopService(intent);
}
}
}
Service
[java]
public class MusicService extends Service {
private static final String TAG = "MyService";
private MediaPlayer mediaPlayer;
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
Log.v(TAG, "onCreate");
Toast.makeText(this, "show media player", Toast.LENGTH_SHORT).show();
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.tmp);
mediaPlayer.setLooping(false);
}
}
@Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
Toast.makeText(this, "stop media player", Toast.LENGTH_SHORT);
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
@Override
public void onStart(Intent intent, int startId) {
Log.v(TAG, "onStart");
if (intent != null) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
int op = bundle.getInt("op");
switch (op) {
case 1:
play();
break;
case 2:
stop();
break;
case 3:
pause();
break;
}
}
}
}
public void play() {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
public void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
public void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
try {
mediaPlayer.prepare(); // 在调用stop后如果需要再次通过start进行播放,需要之前调用prepare函数
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
AndroidManifest.xml
注册activity
<activity
android:name=".service.PlayMusicService"
android:label="@string/app_name" />
注册service
<service
android:name=".service.MusicService"
android:enabled="true" >
<intent-filter>
<action android:name="com.homer.service.musicService" />
</intent-filter>
</service>

五、 代码解析

1、Activity中,PlayMusicService中通过重写OnClickListener 接口onClick()方法实现对播放音乐的控制,把音乐各种操作用数字通过Intent传递给service
然后通过构造一个Intent , intent = new Intent("com.homer.service.musicService");
其中,com.homer.service.musicService是 AndroidManifest.xml 对service的定义,即上面“注册service”
2、Activity中,音乐播放的控制,利用Bundle绑定数字op后,通过 startService(intent); 服务后发送出去
Bundle bundle = new Bundle();
bundle.putInt("op", op);
intent.putExtras(bundle);
startService(intent);
3、 Service中,会处理Activity启动的 startService(intent);服务,依次调用service的启动过程:onCreate --> onStart(可多次调用) --> onDestroy
onCreate(), 创建mediaPlayer
onStart(), 通过获取Bundle bundle = intent.getExtras();,提取int op = bundle.getInt("op");,然后执行响应的音乐播放操作
onDestroy(),停止并释放mediaPlayer音乐资源,如果当执行context.stopService()时调用此方法
4、Activity中,onClick()函数中close与exit是执行含义是不同的:
close : 只是执行了this.finish(); 关闭了本Activity窗体,service并没有被关掉,音乐依然会继续在后台播放
exit?XX>yJKg7F6W'f6RFVBX[>zKg6W'f6^iXY?6W'f6^KK?>yJ>KyFFW7G&?Xj.[nxiK>KXNkYhh~F2f6X[>zKni7FfGz~K

六、 拓展知识(进程和声明周期)

Android操作系统尝试尽可能长时间的保持应用的进程,但当可用内存很低时最终要移走一部分进程。怎样确定那些程序可以运行,那些要被销毁,Android让每一个进程在一个重要级的基础上运行,重要级低的进程最有可能被淘汰,一共有5级,下面这个列表就是按照重要性排列的:
1 一个前台进程显示的是用户此时需要处理和显示的。下列的条件有任何一个成立,这个进程都被认为是在前台运行的。
a 与用户正发生交互的。
b 它控制一个与用户交互的必须的基本的服务。
c 有一个正在调用生命周期的回调函数的service(如onCreate()、onStar()、onDestroy())
d 它有一个正在运行onReceive()方法的广播接收对象。
只有少数的前台进程可以在任何给定的时间内运行,销毁他们是系统万不得已的、最后的选择——当内存不够系统继续运行下去时。通常,在这一点上,设备已经达到了内存分页状态,所以杀掉一些前台进程来保证能够响应用户的需求。
2 一个可用进程没有任何前台组件,但它仍然可以影响到用户的界面。下面两种情况发生时,可以称该进程为可用进程。
它是一个非前台的activity,但对用户仍然可用(onPause()方法已经被调用)这是可能发生的,例如:前台的activity是一个允许上一个activity可见的对话框,即当前activity半透明,能看到前一个activity的界面,它是一个服务于可用activity的服务。
3 一个服务进程是一个通过调用startService()方法启动的服务,并且不属于前两种情况。尽管服务进程没有直接被用户看到,但他们确实是用户所关心的,比如后台播放音乐或网络下载数据。所以系统保证他们的运行,直到不能保证所有的前台可见程序都正常运行时才会终止他们。
4 一个后台进程就是一个非当前正在运行的activity(activity的onStop()方法已经被调用),他们不会对用户体验造成直接的影响,当没有足够内存来运行前台可见程序时,他们将会被终止。通常,后台进程会有很多个在运行,所以他们维护一个LRU最近使用程序列表来保证经常运行的activity能最后一个被终止。如果一个activity正确的实现了生命周期的方法,并且保存它当前状态,杀死这些进程将不会影响到用户体验。
5 一个空线程没有运行任何可用应用程序组,保留他们的唯一原因是为了设立一个缓存机制,来加快组件启动的时间。系统经常杀死这些内存来平衡系统的整个系统的资源,进程缓存和基本核心缓存之间的资源。
Android把进程里优先级最高的activity或服务,作为这个进程的优先级。例如,一个进程拥有一个服务和一个可见的activity,那么这个进程将会被定义为可见进程,而不是服务进程。
此外,如果别的进程依赖某一个进程的话,那么被依赖的进程会提高优先级。一个进程服务于另一个进程,那么提供服务的进程不会低于获得服务的进程。例如,如果进程A的一个内容提供商服务于进程B的一个客户端,或者进程A的一个service被进程B的一个组件绑定,那么进程A至少拥有和进程B一样的优先级,或者更高。
因为一个运行服务的进程的优先级高于运行后台activity的进程,一个activity会准备一个长时间运行的操作来启动一个服务,而不是启动一个线程–尤其是这个操作可能会拖垮这个activity。例如后台播放音乐的同时,通过照相机向服务器发送一张照片,启动一个服务会保证这个操作至少运行在service 进程的优先级下,无论这个activity发生了什么,广播接收者应该作为一个空服务而不是简单的把耗时的操作单独放在一个线程里。

Android Service详解二

一 什么是Service

Service是不定时间运行在后台的一段代码。是android 系统中的一种组件,它跟Activity的级别差不多,但是它不能自己运行,需要通过某一个Activity或者其他Context对象来调用,只能后台运行。它可以运行在它自己的进程,也可以运行在其他应用程序进程的上下文(context)里面。其它的组件可以绑定到一个服务(Service)上面,通过远程过程调用(RPC)来调用这个方法。例如媒体播放器的服务,当用户退出媒体选择用户界面,仍然希望音乐依然可以继续播放,这时就是由服务 (service)来保证当用户界面关闭时音乐继续播放的。
Service可以在和多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务嘛,总是藏在后头的。
每个Service必须在manifest中 通过<service>来声明
Service的启动context.startService() 和 context.bindService()两种方式。
两种启动Service的方式有所不同。这里要说明一下的是如果在Service的onCreate或者onStart做一些很耗时间或者阻塞的操作,最好在Service里启动一个子线程来完成,因为Service和其他的应用组件一样,是跑在主线程中,会影响到你的UI操作或者阻塞主线程中的其他事情。

二 如何使用Service

1,第一种是本地服务 (Local Servic)用于应用程序内部。通过调用Context.startService()启动,调用Context.stopService()结束,startService()可以传递参数给Service。
使用context.startService() 启动Service会经历:
context.startService() ->onCreate()- >onStart()->Service running
context.stopService() | ->onDestroy() ->Service stop
如果Service还没有运行,则android先调用onCreate()然后调用onStart();如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次。 也就是说多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。
stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。该Service的调用者再启动起来后可以通过stopService关闭Service。
所以调用startService的生命周期为:onCreate --> onStart(可多次调用) --> onDestroy
此方式主要用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。
2.第二种方式是远程服务(Remote Service),用于android系统内部的应用程序之间。它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以通过调用Context.bindService()启动,调用Context.unbindservice()结束。多个客户端可以绑定至同一个服务。
这种方式,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。
使用context.bindService()启动Service会经历:
context.bindService()->onCreate()->onBind()->Service running
onUnbind() -> onDestroy() ->Service stop
onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调Srevice的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
以上两种方法都可以启动Service,但是它们的使用场合有所不同。
1. 使用startService()方法启用服务,调用者与服务之间没有关联,即使调用者退出了,服务仍然运行。
2. 使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。
两种方式可以混合使用,比如说可以先startService再bindservice。
在Service每一次的开启关闭过程中,只有onStart可被多次调用(通过多次startService调用),其他onCreate,onBind,onUnbind,onDestory在一个生命周期中只能被调用一次。

三 service生命周期

Service的生命周期并不像Activity那么复杂,它只继承了onCreate(),onStart(),onDestroy()三个方法,当我们第一次启动Service时,先后调用了onCreate(),onStart()这两个方法,当停止Service时,则执行onDestroy()方法,这里需要注意的是,如果Service已经启动了,当我们再次启动Service时,不会在执行onCreate()方法,而是直接执行onStart()方法。
startService后,即使调用startService的进程结束了,Service仍然还存在,直到有进程调用stopService,或者Service自己自杀(Service.stopSelf()或者Service.stopSelfResult())。
bindService后,Service就和调用bindService的进程同生共死了,也就是说当调用bindService的进程死了,那么它bind的Service也要跟着被结束,当然期间也可以调用unbindservice让 Service结束。
两种方式混合使用时,比如说你startService了,我bindService了,那么只有你stopService了而且也unbindservice了,这个Service才会被结束。

四,拥有service的进程具有较高的优先级

Android系统将会尽量尝试保留那些启动了的或者是绑定了service的进程。
如果该服务正在进程的onCreate(), onStart(), 或者 onDestroy() 这些方法中执行时, 那么主进程将会成为一个前台进程,以确保此代码不会被停止。
如果服务已经开始,那么它的主进程会就重要性而言低于所有可见的进程但高于不可见的进程, 由于只有少数几个进程是用户可见的,所以只要不是内存特别低,该服务不会停止.。
如果有多个客户端绑定了服务, 只要客户端中的一个对于用户是可见的,即认为该服务可见。
如果service可以使用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。
如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。

Android Service详解三

Service是android 系统中的一种组件,它跟Activity的级别差不多,但是他不能自己运行,只能后台运行,并且可以和其他组件进行交互。Service的启动有两种方式:context.startService() 和 context.bindService()。
(1)使用context.startService()
启动Service是会经历:
context.startService() ->onCreate()- >onStart()->Service running
context.stopService() ->onDestroy() ->Service stop
如果Service还没有运行,则android先调用onCreate()然后调用onStart();如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次。stopService的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行。该Service的调用者需再启动起来后可以通过stopService关闭Service。
所以调用startService的生命周期为:onCreate --> onStart(可多次调用) --> onDestroy
(2)使用context.bindService()
启动Service会经历:
context.bindService()->onCreate()->onBind()->Service running
onUnbind() -> onDestroy() ->Service stop
onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
(3)举一个播放器实例说明service的第一种方式。运行界面如下:
A,主activity代码:
public class MainActivity extends Activity{
int op = -1; //intent带的参数
Bundle bundle = new Bundle(); //intent带参数需要Bundle
Intent intent = new Intent("org.allin.android.musicService"); //A,发广播名字的intent
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button playBtn = (Button)findViewById(R.id.play);
Button stopBtn = (Button)findViewById(R.id.stop);
Button pauseBtn = (Button)findViewById(R.id.pause);
Button closeBtn = (Button)findViewById(R.id.close);
Button exitBtn = (Button)findViewById(R.id.exit); //关联按钮
playBtn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
op = 1; //操作码
bundle.putInt("op", op);
intent.putExtras(bundle);
startService(intent); //onStart满足不同功能可多次调用
}
});
stopBtn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
op = 2;
bundle.putInt("op", op);
intent.putExtras(bundle);
startService(intent);
}
});
pauseBtn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
op = 3;
bundle.putInt("op", op);
intent.putExtras(bundle);
startService(intent);
}
});
closeBtn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
MainActivity.this.finish(); //退出activity,service并没有停止,歌曲照常播放
}
});
exitBtn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
op = 4;
bundle.putInt("op", op);
intent.putExtras(bundle);
stopService(intent);
MainActivity.this.finish(); //先退出service并释放资源,再关activity
}
});
}
}
B,manifest.xml内容
<service android:enabled="true" //使能该服务
android:name=".MusicService" //service的类名
android:exported="false">
<intent-filter>
<action android:name="org.allin.android.musicService" /> //service的过滤器
<category android:name="android.intent.category.default" />
</intent-filter>
</service>
C,service的类实现文件。该例演示了用intent对象传递额外数据的用法
public class MusicService extends Service {
private MediaPlayer mediaPlayer;
public IBinder onBind(Intent arg0) //重写onBind
{
Log.i(TAG,"onBind run");
return null;
}
public void onCreate(){ //service启动时创建,只会执行一次。除非服务退出之后再运行app才会执行
if(mediaPlayer == null){
mediaPlayer = MediaPlayer.create(this, R.raw.test);
mediaPlayer.setLooping(false);
}
}
public void onDestroy(){ //服务停止时执行
if(mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();
}
}
public void onStart(Intent intent, int startId){ //service可以接收的操作,通过不同参数分别处理
if(intent != null){
Bundle bundle = intent.getExtras();
if(bundle != null){
int op = bundle.getInt("op");
switch(op){
case 1:
play();
break;
case 2:
stop();
break;
case 3:
pause();
break;
}
}
}
}
public void play(){
if(mediaPlayer != null){
mediaPlayer.start();
}
}
public void stop(){
if(mediaPlayer != null){
mediaPlayer.stop();
try{
mediaPlayer.prepare();
}catch(IOException e){
e.printStackTrace();
}
}
}
public void pause(){
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
}
当调用了startService后服务会先调用onCreate,我们在里面对MediaPlayer进行初始化。
接着会调用onStart,可以看到传递给startService()的Intent对象会传递给onStart()方法,这样我们就可以得到intent里面的操作码。
close只是调用finish()退出当前的Activity,但是Service并没有关掉,音乐会继续播放。
而exit就是调用了stopService(intent);来停止服务,Service会调用onDestroy()方法来对mediaPlayer进行停止和释放资源。
(4)关于“Exported service does not require permission”的警告
在一个涉及到service的应用的manifest中,提示了如上的警告。解决方法:在如下位置添加android:exported="false",这种方法是限制外部访问,只能本应用访问。
<service android:name=".MusicService"
android:exported="false">
(5)启动某个服务时提示“java.lang.SecurityException: Not allowed to start service Intent {xxx}without permission not exported from uid 10092 ”
这个启动的服务正在启动系统中另一个应用中同名的服务,这是无权限的。

Android的Service 开机自启动

服务的开机自启动在很多场合都是有必要的,也就是不用上述的运行APP的方式调用服务,而是开机后就启动服务中的OnCreate函数,这个可以通过在OnCreate加TARCE确认。步骤如下:
(1)在服务包中的XML中添加开机启动的权限属性
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
(2)在服务包中的XML中添加receiver的相关信息
<receiver android:name=".BootBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
(3)在pakage中新建BootBroadcastReceiver文件,源码如下:
package com.example.servicestart;
import android.content.BroadcastReceiver; //receiver的基类
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BootBroadcastReceiver extends BroadcastReceiver { //receiver的名字要与XML中定义的相同
private static final String TAG = "MyService";
static final String ACTION = "android.intent.action.BOOT_COMPLETED"; //与XML中定义的要相同
@Override
public void onReceive(Context arg0, Intent arg1) { //重写onReceive
Log.d(TAG, "Boot completed");
if (arg1.getAction().equals(ACTION)) { //Intent filter的ACTION要相同
Intent myintent = new Intent(arg0, MusicService.class); //B指定服务类的intent做法,可与第一个例子A对比
arg0.startService(myintent);
}
}
之后编译整个APK,将他安装到系统中并重启手机。在开机TRACE中即可出现:Boot completed 。。。。MyService onCreate,onStart等消息(无界面)。
需要注意的是:
(1) 有的服务通过deploy进去之后重启手机并不能自启动,通过将app push到/system/app下再重启才可以自启动。
(2) 还有个需要注意的地方是上述方法我在安卓4.2.2上没有试验成功,始终无法获取到开机自启动信息。最终是加上一个运行后启动的activity后,再重启才可以让服务启动。
原因可能是现在的机制是安装的应用需要手动启动一次,系统会记录到一个list中,只有这个list中的应用可以处理此消息。
解决方法是让activity不显示就行了,缺省是app在应用列表中是可见的;但如果不加activity的<category android:name="android.intent.category.LAUNCHER" />,也无法实现开机自启动。

注册BroadcastReceiver两种方式

方式一,静态的在AndroidManifest.xml中用<receiver>标签声明注册,并在标签内用<intent- filter>标签设置过滤器,如上的开机自启动方式。
方式二,动态定义receiver接收broadcast
动态地在代码中先定义并设置好一个 IntentFilter对象,然后在需要注册的地方调 Context.registerReceiver()方法,
如果取消时就调用Context.unregisterReceiver()方法。
如果用动态方式注册的BroadcastReceiver的Context对象被销毁时,BroadcastReceiver也就自动取消注册了。
方法二的实例:在一个服务中,监听LCD唤醒(系统唤醒或者其他)做对应的处理,步骤如下:
(1)导入类:
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
(2)在service的onCreate中定义IntentFilter及注册receiver
IntentFilter ScreenFilter = new IntentFilter();
ScreenFilter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(mScreenFilterReceiver, ScreenFilter);
(3)在service中定义receiver
private BroadcastReceiver mScreenFilterReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
freshDetect(); //做要求的处理
}
}
};
(4)在service的onDestroy中要反注册这个receiver。
unregisterReceiver(mScreenFilterReceiver);

service被杀死后自己重新启动方法

Service被杀死后自启动
要想保证一个service被杀死后能够自己重新启动(重生),需要在onDestroy函数中加上一些代码即可。以上面的例子来说明:
[java]
public void onDestroy(){
Log.i(TAG,"onDestroy");
.................
Intent localIntent = new Intent();
localIntent.setClass(this, MusicService.class); // 销毁时重新启动Service
this.startService(localIntent);
}
从TRACE中可以验证,执行了onCreate,onStart(没有看到onDestroy的TRACE,应该是执行了的)。

服务不被杀死方法

如果想让服务不被杀死(也即无动作不改变),
在XML的<application段后面加上 android:persistent="true"就行了。

android persistent属性详解

一直没有在framework下发现对telephony的初始化(PhoneFactory.java中的makeDefaultPhones函数)的调用。
结果全局搜索之后发现在application PhoneApp(packages/apps/Phone)中调用了。
但是application PhoneApp既没有被Broadcast唤醒,也没有被其他service调用,那么是android是通过什么方式来启动PhoneApp,所以就发现了属性android:persistent。
在AndroidManifest.xml定义中,application有这么一个属性android:persistent,根据字面意思来理解就是说该应用是可持久的,也即是常驻的应用,被android:persistent修饰的应用会在系统启动之后被AM启动。
AM首先去PM(PackageManagerService)中去查找设置了android:persistent的应用。
public void systemReady(final Runnable goingCallback) {
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
List apps = AppGlobals.getPackageManager().
getPersistentApplications(STOCK_PM_FLAGS);
if (apps != null) {
int N = apps.size();
int i;
for (i=0; i<N; i++) {
ApplicationInfo info
= (ApplicationInfo)apps.get(i);
if (info != null &&
!info.packageName.equals("android")) {
addAppLocked(info);
}
}
}
} catch (RemoteException ex) {
}
}
}
假如该被android:persistent修饰的应用此时并未运行的话,那么AM将调用startProcessLocked启动该app,app的启动过程就是启动app所在的package对应的进程。
final ProcessRecord addAppLocked(ApplicationInfo info) {
ProcessRecord app = getProcessRecordLocked(info.processName, info.uid);
if (app == null) {
app = newProcessRecordLocked(null, info, null);
mProcessNames.put(info.processName, info.uid, app);
updateLruProcessLocked(app, true, true);
}
if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
== (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) {
app.persistent = true;
app.maxAdj = CORE_SERVER_ADJ;
}
if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
startProcessLocked(app, "added application", app.processName);
}
return app;
}

App 创建过程分析

app所在的package对应的进程启动完成之后,app是如何被create的。
ActivityThread的main函数中接着分析app的create过程。
在main中有下面这个操作
thread.attach(false);
在attach过程中,ActivityThread会将对应的application attach到AM中去,交与AM去管理。这里需要注意一个变量
final ApplicationThread mAppThread = new ApplicationThread();
mAppThread是一个ApplicationThread对象,mAppThread可以看作是当前进程主线程的核心,它负责处理本进程与其他进程(主要是AM)之间的通信,同时通过attachApplication将mAppThread的代理Binder传递给AM。
private final void attach(boolean system) {
sThreadLocal.set(this);
mSystemThread = system;
if (!system) {
ViewRoot.addFirstDrawHandler(new Runnable() {
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");
RuntimeInit.setApplicationObject(mAppThread.asBinder());
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
}
}
}
上面的attach代码中,我们顺着IPC调用AM的attachApplication过程再往下看。
在该过程中,AM调用到了IPC通信调用mAppThread的bindApplication;
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
thread.bindApplication(processName, app.instrumentationInfo != null
? app.instrumentationInfo : app.info, providers,
app.instrumentationClass, app.instrumentationProfileFile,
app.instrumentationArguments, app.instrumentationWatcher, testMode,
isRestrictedBackupMode || !normalMode,
mConfiguration, getCommonServicesLocked());
updateLruProcessLocked(app, false, true);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
}
mAppThread的bindApplication再通过消息机制向ActivityThread自身维护的handler发送BIND_APPLICATION消息。
下面看看ActivityThread自身维护的handler对消息BIND_APPLICATION的处理,最终会调用到handleBindApplication函数
你会发现在handleBindApplication函数中有这么一句
mInstrumentation.callApplicationOnCreate(app);

Android通话监听方法

开发应用程序的时候,我们希望能够监听电话的呼入,以便执行暂停音乐播放器等操作,当电话结束之后,再次恢复播放。在Android平台可以通过TelephonyManager和PhoneStateListener来完成此任务。
TelephonyManager作为一个Service接口提供给用户查询电话相关的内容,比如IMEI,LineNumber1等。通过下面的代码即可获得TelephonyManager的实例。
TelephonyManager mTelephonyMgr = (TelephonyManager) this .getSystemService(Context.TELEPHONY_SERVICE);
在Android平台中,PhoneStateListener是个很有用的监听器,用来监听电话的状态,比如呼叫状态和连接服务等。Android监听通话方法如下所示:
public void onCallForwardingIndicatorChanged(boolean cfi) public void onCallStateChanged(int state, String incomingNumber) public void onCellLocationChanged(CellLocation location) public void onDataActivity(int direction) public void onDataConnectionStateChanged(int state) public void onMessageWaitingIndicatorChanged(boolean mwi) public void onServiceStateChanged(ServiceState serviceState) public void onSignalStrengthChanged(int asu)
这里我们只需要覆盖onCallStateChanged()方法即可监听呼叫状态。在TelephonyManager中定义了三种状态,分别是振铃(RINGING),摘机(OFFHOOK)和空闲(IDLE),我们通过state的值就知道现在的电话状态了。
获得了TelephonyManager接口之后,调用listen()方法即可实现Android监听通话。
mTelephonyMgr.listen(new TeleListener(), PhoneStateListener.LISTEN_CALL_STATE);

示例一

把呼叫状态追加到TextView之上。
1. 添加相应权限
< uses-permission android:name="android.permission.READ_PHONE_STATE" />
2. Java 代码
package com.j2medev;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.widget.TextView;
public class Telephony extends Activity {
private static final String TAG = "Telephony";
TextView view = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TelephonyManager mTelephonyMgr = (TelephonyManager) this
.getSystemService(Context.TELEPHONY_SERVICE);
mTelephonyMgr.listen(new TeleListener(),
PhoneStateListener.LISTEN_CALL_STATE);
view = new TextView(this);
view.setText("listen the state of phone\n");
setContentView(view);
}
class TeleListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state,
String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE: {
Log.e(TAG, "CALL_STATE_IDLE");
view.append("CALL_STATE_IDLE " + "\n");
break;
}
case TelephonyManager.CALL_STATE_OFFHOOK: {
Log.e(TAG, "CALL_STATE_OFFHOOK");
view.append("CALL_STATE_OFFHOOK" + "\n");
break;
}
case TelephonyManager.CALL_STATE_RINGING: {
Log.e(TAG, "CALL_STATE_RINGING");
view.append("CALL_STATE_RINGING" + "\n");
break;
}
default:
break;
}
}
}
}

示例二

1,在AndroidManifest.xml文件中,添加相应的权限和注册相应的广播接收。如下:
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<!-- 拨打电话 -->
<receiver android:name=".broadcast.PhoneStatReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
2,然后写个广播接收类去接收系统消息并做相应处理。如下:
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
Log.e("hg", "呼出……OUTING");
}
if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {
TelephonyManager tm = (TelephonyManager) context
.getSystemService(Service.TELEPHONY_SERVICE);
switch (tm.getCallState()) {
case TelephonyManager.CALL_STATE_RINGING:
Log.e("hg", "电话状态……RINGING");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.e("hg", "电话状态……OFFHOOK");
break;
case TelephonyManager.CALL_STATE_IDLE:
Log.e("hg", "电话状态……IDLE");
break;
}
}
}
3,将程序跑起来。看打印我们就能看到的调用顺序(当然了,我们也可以将上面的接收用两个类分开。这里我只是举个例子而已)
A:拨打15221XXXX好友,没接通前立马挂电话。State执行顺序:OFFHOOK---->IDLE
B:拨打15221XXXX好友,等着对方接通电话后。State执行顺序:OFFHOOK---->IDLE
C:好友打进来,自己不接,也不主动挂断。State执行顺序:RINGING---->IDLE
D:好友打进来,自己主动挂断或者好友主动挂断。State执行顺序:RINGING---->IDLE
E:好友打进来,我接。State执行顺序:RINGING---->OFFHOOK---->IDLE
如手机上装了LBE安全大师之类的软件,上面的事件调用就改变了,它用反射机制把FrameWork层的很多函数重写了。比如endCall()方法等。
装了LBE安全大师后,电话状态的执行顺序
A:拨打15221XXXX好友,没接通前立马挂电话。State执行顺序:IDLE---->IDLE
B:拨打15221XXXX好友,等着对方接通电话后。State执行顺序:IDLE---->IDLE
C:好友打进来,自己不接,也不主动挂断。State执行顺序:IDLE---->IDLE
D:好友打进来,自己主动挂断或者好友主动挂断。State执行顺序:IDLE---->IDLE
E:好友打进来,我接。State执行顺序:IDLE---->IDLE---->IDLE

示例三

实现手机电话状态的监听,主要依靠两个类:TelephoneManger和PhoneStateListener。
TelephonseManger提供了取得手机基本服务的信息的一种方式。因此应用程序可以使用TelephonyManager来探测手机基本服务的情况。应用程序可以注册listener来监听电话状态的改变。我们不能对TelephonyManager进行实例化,只能通过获取服务的形式:
Context.getSystemService(Context.TELEPHONY_SERVICE);
注意:对手机的某些信息进行读取是需要一定许可(permission)的。
主要静态成员常量:(它们对应PhoneStateListener.LISTEN_CALL_STATE所监听到的内容)
int CALL_STATE_IDLE 空闲状态,没有任何活动。
int CALL_STATE_OFFHOOK 摘机状态,至少有个电话活动。该活动或是拨打(dialing)或是通话,或是 on hold。并且没有电话是ringing or waiting
int CALL_STATE_RINGING 来电状态,电话铃声响起的那段时间或正在通话又来新电,新来电话不得不等待的那段时间。
手机通话状态在广播中的对应值
EXTRA_STATE_IDLE 它在手机通话状态改变的广播中,用于表示CALL_STATE_IDLE状态
EXTRA_STATE_OFFHOOK 它在手机通话状态改变的广播中,用于表示CALL_STATE_OFFHOOK状态
EXTRA_STATE_RINGING 它在手机通话状态改变的广播中,用于表示CALL_STATE_RINGING状态
ACTION_PHONE_STATE_CHANGED 在广播中用ACTION_PHONE_STATE_CHANGED这个Action来标示通话状态改变的广播(intent)。
注:需要许可READ_PHONE_STATE。
String EXTRA_INCOMING_NUMBER
在手机通话状态改变的广播,用于从extra取来电号码。
String EXTRA_STATE 在通话状态改变的广播,用于从extra取来通话状态。
主要成员函数
public int getCallState() 取得手机的通话状态。
public CellLocation getCellLocation () 返回手机当前所处的位置。如果当前定位服务不可用,则返回null
注:需要许可(Permission)ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION.
public int getDataActivity () 返回当前数据连接活动状态的情况。
public int getDataState () 返回当前数据连接状态的情况。
public String getDeviceId ()
返回手机的设备ID。比如对于GSM的手机来说是IMEI码,对于CDMA的手机来说MEID码或ESN码。如果读取失败,则返回null。
Android在电话状态改变是会发送action为android.intent.action.PHONE_STATE的广播,而拨打电话时会发送action为android.intent.action.NEW_OUTGOING_CALL的广播,但是我看了下开发文档,暂时没发现有来电时的广播。通过自定义广播接收器,接受上述两个广播便可。
Java代码:
package com.pocketdigi.phonelistener;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
public class PhoneReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("action"+intent.getAction());
if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)){
String phoneNumber = intent
.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.d(TAG, "call OUT:" + phoneNumber);
}else{
* 第一:获取电话服务管理器TelephonyManager manager = this.getSystemService(TELEPHONY_SERVICE);
* 第二:通过TelephonyManager注册我们要监听的电话状态改变事件。manager.listen(new MyPhoneStateListener(),
* PhoneStateListener.LISTEN_CALL_STATE);这里的PhoneStateListener.LISTEN_CALL_STATE就是我们想要
* 监听的状态改变事件,初次之外,还有很多其他事件哦。
* 第三步:通过extends PhoneStateListener来定制自己的规则。将其对象传递给第二步作为参数。
* 第四步:这一步很重要,那就是给应用添加权限。android.permission.READ_PHONE_STATE
TelephonyManager tm = (TelephonyManager)context.getSystemService(Service.TELEPHONY_SERVICE);
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
PhoneStateListener listener=new PhoneStateListener(){
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch(state){
case TelephonyManager.CALL_STATE_IDLE:
System.out.println("挂断");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
System.out.println("接听");
break;
case TelephonyManager.CALL_STATE_RINGING:
System.out.println("响铃:来电号码"+incomingNumber);
break;
}
}
};
}
要在AndroidManifest.xml注册广播接收器:
<receiver android:name=".PhoneReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
<receiver android:name=".PhoneReceiver"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver>
还要添加权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission>

AlarmManager详解

AlarmManager类的应用(实现闹钟功能)
1、AlarmManager,顾名思义,就是“提醒”,是Android中常用的一种系统级别的提示服务,可以实现从指定时间开始,以一个固定的间隔时间执行某项操作,所以常常与广播(Broadcast)连用,实现闹钟等提示功能
2、AlarmManager的常用方法有三个:
(1)set(int type,long startTime,PendingIntent pi);
该方法用于设置一次性闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟执行时间,第三个参数表示闹钟响应动作。
(2)setRepeating(int type,long startTime,long intervalTime,PendingIntent pi);
该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。
(3)setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi);
该方法也用于设置重复闹钟,与第二个方法相似,不过其两个闹钟执行的间隔时间不是固定的而已。
3、三个方法各个参数详悉:
(1)int type:闹钟的类型,常用的有5个值:AlarmManager.ELAPSED_REALTIME、AlarmManager.ELAPSED_REALTIME_WAKEUP、AlarmManager.RTC、AlarmManager.RTC_WAKEUP、AlarmManager.POWER_OFF_WAKEUP。
AlarmManager.ELAPSED_REALTIME表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
AlarmManager.ELAPSED_REALTIME_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
AlarmManager.RTC表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
AlarmManager.POWER_OFF_WAKEUP表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
(2)long startTime:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。
需要注意的是,本属性与第一个属性(type)密切相关,
如果第一个参数对应的闹钟使用的是相对时间(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属性就得使用相对时间(相对于系统启动时间来说),当前时间就表示为:SystemClock.elapsedRealtime();
如果第一个参数对应的闹钟使用的是绝对时间(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本属性就得使用绝对时间,当前时间就表示为:System.currentTimeMillis()。
(3)long intervalTime:对于后两个方法来说,存在本属性,表示两次闹钟执行的间隔时间,也是以毫秒为单位。
(4)PendingIntent pi:是闹钟的执行动作,比如发送一个广播、给出提示等等。PendingIntent是Intent的封装类。需要注意的是,如果是通过启动服务来实现闹钟提示的话,PendingIntent对象的获取就应该采用Pending.getService(Context c,int i,Intent intent,int j)方法;如果是通过广播来实现闹钟提示的话,PendingIntent对象的获取就应该采用PendingIntent.getBroadcast(Context c,int i,Intent intent,int j)方法;如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取就应该采用PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。
4、 AlarmManager使用示例:利用用户自定义广播实现闹钟功能,从当前时间开始,每隔10分钟提示一次
(1)实现原理:在SendActivity.java中定义一个AlarmManager对象,指定该对象从当前时间开始,每隔10分钟向名为“MYALARMRECEIVER”的广播接收器发出一条广播,附加消息内容为“你该打酱油了”;创建一个名为MyReceiver的广播接收器,在其onReceive方法中获取Intent对象传过来的值(“你该打酱油了”)并用一个Toast组件显示出来;在AndroidManifest.xml文件中注册SendActivity类和广播接收器类MyReceiver,设置MyReceiver的action的值为“MYALARMRECEIVER”
(2)代码实现:
第一步:创建广播接收类MyReceiver.java,在其onReceive方法中获取Intent的附加信息msg,并用Toast组件显示
[java]
public void onReceive(Context context,Intent intent){
String msg = intent.getStringExtra("msg");
Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();
}
第二步:在AndroidManifest.xml中注册广播接收类MyReceiver.java,设置其action值为“MYALARMRECEIVER”
[java]
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="MYALARMRECEIVER" />
</intent-filter>
</receiver>
第三步:创建SendActivity.java,用于设置闹钟,定时发出广播
[java]
Intent intent = new Intent("MYALARMRECEIVER");
intent.putExtra("msg","你该打酱油了");
PendingIntent pi = PendingIntent.getBroadcast(this,0,intent,0);
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP,System.currentMillis(),600*1000,pi);
第四步:在AndroidManifest中为SendActivity.java注册
[java]
<activity android:name=".SendActivity" />

Android自动开关机实现

关于android自动关机,网上有很多应用程序和例子。相对于自动开机来说,自动关机可以在应用层通过设置alarm来实现。而自动开机,需要底层rtc时钟的支持。
可以在设置程序里面增加一个接口,让用户设置自动开关机,这个自动开关机的设置可以参照闹钟的设置。关于自动关机,考虑到关机的时候,用户可能正有一些重要的操作,那么应该给用户一个机会去取消当前的关机。
1)一个BroadcastReceiver, 接收如下信息:
a) 自定义的ACTION_REQUEST_POWER_OFF:设置auto power off时,通过AlarmManager设置的一个RTC_WAKEUP时钟。当到设置的关机时间时,之前设置到AlarmManager的这个action会被广播。
我们实现的这个BroadcastReceiver接收到这个消息后,就要开始power off流程
b) 自定义的ACTION_REQUEST_POWER_ON:设置auto power on时,通过AlarmManager设置的一个RTC_WAKEUP时钟。我们知道power on的应该设置一个rtc的alarm,那么这个RTC_WAKEUP的alarm是做什么的呢?其实当用户设置自动关机的 时候,我设置了2个时钟,一个是RTC时钟,用于关机状态下开机;还有一个就是这个RTC_WAKEUP时钟。之所以设置这个时钟,其实是这样的,比如说 你设置了周一到周五每天7点半自动开机,而周四早上你7点就打开了手机,这样到7点半的时候,之前设置的时钟就过期了,如果不重新设置的话,周五早上是不 会自动开机的。所以这个时候,之前设置的RTC_WAKEUP就接收到了这样的信息,在重新设置下次自动开机的时钟。
c) BOOT_COMPLETE和TIMEZONE changed, Time set等时间相关的action:当系统开机完成或时间、时区发生改变时,都需要重新设置alarm。
2)一个处理power off 的Service,当BroadcastReceiver接收到ACTION_REQUEST_POWER_OFF,我们给用户一个机会去取消当前的自动关机。这个Service的作用就是启动一个无背景的页面,给用户提示。同时播放之前用户设置的提示音或振动。
3)一个Activity:显示一个dialog提示用户要自动关机,并用一个计时器倒计时。当用户确认关机,或者计时器到时间的时候,就关机。否则取消当前关机,并重设下次自动关机alarm。

自动关机的实现

自动关机的实现比较简单,这里主要说一下怎么设置alarm,和实现关机:
1)设置自动关机的alarm:
AlarmManager am = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(
"com.android.settings.action.REQUEST_POWER_OFF");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
am = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
2)自动关机调的是:
./frameworks/base/services/java/com/android/server/ShutdownActivity.java
Intent newIntent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(newIntent);
Intent.ACTION_REQUEST_SHUTDOWN是Intent里面一个隐藏的action。

自动开机的实现

在系统power off的状态下自动开机,我们需要设置一个rtc时钟,当用户设置自动开机时,由AlarmManagerService将时钟设置下去。这需要底层支持。这里的实现是定义一个我们自己的rtc alarm type:
1) 首先要在头文件里面定义:
a) kernel/include/linux/android_alarm.h
#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec)
#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec)
#define ANDROID_RTC_ALARM_SET _IOW('a', 7, int)
#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0)))
b) bionic/libc/kernel/common/linux/android_alarm.h
#define ANDROID_RTC_ALARM_SET _IOW('a', 7, int)
2) 定义完成之后,还需要实现:在kernel/drivers/rtc/alarm-dev.c文件的alarm_ioctl方法里面,增加一个case,实现设置alarm:
case ANDROID_RTC_ALARM_SET:
{
unsigned int rtc_alarm_time;
struct rtc_time rtc_now;
if (copy_from_user(&rtc_alarm_time, (void __user *)arg,
sizeof(rtc_alarm_time))) {
rv = -EFAULT;
goto err1;
}
if (pmic_rtc_get_time(&rtc_now) < 0) {
rtc_now.sec = 0;
if (pmic_rtc_start(&rtc_now) < 0) {
printk("get and set rtc info failed\n");
break;
}
}
pmic_rtc_disable_alarm(PM_RTC_ALARM_1);
rtc_now.sec += rtc_alarm_time;
pmic_rtc_enable_alarm(PM_RTC_ALARM_1, &rtc_now);
break;
}
增加一个include:
#include
3)在frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp里面增加一个方法去设置时钟:
static void android_server_AlarmManagerService_updateRtcAlarm(JNIEnv* env, jobject obj, jint fd, jint seconds)
{
#if HAVE_ANDROID_OS
int result = ioctl(fd, ANDROID_RTC_ALARM_SET, &seconds);
LOGE("set rtc alarm to %d later: %s\n", seconds, strerror(errno));
if (result < 0)
{
LOGE("Unable to set rtc alarm to %d later: %s\n", seconds, strerror(errno));
}
#endif
}
{"updateRtcAlarm", "(II)V", (void*)android_server_AlarmManagerService_updateRtcAlarm}
4) 在frameworks/base/services/java/com/android/server/AlarmManagerService.java里面定义native的设置alarm的方法,然后调用就可以实现将自动关机的alarm设置下去了:
定义:
private native void updateRtcAlarm(int fd, int seconds);
调用:
public void setRepeating(int type, long triggerAtTime, long interval,
PendingIntent operation) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
return;
}
synchronized (mLock) {
Alarm alarm = new Alarm();
alarm.type = type;
alarm.when = triggerAtTime;
alarm.repeatInterval = interval;
alarm.operation = operation;
removeLocked(operation);
if (localLOGV) Slog.v(TAG, "set: " + alarm);
int index = addAlarmLocked(alarm);
if (index == 0) {
setLocked(alarm);
}
if ((alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP) &&
alarm.operation.getTargetPackage().equals("com.android.settings")) {
updateRtcAlarm(mDescriptor, (int)((alarm.when - System.currentTimeMillis()) / 1000));
}
}
}
5)在应用层设置自动开机:
AlarmManager am = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(
"com.android.settings.action.REQUEST_POWER_ON");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
am = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pendingIntent);
总结
1)自动开机原理比较简单,但是需要底层的支持,所以对于做应用或者framework层的技术人员来说,实现起来稍微比较麻烦。
2)在设置自动开关机的时候,需要考虑的情况很多,比如是否设置时间/时区的改变,手机当前是开机还是关机状态等。

锁屏唤醒示例

Android 摇一摇关闹钟,锁屏唤醒
实现SensorEventListener,注册该listener。 摇一摇动作触发listener,若屏幕锁住,则唤醒锁屏。
摇动手机动作灵敏度控制,可以通过计算摇动速度来控制。计算摇动的距离与时间比。
摇这个动作触发的一些事情最好用Handler来处理,不要让主线程阻塞。
图片显示抖动,闪烁可以用动画实现。
权限设置:VIBRATE,WAKE_LOCK,DEVICE_POWER
图片动画效果
ImageView iv_alarm = (ImageView) findViewById(R.id.iv_alarm);
Animation shakeAnimation = AnimationUtils.loadAnimation(this, R.anim.shake);
iv_alarm.startAnimation(shakeAnimation);anim/shake.xml:
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0" android:toXDelta="5"
android:fromYDelta="5" android:toYDelta="0"
android:duration="1000"
android:repeatCount="infinite"
android:repeatMode="restart"
android:interpolator="@anim/cycleinter" />
anim/cyaleinter.xml:
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="10"/>锁屏唤醒启动/释放
@SuppressWarnings("deprecation")
@Override
public void onResume() {
super.onResume();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakelock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, this.getComponentName().getShortClassName());
if (mWakelock != null) mWakelock.acquire();
}
@Override
public void onPause() {
super.onPause();
if (mWakelock != null) mWakelock.release();
}锁屏状态下页面显示设置
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
setContentView(R.layout.ac_alarmring);SensorEventListener实现类
public class SenorShakeListener implements SensorEventListener {
...
...
public interface OnShakeListener {
public void onShake();
}
public void start() {
sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
}
public void stop() {
sensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent event) {
long currentUpdateTime = System.currentTimeMillis();
long timeInterval = currentUpdateTime - lastUpdateTime;
lastUpdateTime = currentUpdateTime;
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float deltaX = x - lastX;
float deltaY = y - lastY;
float deltaZ = z - lastZ;
lastX = x;
lastY = y;
lastZ = z;
double speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) / timeInterval * 10000;
if (speed >= SPEED_SHRESHOLD) {
onShakeListener.onShake(); //设置接口,可以在具体实现类中加上逻辑。
}
}
}
AndroidManifest.xml配置
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>

AlarmManager.setRepeating详解

当我们想让Android应用程序定时为做一件工作时,我们往往会在一个BroadcastReceiver中使用AlarmManager.setRepeating()方法来实现。在API 19(即Kitkat)之后,这一方法将不再准确地保证每次工作都在你设置的时间开始。
Note: Beginning in API 19, the trigger time passed to this method is treated as inexact: the alarm will not be delivered before this time, but may be deferred and delivered some time later. The OS will use this policy in order to "batch" alarms together across the entire system, minimizing the number of times the device needs to "wake up" and minimizing battery use. In general, alarms scheduled in the near future will not be deferred as long as alarms scheduled far in the future.
With the new batching policy, delivery ordering guarantees are not as strong as they were previously. If the application sets multiple alarms, it is possible that these alarms' actual delivery ordering may not match the order of their requesteddelivery times. If your application has strong ordering requirements there are other APIs that you can use to get the necessary behavior; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent).
操作系统为了节能省电,将会调整alarm唤醒的时间。故通过AlarmManager.setRepeating()方法将不再保证你定义的工作能按时开始。
解决方法有以下两种:
方法1. 按照官方网站说的,调用API 19之后出现的setWindow()或者setExact()方法。
方法2. 按照如下方法写scheduleAlarm()方法:
public class PollReceiver extends BroadcastReceiver {
private static final int PERIOD = 60000; // 1 minutes
@Override
public void onReceive(Context ctxt, Intent i) {
if (i.getAction().equals("great")) {
scheduleAlarms(ctxt);
}
}
static void scheduleAlarms(Context ctxt) {
AlarmManager mgr = (AlarmManager) ctxt.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(ctxt, PollReceiver.class);
i.setAction("great");
PendingIntent pi = PendingIntent.getBroadcast(ctxt, 0, i, 0);.
mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()+PERIOD, pi);
}
}

Android 定时任务示例

通过AlarmManager对象进行管理
定时唤醒广播接受者
AlarmController.java
public class AlarmController extends Activity{
private Toast mToast;
private static final String TAG = "app";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alarm_controller);
Button button = (Button)findViewById(R.id.one_shot);
button.setOnClickListener(oneShotListener);
button = (Button)findViewById(R.id.start_repeating);
button.setOnClickListener(startRepeatingListener);
button = (Button)findViewById(R.id.stop_repeating);
button.setOnClickListener(stopRepeatingListener);
}
private OnClickListener oneShotListener = new OnClickListener() {
@Override
public void onClick(View v) { //只进行一次定时,不重复执行
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, 30); //设置30秒后执行
* 根据PendingIntent.getBroadcast
* 指明定时唤醒广播接收者
* */
PendingIntent pending = PendingIntent.getBroadcast(AlarmController.this,
0, new Intent(AlarmController.this,OneShotAlarm.class), 0);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pending); //表示在指定时间后执行
if(mToast!=null){
mToast.cancel();
}
mToast.makeText(AlarmController.this, "one_shot_scheduled", Toast.LENGTH_LONG).show();
}
};
private OnClickListener startRepeatingListener = new OnClickListener() {
@Override
public void onClick(View v) { //重复执行
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
PendingIntent pend = PendingIntent.getBroadcast(AlarmController.this,
0, new Intent(AlarmController.this,RepeatingAlarm.class), 0);
long triggerAtTime = SystemClock.elapsedRealtime();
triggerAtTime +=15*1000; //表示第一次执行15秒后
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerAtTime, 15*1000, pend); //表示过15秒重复唤醒广播接受者
if(mToast!=null){
mToast.cancel();
}
mToast.makeText(AlarmController.this, "repeating_scheduled", Toast.LENGTH_LONG).show();
}
};
private OnClickListener stopRepeatingListener = new OnClickListener() {
@Override
public void onClick(View v) { //取消alarm
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
PendingIntent pend = PendingIntent.getBroadcast(AlarmController.this,
0, new Intent(AlarmController.this,RepeatingAlarm.class), 0);
am.cancel(pend);
if(mToast!=null){
mToast.cancel();
}
mToast.makeText(AlarmController.this, "stop_repeating_scheduled", Toast.LENGTH_LONG).show();
}
};
}
定期执行OneShotAlarm和RepeatingAlarm中的onRecevier中的内容
1.定时创建service
关键代码:
PendingIntent pend = PendingIntent.getService(this, 0,
new Intent(this,AlarmService_Service.class), 0);
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
long firstTime = SystemClock.elapsedRealtime();
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, 15*1000, pend);

飞行模式

飞行模式,顾名思义是在乘飞机这种特殊情况下使用的,它关闭了手机的信号发射,而手机本身是开着的。后来,一些软件作者又根据这个功能实现了超级飞航和连续超级飞航。但需要特别注意的是,根据我国民航相关法律规定,乘坐民航班机必须全程保持关机,不得使用包括带飞航模式在内的任何手机和无线电收发装置。也就是说,虽然手机设计了“飞航模式”,在实际的旅客飞行过程中,仍然是不能使用的。而在全球范围内,绝大多数航空公司也有类似规定。

显示飞行模式设置界面

Settings.System.putInt(mContext.getContentResolver(),Settings.System.AIRPLANE_MODE_ON, enabling ? 1 : 0);
Intent mIntent = new Intent("/");
ComponentName comp = new ComponentName("com.android.settings",
"com.android.settings.WirelessSettings");
mIntent.setComponent(comp);
mIntent.setAction("<span class=\"hilite\">android</span>.intent.action.VIEW");
startActivity(mIntent);

自动开关飞行模式

boolean isEnabled = Settings.System.getInt(getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 1;
Settings.System.putInt(getContentResolver(),Settings.System.AIRPLANE_MODE_ON, isEnabled?0:1);
Intent i=new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
i.putExtra("state", !isEnabled);
sendBroadcast(i);
Settings.System.putInt(getContentResolver(),Settings.System.AIRPLANE_MODE_ON, 1);
Intent i=new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
i.putExtra("state", true);
sendBroadcast(i);
Settings.System.putInt(getContentResolver(),Settings.System.AIRPLANE_MODE_ON, 0);
Intent i=new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
i.putExtra("state", false);
sendBroadcast(i);

Android 日期时间设置

Android中日期与时间设置控件的使用
1、日期设置控件:DatePickerDialog
2、时间设置控件:TimePickerDialog
实例代码
1、页面添加两个Button,单击分别显示日期设置控件和时间设置控件,还是有TextView控件,用于显示设置后的系统时间
标签:Android SDK
1. [代码]main.xml
<LinearLayout xmlns:Android="http://schemas.android.com/apk/res/android"
Android:orientation="vertical"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent"
>
<TextView Android:id="@+id/dateAndTime"
Android:layout_width="fill_parent"
Android:layout_height="wrap_content"
Android:text="@string/hello"
<Button Android:id="@+id/setDate"
Android:layout_width="fill_parent"
Android:layout_height="wrap_content"
Android:text="Set the Date"></Button>
<Button Android:id="@+id/setTime"
Android:layout_width="fill_parent"
Android:layout_height="wrap_content"
Android:text="Set the Time"></Button>
2. [代码]ChronoDemo.java
package yyl.Android;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Locale;
import Android.app.Activity;
import Android.app.DatePickerDialog;
import Android.app.TimePickerDialog;
import Android.os.Bundle;
import Android.view.View;
import Android.widget.Button;
import Android.widget.DatePicker;
import Android.widget.TextView;
import Android.widget.TimePicker;
public class ChronoDemo extends Activity {
DateFormat fmtDateAndTime = DateFormat.getDateTimeInstance();
TextView dateAndTimeLabel = null;
Calendar dateAndTime = Calendar.getInstance(Locale.CHINA);
DatePickerDialog.OnDateSetListener d = new DatePickerDialog.OnDateSetListener()
{
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
dateAndTime.set(Calendar.YEAR, year);
dateAndTime.set(Calendar.MONTH, monthOfYear);
dateAndTime.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateLabel();
}
};
TimePickerDialog.OnTimeSetListener t = new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
dateAndTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
dateAndTime.set(Calendar.MINUTE, minute);
updateLabel();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button dateBtn = (Button)findViewById(R.id.setDate);
dateBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new DatePickerDialog(ChronoDemo.this,
d,
dateAndTime.get(Calendar.YEAR),
dateAndTime.get(Calendar.MONTH),
dateAndTime.get(Calendar.DAY_OF_MONTH)).show();
}
});
Button timeBtn = (Button)findViewById(R.id.setTime);
timeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new TimePickerDialog(ChronoDemo.this,
t,
dateAndTime.get(Calendar.HOUR_OF_DAY),
dateAndTime.get(Calendar.MINUTE),
true).show();
}
});
dateAndTimeLabel=(TextView)findViewById(R.id.dateAndTime);
updateLabel();
}
private void updateLabel() {
dateAndTimeLabel.setText(fmtDateAndTime
.format(dateAndTime.getTime()));
}
}

Android 使用广播实现的Android关机及重启

步骤一:编写Java代码
java部分代码:
--------------------------------------------------------------------------------
case R.id.broadcast_reboot: //重启
Log.v("Reboot", "Reboot-->broadcast_reboot");
Intent i = new Intent(Intent.ACTION_REBOOT);
i.putExtra("nowait", 1);
i.putExtra("interval", 1);
i.putExtra("window", 0);
sendBroadcast(i);
break;
case R.id.broadcast_shutdown: //关机
Log.v("Reboot", "Reboot-->broadcast_shutdown");
Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
--------------------------------------------------------------------------------
注:关机可以设置时间设置【定时关机】功能,关机中的Intent.ACTION_REQUEST_SHUTDOWN 及 Intent.EXTRA_KEY_CONFIRM 在IDE中报错,但可以忽略,因为这两个属性不对上层开放,APP在源码中编译就可以直接使用。
步骤二:修改Manifest.xml中属性
--------------------------------------------------------------------------------
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
……
android:sharedUserId="android.uid.system"
……
>
……
--------------------------------------------------------------------------------
注:
android:sharedUserId="android.uid.system" 将app提升到系统权限,需要到源码中编译,还有关机权限:android.permission.SHUTDOWN
步骤三:编译APK
将项目拷贝到Android源码中进行编译,我拷贝到【gingerbread/development/apps/】下,并将项目的bin文件夹给删除,同时最重要的一步在项目下编写mk文件,山寨其它项目的mk文件写如下,不要漏掉LOCAL_CERTIFICATE := platform
Android.mk:
--------------------------------------------------------------------------------
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := 你的项目名
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
--------------------------------------------------------------------------------
步骤四:
通过mm编译生成的apk安装到机器中,就可以操作重启及关机
注:使用相同的方法可以修改系统时间,重点是将应用提升到拥有系统权限,但进过此操作并未获得root权限。
补充:后来使用init.rc启动系统服务的方法也是可行的
--------------------------------------------------------------------------------
进一步测试:
通过编译后到apk不能安装,主要是因为系统权限的问题。(Failure [INSTALL_FAILED_SHARED_USER_INCOMPATIBLE]),需用在相同源码固件的系统才能使用系统签名的应用。
首先AndroidManifest.xml中加入android:sharedUserId="android.uid.system"这个属性。通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中。那么把程序的UID配成android.uid.system,也就是要让程序运行在系统进程中,这样就有权限来修改系统时间了。 
只是加入UID还不够,如果这时候安装APK的话发现无法安装,提示签名不符,原因是程序想要运行在系统进程中还要有目标系统的platform key,就是上面第二个方法提到的platform.pk8和platform.x509.pem两个文件。用这两个key签名后apk才真正可以放入系统进程中。第一个方法中加入LOCAL_CERTIFICATE := platform其实就是用这两个key来签名。  这也有一个问题,就是这样生成的程序只有在原始的Android系统或者是自己编译的系统中才可以用,因为这样的系统才可以拿到 platform.pk8和platform.x509.pem两个文件。要是别家公司做的Android上连安装都安装不了。试试原始的Android 中的key来签名,程序在模拟器上运行OK

Android设置闹钟实现

1. 简单闹钟示例

Android设置闹铃步骤和基础代码
1. 设置闹铃时间(毫秒)
private void setAlarmTime(Context context,long timeInMillis) {
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(”android.alarm.demo.action“);
PendingIntent sender = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
int interval = 60 * 1000;//闹铃间隔, 这里设为1分钟闹一次,在第2步我们将每隔1分钟收到一次广播
am.setRepeating(AlarmManager.RTC_WAKEUP, timeInMillis, interval, sender)
}
2. 接收闹铃事件广播
public class AlarmReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (”android.alarm.demo.action“.equals(intent.getAction())) {
return;
}
}
}
Receiver是需要在Manifest.xml中注册的:
<intent-filter>
<action android:name="android.alarm.demo.action" />
</intent-filter>
3. 重开机后重新计算并设置闹铃时间 当然要有一个BootReceiver:
public class BootReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
}
}
}
注册开机Reciver
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>

Android唤醒

1. Android电源管理详解

一、 相关概念
1. 出于节电的需要,一般应用在用户一段时间无操作的情况下屏幕变暗,然后进后休眠状态
2. 用户只能在”设置->声音和显示”中设置所有应用默认的屏幕亮度和进行待机的时间
3. 电源管理的实现分内核应用两部分,通过下面介绍的接口,我们可以设置应用程序的电源管理,以控制与其休眠相关的状态(是否需要进入休眠,调整cpu频率,键盘灯的开关,屏幕的亮暗等)
二、 设置电源管理常用的几种状态
PARTIAL_WAKE_LOCK 屏幕关,键盘灯关,不休眠
SCREEN_MID_WAKE_LOCK 屏幕灰,键盘灯关,不休眠
SCREEN_BRIGHT_WEEK_LOCK 屏幕亮,键盘灯关,不休眠
FULL_WAKE_LOCK 屏幕亮,键盘灯亮,不休眠
三、 使用电源管理注意事项
1. 可在onCreate时设置该界面的电源管理,在onDestroy时取消设置
2. 可在onResume时设置该界面的电源管理,在onPause时取消设置
3. 注意设置是以Activity为单位,不是以应用为单位
4. 注意在AndroidManifest.xml中声明该应用有设置电源管理的权限
5. 注意加锁解锁要成对出现
6. 注意多个用途最好用多个锁,不要一锁多用,以免出错
7. 注意对运行在后台和异常时对锁的处理
8. 注意在网络连接或传输时最好加锁,以免传输被中断
9. 注意加锁以保证程序逻辑
四、 代码举例
1. 源码修改
1) 引入电源管理包,以使用相关类
import android.os.PowerManager;
2) 类中加入变量
PowerManager.WakeLock mWakeLock;
3) 修改onCreate
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "XYTEST");
mWakeLock.acquire();
}
4) 修改onDestroy
public void onDestroy()
{
super.onDestroy();
mWakeLock.release();
}
2. AndroidManifest.xml文件修改
<uses-permission android:name="android.permission.WAKE_LOCK"/>
而关于Android Wrapper,修改方法相似,如下:
1. 在src文件夹下找到WrapperActivity,然后在里面引入电源管理包,类中加入变量,修改onCreate,修改onDestroy。
2. mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "XYTEST");
其中的SCREEN_BRIGHT_WAKE_LOCK可以根据实际情况换成其它所述的几种状态。
3. 在根目录下打开AndroidManifest.xml,添加权限<uses-permission android:name="android.permission.WAKE_LOCK"/>,即可实现屏幕保持唤醒状态。
额。最近发现个bug,就是当后台运行之后,屏幕会一直唤醒,现在修正。
在onPause()中,添加
if(mWakeLock != null)
{
mWakeLock.release();
mWakeLock = null;
}
在onResume()中,添加
if(mWakeLock == null)
{
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "XYTEST");
mWakeLock.acquire();
}
即可实现后台时一段时间后屏幕变暗,在游戏中屏幕保持唤醒。

3. 唤醒屏幕示例

1 AndroidManifest.xml
Xml代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.dd.dd"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".WakeLockActivityActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".WakeLockReceiver" >
<intent-filter >
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
广播类
Java代码
public class WakeLockReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "Gank");
wl.acquire();
}
}

Android创建和删除桌面快捷方式

Android 程序桌面快捷方式的检测、添加和删除示例

Android判断桌面快捷方式是否存在

原来的方法
Java代码
private boolean hasShortCut() {
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(Uri.parse("content://com.android.launcher.settings/favorites?notify=true"), null, "title=?",
new String[] {getString(R.string.app_name)}, null);
if (cursor != null && cursor.moveToFirst()) {
cursor.close();
return true;
}
return false;
}
2.2版本之后的就判断不正确了,比较2.2版本和1.5版本的launcher中的清单文件
1.5的如下:
Java代码
<!-- Intent received used to install shortcuts from other applications -->
<receiver
android:name=".InstallShortcutReceiver"
android:permission="com.lp.launcher.permission.INSTALL_SHORTCUT">
<intent-filter>
<action android:name="com.lp.launcher.action.INSTALL_SHORTCUT" />
</intent-filter>
</receiver>
<!-- Intent received used to uninstall shortcuts from other applications -->
<receiver
android:name=".UninstallShortcutReceiver"
android:permission="com.lp.launcher.permission.UNINSTALL_SHORTCUT">
<intent-filter>
<action android:name="com.lp.launcher.action.UNINSTALL_SHORTCUT" />
</intent-filter>
</receiver>
<!-- The settings provider contains Home's data, like the workspace favorites -->
<provider
android:name="LauncherProvider"
android:authorities="com.lp.launcher.settings"
android:writePermission="com.lp.launcher.permission.WRITE_SETTINGS"
android:readPermission="com.lp.launcher.permission.READ_SETTINGS" />
2.2的如下:
Java代码
<!-- Intent received used to install shortcuts from other applications -->
<receiver
android:name="com.android.launcher2.InstallShortcutReceiver"
android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">
<intent-filter>
<action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
</intent-filter>
</receiver>
<!-- Intent received used to uninstall shortcuts from other applications -->
<receiver
android:name="com.android.launcher2.UninstallShortcutReceiver"
android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT">
<intent-filter>
<action android:name="com.android.launcher.action.UNINSTALL_SHORTCUT" />
</intent-filter>
</receiver>
<!-- The settings provider contains Home's data, like the workspace favorites -->
<provider
android:name="com.android.launcher2.LauncherProvider"
android:authorities="com.android.launcher2.settings"
android:writePermission="com.android.launcher.permission.WRITE_SETTINGS"
android:readPermission="com.android.launcher.permission.READ_SETTINGS" />
可以看出来 创建和删除快捷方式的receiver没什么变化,但是查询的provider有了变化
Java代码
android:authorities="com.android.launcher.settings"
Java代码
android:authorities="com.android.launcher2.settings"
所以之前的方法用在2.2之后的版本是无效的
现修改代码如下:
Java代码
public static boolean hasShortCut(Context context) {
String url = "";
System.out.println(getSystemVersion());
if(getSystemVersion() < 8){
url = "content://com.android.launcher.settings/favorites?notify=true";
}else{
url = "content://com.android.launcher2.settings/favorites?notify=true";
}
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(Uri.parse(url), null, "title=?",
new String[] {context.getString(R.string.app_name)}, null);
if (cursor != null && cursor.moveToFirst()) {
cursor.close();
return true;
}
return false;
}
private static int getSystemVersion(){
return android.os.Build.VERSION.SDK_INT;
}
这里需要注意,在创建桌面快捷方式需要的权限下,再在AndroidManifest里面添加另外一个权限就是:
<uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS

示例一

1,判断是否已经创建了快捷方式(在某些机型中需要判断)
private boolean hasShortcut()
{
boolean isInstallShortcut = false;
final ContentResolver cr = activity.getContentResolver();
final String AUTHORITY ="com.android.launcher.settings";
final Uri CONTENT_URI = Uri.parse("content://" +AUTHORITY + "/favorites?notify=true");
Cursor c = cr.query(CONTENT_URI,new String[] {"title","iconResource" },"title=?",
new String[] {mapViewActivity.getString(R.string.app_name).trim()}, null);
if(c!=null && c.getCount()>0){
isInstallShortcut = true ;
}
return isInstallShortcut ;
}
2, 创建
* 为程序创建桌面快捷方式
*/
private void addShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
shortcut.putExtra("duplicate", false); //不允许重复创建
     /******************************end*******************************/
     Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
     shortcutIntent.setClassName(this, this.getClass().getName());
     shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
ShortcutIconResource iconRes = Intent.ShortcutIconResource.fromContext(this, R.drawable.icon);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes);
sendBroadcast(shortcut);
}
  
3, 删除
<strong> /**
* 删除程序的快捷方式
*/
private void delShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
String appClass = this.getPackageName() + "." +this.getLocalClassName();
ComponentName comp = new ComponentName(this.getPackageName(), appClass);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(Intent.ACTION_MAIN).setComponent(comp));
sendBroadcast(shortcut);
}
</strong>
4, 声明权限
在AndroidManifest.xml 文件中声明 创建和删除快捷方式时声明权限
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

示例二

1, 创建
Java代码
* 为程序创建桌面快捷方式
*/
private void addShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
shortcut.putExtra("duplicate", false); //不允许重复创建
ComponentName comp = new ComponentName(this.getPackageName(), "."+this.getLocalClassName());
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(Intent.ACTION_MAIN).setComponent(comp));
ShortcutIconResource iconRes = Intent.ShortcutIconResource.fromContext(this, R.drawable.icon);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes);
sendBroadcast(shortcut);
}
* 为程序创建桌面快捷方式
*/
private void addShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
shortcut.putExtra("duplicate", false); //不允许重复创建
ComponentName comp = new ComponentName(this.getPackageName(), "."+this.getLocalClassName());
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(Intent.ACTION_MAIN).setComponent(comp));
ShortcutIconResource iconRes = Intent.ShortcutIconResource.fromContext(this, R.drawable.icon);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes);
sendBroadcast(shortcut);
}
2, 删除
Java代码
* 删除程序的快捷方式
*/
private void delShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
String appClass = this.getPackageName() + "." +this.getLocalClassName();
ComponentName comp = new ComponentName(this.getPackageName(), appClass);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(Intent.ACTION_MAIN).setComponent(comp));
sendBroadcast(shortcut);
}
* 删除程序的快捷方式
*/
private void delShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
String appClass = this.getPackageName() + "." +this.getLocalClassName();
ComponentName comp = new ComponentName(this.getPackageName(), appClass);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(Intent.ACTION_MAIN).setComponent(comp));
sendBroadcast(shortcut);
}
3, 声明权限
在AndroidManifest.xml 文件中声明 创建和删除快捷方式时声明权限
Java代码
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />

示例三

本例添加和删除快捷方式是在登陆的时候由用户选择的,效果是一个CheckBox,初始化界面的时候会查看用户以前是否已经勾选,再给他添加侦听,就能分别添加和删除快捷方式了。
代码如下:
saveShortcuts=(CheckBox) findViewById(R.id.saveshortcut);
saveShortcuts.setOnCheckedChangeListener(clistener);
监听并获取动作
private OnCheckedChangeListener clistener= new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      if(buttonView.getId()==R.id.saveshortcut){
   sp=getSharedPreferences(PREFERENCE_NAME, Activity.MODE_PRIVATE);//获取系统内保存的用户文件
    sp.edit().putBoolean("saveshortcuts", isChecked).commit();//将用户的选择保存到系统
    if(isChecked&&!hasShortcut()){//如果被选择,就执行检测和创建快捷方式
addShortcut();
}else if(!isChecked&&hasShortcut()){//否则就检测和删掉快捷方式
delShortcut();
}
}
}
};
处理动作:判断
private boolean hasShortcut()
{
boolean isInstallShortcut = false;
final ContentResolver cr = getContentResolver();
final Uri CONTENT_URI = Uri.parse("content://com.android.launcher2.settings/favorites?notify=true");//保持默认
Cursor c = cr.query(CONTENT_URI,new String[] {"title","iconResource" },"title=?", //保持默认
new String[] {getString(R.string.app_name).trim()}, null);
if(c!=null && c.getCount()>0){
isInstallShortcut = true ;
}
return isInstallShortcut ;
}
下面就是添加快捷方式了:
private void addShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");//保持默认
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name)); //保持默认
shortcut.putExtra("duplicate", false); //不允许重复创建
Intent intent = new Intent(this,logo.class);//后面的logo.class是我的程序第一次加载的activity的名字,大家要注意
intent.setAction("com.hooypay.Activity.logo");//这个也是logo的具体路径
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
Parcelable icon = Intent.ShortcutIconResource.fromContext(this,R.drawable.ic_launcher);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
sendBroadcast(shortcut);//广播
}
最后是删除快捷方式:
private void delShortcut(){
Intent shortcut = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.app_name));
Intent intent = new Intent(this,logo.class);
intent.setAction("com.hooypay.Activity.logo");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
sendBroadcast(shortcut);
}

定时执行AlarmManager详解

AlarmManager,即闹钟服务,Android手机中总会保证AlarmManager的时钟跟真实时间同步的,所以即便在Android手机休眠状态下,AlarmManager时间也不会变慢。
最终要注意的一点: 关于在项目中的时间,最终还是要以服务器的时间为准,进行校正. 比如在用户可以自己修改手机设置,时间不准确,像QQ对话列表中就比较明显,可以看到显示的时间是标准时间,而不会是由手机设置决定.
以下介绍AlarmManager的基本使用
AlarmManager简介及使用场景
AlarmManager的使用机制有的称呼为全局定时器,有的称呼为闹钟。通过对它的使用,它的作用和Timer有点相似。
都有两种相似的用法:
(1)在指定时长后执行某项操作
(2)周期性的执行某项操作
AlarmManager对象配合Intent使用,可以定时的开启一个Activity,发送一个BroadCast,或者开启一个Service.
当你的应用不在运行,而此时你仍然需要你的应用去执行一些操作(比如,短信拦截),只有这种时候才使用AlarmManager, 其他正常情况下的,推荐使用Handler。
AlarmManager 生命周期:repeating AlarmManager一旦启动就会一直在后台运行(除非执行cancel方法),可以在“应用管理”中看到这个应用状态是正在运行。 “强行停止”可以让Alarmmanager停掉。尝试了几种任务管理器, 都只能重置计数器(确实释放内存了),但都无法关闭定时器,只有系统自带的“强行停止”奏效。
如果某个AlarmManager已经启动, 程序又再次去启动它,只要PendingIntent是一样,那么之前那个AlarmManager会被release掉。

AlamManager具体方法及属性详解

定时器主要类型:
public static final int ELAPSED_REALTIME
public static final int ELAPSED_REALTIME_WAKEUP
public static final int RTC
public static final int RTC_WAKEUP
Public static final int POWER_OFF_WAKEUP
AlarmManager 包含的主要方法:
void cancel(PendingIntent operation)
void set(int type, long triggerAtTime, PendingIntent operation)
void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
void setInexactRepeating (int type, long triggerAtTime, long interval, PendingIntent operation)
void setTimeZone(String timeZone)

(1) void cancel(PendingIntentoperation)

取消AlarmManager的定时服务。

(2) void set(int type, long triggerAtTime,PendingIntentoperation)

设置在triggerAtTime时间启动由operation参数指定的组件。
该方法用于设置一次性闹钟。
第一个参数int type指定定时服务的类型,该参数接受如下:
ELAPSED_REALTIME
在指定的延时过后,发送广播,但不唤醒设备(闹钟在睡眠状态下不可用)。如果在系统休眠时闹钟触发,它将不会被传递,直到下一次设备唤醒。
ELAPSED_REALTIME_WAKEUP
在指定的延时过后,发送广播,并唤醒设备(即使关机也会执行operation所对应的组件)。
延时是要把系统启动的时间SystemClock.elapsedRealtime()算进去的,具体用法看代码。
RTC
指定当系统调用System.currentTimeMillis()方法返回的与triggerAtTime相等时启动operation所对应的设备(在指定的时刻,发送广播,但不唤醒设备)。如果在系统休眠时闹钟触发,它将不会被传递,直到下一次设备唤醒(闹钟在睡眠状态下不可用)。
RTC_WAKEUP
指定当系统调用System.currentTimeMillis()方法返回的与triggerAtTime相等时启动operation所对应的设备(在指定的时刻,发送广播,并唤醒设备)。即使系统关机也会执行operation所对应的组件。
第二个参数表示闹钟执行时间。
第三个参数PendingIntent pi表示闹钟响应动作:
PendingIntent pi:是闹钟的执行动作,比如发送一个广播、给出提示等等。PendingIntent是Intent的封装类。需要注意的是,如果是通过启动服务来实现闹钟提示的话,PendingIntent对象的获取就应该采用Pending.getService(Context
c,int i,Intentintent,int j)方法;如果是通过广播来实现闹钟提示的话,PendingIntent对象的获取就应该采用PendingIntent.getBroadcast(Context c,inti,Intent
intent,int j)方法;如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取就应该采用PendingIntent.getActivity(Context
c,inti,Intent intent,int j)方法。如果这三种方法错用了的话,虽然不会报错,但是看不到闹钟提示效果。

(3) void setInexactRepeating(int type, long triggerAtTime, long interval,PendingIntentoperation)

设置一个非精确的周期性任务。
该方法也用于设置重复闹钟,与第二个方法相,不过其两个闹钟执行的间隔时间不是固定的而已。
它相对而言更省电(power-efficient)一些,因为系统可能会将几个差不多的闹钟合并为一个来执行,减少设备的唤醒次数。第三个参数intervalTime为闹钟间隔,内置的几个变量如下:
INTERVAL_DAY:设置闹钟,间隔一天
INTERVAL_HALF_DAY:设置闹钟,间隔半天
INTERVAL_FIFTEEN_MINUTES:设置闹钟,间隔15分钟
INTERVAL_HALF_HOUR:设置闹钟,间隔半个小时
INTERVAL_HOUR:设置闹钟,间隔一个小时

(4) void setRepeating(int type, long triggerAtTime, long interval,PendingIntentoperation)

设置一个周期性执行的定时服务。
第一个参数表示闹钟类型,
第二个参数表示闹钟首次执行时间,
第三个参数表示闹钟两次执行的间隔时间,
第四个参数表示闹钟响应动作。

(5) void setTime(long millis)

设置系统“墙”时钟。需要android.permission.SET_TIME.权限。

(6) void setTimeZone(StringtimeZone)

设置系统的默认时区。需要android.permission.SET_TIME_ZONE.权限。
如何使用AlarmManager使用AlarmManager共有三种方式, 都是通过PendingIntent。
getActivity(Context, int, Intent, int)
getBroadcast(Context, int, Intent, int)
getService(Context, int, Intent, int)
这边就举一个使用BroadCast的例子。
首先是创建一个BroadCast类,需要继承BroadCastReceiver, 如下:
package com.yfz;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class ActionBroadCast extends BroadcastReceiver {
private static int num = 0;
* @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
*/
@Override
public void onReceive(Context context, Intent intent) {
Log.e("ActionBroadCast", "New Message !" + num++);
}
}
启动AlarmManager, 这边就直接在Activity中启动了, 如下:
package com.yfz;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
public class AlarmTestActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, new Intent(this, ActionBroadCast.class), Intent.FLAG_ACTIVITY_NEW_TASK);
long now = System.currentTimeMillis();
am.setInexactRepeating(AlarmManager.RTC_WAKEUP, now, 3000, pi);
}
}
这边用Repeating的方式。 每隔3秒发一条广播消息过去。RTC_WAKEUP的方式,保证即使手机休眠了,也依然会发广播消息。
最后在AndroidManifest文件,注册一下Activity和BroadCast(实际使用中最好再加个filter,自己定义一个Action比较好)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yfz"
android:versionCode="1"
android:versionName="1.0" >
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".AlarmTestActivity" >
<receiver
android:name="ActionBroadCast">
</receiver>
</activity
</application>

定时器进程被杀死之后,事件不触发的解决方案

在Android中,AlarmManager提供了不受休眠状态的系统定时功能,其一般使用方法如下。
1、创建一个BroadcastReceiver类的子类,接收定时器事件:
public class MyReceiver extends BroadcastReceiver {
.....
}
2、在AndroidMenifest.xml中定义上述广播事件接收类的定义:
<receiver android:name=".MyReceiver">
3、在程序中在需要时设置定时器:
Intent intent = new Intent(context,MyReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP , SystemClock.elapsedRealtime() + ms, pendingIntent);
经过ms毫秒之后,MyReceiver会被调用,从而实现定时触发。
但是,上述实现存在一个问题:如果设置定时器的进程被杀死之后,定时器事件就不会触发。而在Android中,系统在需要时会自动终止后台进程,因此在定时过程中,进程被杀死的可能性是非常之大的,特别是在一些内存较少的设备中,基本上后台进程所设置的定时器很难被触发。
为了让定时器在进程被终止后还能触发,需要对上述实现做一个小的修改:在AndroidMefest.xml中如下定义广播接收类:
<receiver android:name=".MyReceiver" android:process=":newinst">

Android 定时执行任务 AlarmManager的使用

所有的定时任务在手机重启后会消失,如果需要重启后继续用,可以加个开机自启,然后重新设置.
AlarmManager可以在指定的时间执行指定的任务,最常用的功能就是利用这个类写闹铃程序。
下面开始学习AlarmManager用法。
首先,设置AlarmManager在指定的时间发送广播:
package com.pocketdigi.alarm;
import java.util.Calendar;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
public class AlarmActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Calendar c=Calendar.getInstance();
c.set(Calendar.YEAR,2011);
c.set(Calendar.MONTH,Calendar.JUNE);//也可以填数字,0-11,一月为0
c.set(Calendar.DAY_OF_MONTH, 28);
c.set(Calendar.HOUR_OF_DAY, 19);
c.set(Calendar.MINUTE, 50);
c.set(Calendar.SECOND, 0);
Intent intent=new Intent(this,AlarmReceiver.class);
PendingIntent pi=PendingIntent.getBroadcast(this, 0, intent,0);
AlarmManager am=(AlarmManager)getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi);
}
}
广播接收器:
package com.pocketdigi.alarm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("收到广播");
Intent it=new Intent(context,AlarmActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(it);
}
}
AndroidManifest.xml里加上receiver:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pocketdigi.alarm"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AlarmActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".AlarmReceiver" android:process=".abc"/>
</application>
</manifest>
注册广播接收器receiver,注意android:process,该值是广播进程的名字,不填默认是包名,但如果不填,在AlarmManager时间设为过去的时间时,会不停收到广播(死循环)前面加.或:

Android CountDownTimer倒计时器的使用

在一个TextView不断显示剩下的时间,代码如下:
[java] view plaincopy
private TextView vertifyView;
private CountDownTimer timer = new CountDownTimer(10000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
vertifyView.setText((millisUntilFinished / 1000) + "秒后可重发");
}
@Override
public void onFinish() {
vertifyView.setEnabled(true);
vertifyView.setText("获取验证码");
}
};
调用的时候很简单:timer.start();
在CountDownTimer timer = new CountDownTimer(10000, 1000)中,
第一个参数表示总时间,第二个参数表示间隔时间。
此例中就是每隔一秒会回调一次方法onTick,然后10秒之后会回调onFinish方法。

长时间不动,不允许黑屏

View.setKeepScreenOn(true);

Android 音量调节

获取各类系统音量

设置音量的方法也很简单,AudioManager提供了方法:
publicvoidsetStreamVolume(intstreamType,intindex,intflags)其中streamType有内置的常量,去文档里面就可以看到。
AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_VOICE_CALL );
int current = mAudioManager.getStreamVolume( AudioManager.STREAM_VOICE_CALL );
Log.d(“VIOCE_CALL”, “max : ” + max + ” current : ” + current);
max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_SYSTEM );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_SYSTEM );
Log.d(“SYSTEM”, “max : ” + max + ” current : ” + current);
max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_RING );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_RING );
Log.d(“RING”, “max : ” + max + ” current : ” + current);
max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_MUSIC );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_MUSIC );
Log.d(“MUSIC”, “max : ” + max + ” current : ” + current);
max = mAudioManager.getStreamMaxVolume( AudioManager.STREAM_ALARM );
current = mAudioManager.getStreamVolume( AudioManager.STREAM_ALARM );
Log.d(“ALARM”, “max : ” + max + ” current : ” + current);

多媒体音量控制

当开发多媒体应用或者游戏应用的时候,需要使用音量控制键来设置程序的音量大小。
在Android系统中有多中音频流,通过Activity中的函数 setVolumeControlStream(int streamType)可以设置该Activity中音量控制键控制的音频流,一般在onCreate函数中设置。
Android中有如下几种音频流(streamType是需要调整音量的类型):
AudioManager.STREAM_MUSIC /音乐回放即媒体音量/
AudioManager.STREAM_RING /铃声/
AudioManager.STREAM_ALARM /警报/
AudioManager.STREAM_NOTIFICATION /窗口顶部状态栏通知声/
AudioManager.STREAM_SYSTEM /系统/
AudioManager.STREAM_VOICECALL /通话 /
AudioManager.STREAM_DTMF /双音多频,不是很明白什么东西 /
AudioManager可以修改系统Android系统的音量,下面介绍几个AudioManager的几个音量调整方面的方法.
首先是得到AudioManager实例:
AudioManager am=(AudioManager)getSystemService(Context.AUDIO_SERVICE);
调整音量方法有两种,一种是渐进式,即手动按音量键一样,一步一步增加或减少,另一种是直接设置音量值

1、渐进式设置音量方法

public void adjustStreamVolume (int streamType, int direction, int flags)
am.adjustStreamVolume (AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
三个参数含义
第一个streamType是需要调整音量的类型,这里设的是媒体音量,可以是:
STREAM_ALARM 警报
STREAM_MUSIC 音乐回放即媒体音量
STREAM_NOTIFICATION 窗口顶部状态栏Notification,
STREAM_RING 铃声
STREAM_SYSTEM 系统
STREAM_VOICE_CALL 通话
STREAM_DTMF 双音多频,不是很明白什么东西
第二个direction,是调整的方向,增加或减少,可以是:
ADJUST_LOWER 降低音量
ADJUST_RAISE 升高音量
ADJUST_SAME 保持不变,这个主要用于向用户展示当前的音量
第三个flags是一些附加参数,只介绍两个常用的
FLAG_PLAY_SOUND 调整音量时播放声音
FLAG_SHOW_UI 调整时显示音量条,就是按音量键出现的那个
0 表示什么也没有

2、直接设置音量值的方法

public void setStreamVolume (int streamType, int index, int flags)
am.setStreamVolume(AudioManager.STREAM_MUSIC, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), AudioManager.FLAG_PLAY_SOUND);
am.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);//得到听筒模式的最大值
am.getStreamVolume(AudioManager.STREAM_VOICE_CALL);//得到听筒模式的当前值
第一个和第三个参数与上面的相同
第二个参数是一个音量的int值,getStreamMaxVolume(int streamType)得到的是该类型音量的最大值,可以根据这个值计算你需要的音量

设置音乐音量

SeekBar mMusicVolume;
AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); //获取当前音乐音量
mMusicVolume.setMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)); //SEEKBAR设置为音量的最大阶数
mMusicVolume.setProgress(mVolume); //设置seekbar为当前音量进度
mMusicVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0); //拖动seekbar时改变音量
}
});

通过设置改变默认音频流来调节媒体音量

setVolumeControlStream(AudioManager.STREAM_MUSIC);//设置为媒体,通常在onCreate中进行
setVolumeControlStream(AudioManager.STREAM_RING);//Android手机通常默认为铃声,用完后可恢复设置为铃声,通常在onDestroy中进行

重写onKeyDown调节媒体音量;/h4>
AudioManager audio = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
重写 Activity 的onKeyDown 方法
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
audio.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE,
AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_SHOW_UI);
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
audio.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER,
AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_SHOW_UI);
return true;
default:
break;
}
return super.onKeyDown(keyCode, event);
}

Android MediaPlayer详解

MediaPlayer player;//声明
player=new MediaPlayer();//实例化对象
player.reset();//重置为初始化状态
player.setDataSource(path);//设置播放文件路径,如sdcard/xxx.mp3
player.setLooping(true);//设置是循环播放
player.prepare();//重新播放时调用此接口
player.setVolume(setvoloum, setvoloum);//设置音量,float类型范围0~1
player.start();//开始播放

Android开发之蜂鸣提示音和震动提示的实现原理

1. 蜂鸣效果实现

1.准备一个 音频文件,如:beep.ogg。 ogg格式是声音压缩格式的一种,类似mp3这样。播放它,就产生了蜂鸣的效果。
2.为activity注册的默认音频通道。
  activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
 这里声明为 STREAM_MUSIC的通道,就是多媒体播放,注册后,我们使用 手机上的音量大小键就可以调节播放的声音大小。
如果不设定这个通道的话,我们的这个activity默认音量按钮处理将作用于 手机铃音的大小。
3.检查当前的 铃音模式,或者成为 情景模式。
  说明:getRingerMode() ——返回当前的铃声模式。如RINGER_MODE_NORMAL(普通)、RINGER_MODE_SILENT(静音)、RINGER_MODE_VIBRATE(震动)
代码如下:
  //如果当前是铃音模式,则继续准备下面的 蜂鸣提示音操作,如果是静音或者震动模式。就不要继续了。因为用户选择了无声的模式,我们就也不要出声了。
AudioManager audioService = (AudioManager) activity
.getSystemService(Context.AUDIO_SERVICE);
if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
shouldPlayBeep = false;
}
4.初始化MediaPlayer对象,指定播放的声音 通道为 STREAM_MUSIC,这和上面的步骤一致,指向了同一个通道。
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
注册事件。当播放完毕一次后,重新指向流文件的开头,以准备下次播放。
代码如下:
mediaPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer player) {
player.seekTo(0);
}
});
设定数据源,并准备播放
代码如下:
AssetFileDescriptor file = activity.getResources().openRawResourceFd(
R.raw.beep);
try {
mediaPlayer.setDataSource(file.getFileDescriptor(),
file.getStartOffset(), file.getLength());
file.close();
mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mediaPlayer.prepare();
} catch (IOException ioe) {
Log.w(TAG, ioe);
mediaPlayer = null;
}
return mediaPlayer;
5.开始播放
代码如下:
if (playBeep && mediaPlayer != null) {
mediaPlayer.start();
}
-----------------------------------------------------------------

2. 震动效果实现

分两步:
1.声明权限
  在AndroidManifest.xml 里写
代码如下:
  <uses-permission android:name="android.permission.VIBRATE"/>
2.获得震动服务。
代码如下:
  Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
3.启动震动。
代码如下:
  vibrator.vibrate(VIBRATE_DURATION);
代码如下:
public void playBeepSoundAndVibrate() {
if (enableVibrate) {
Vibrator vibrator = (Vibrator) activity
.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_DURATION);
}
}

资源获取与语言改变

public void chooseLanguage(Locale locale) {
Resources resources = getResources();//获得res资源对象
Configuration config = resources.getConfiguration();//获得设置对象
DisplayMetrics dm = resources .getDisplayMetrics();//获得屏幕参数:主要是分辨率,像素等。
config.locale = locale; //简体中文
resources.updateConfiguration(config, dm);
}
示例:app用户根据自己的语言喜好,设置app语言。语言设置只针对本app,并在下次启动应用时保留前一次启动设置。
更新语言:
public static void changeAppLanguage(Resources resources, String lanAtr) {
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
if (lanAtr.equals("ru_RU")) {
config.locale = new Locale("ru", "RU");
} else if (lanAtr.equals("en_US")) {
config.locale = Locale.ENGLISH;
} else if (lanAtr.equals("pt")) {
config.locale = new Locale("pt");
} else {
config.locale = Locale.getDefault();
}
resources.updateConfiguration(config, dm);
}sharePreferences存入设置语言:
Sharences sharedPreferences = getActivity().getSharedPrefeivity().getPackageName(), 0);
sharedPreferences.edit().putString("language", lanAtr).commit();语言更新后,对于之前出现且目前仍旧存活的activity,语言设置是不生效的。可以通过重启对应的activity,让语言及时生效。
private void restart() {
Intent it = new Intent(getActivity(), MainActivity.class); //MainActivity是你想要重启的activity
it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getActivity().startActivity(it);
}NOTE:
一般,从用户体验角度讲,语言设置功能入口会放在App的前几层,如果入口太深,导致用户无法快速找到语言设置入口,并且如果要讲应用重启的话,用户行为操作记录会比较麻烦。
重启对应Activity有几种方式:
如果用户进入语言设置需要太多的层级,或者在操作语言设置之前操作的其他行为,APP想保存的,那可以通过广播的方式(sendBroadcast()),语言改变时发送广播,所有activity接受到广播后(BroadcastReceiver),都进行重启操作;
如果允许用户设置语言后,app回到主目录,这样就简单很多,直接调用上面的restart()方法即可。
重启singleTask activity:
如果你的启动activity是singleTask,向上面那样重启,语言还是不生效的。这种情况如何呢?可以通过了解、利用其生命周期来解决,在切回singleTask属性的activity时,activity会调用onNewIntent()方法。 重写该方法就可以。以下是一种解决方法,先finish自己,然后重启自己。
@Override
protected void onNewIntent(Intent intent) {
if (intent.getAction() == null) {
finish();
Intent i = new Intent(this, MainActivity.class);
startActivity(i);
} else {
}
}

通过字符串变量名获取资源ID

有几十张图片,存放在drawable目录下,通过Web Service可以取到要显示的图片文件名,通过这个文件名,来展示相应的图片。
两种方法:第一种是最笨的,写几十个if,else if,对比返回的文件名,确定要显示的图片的Resources ID.
第二种方法,用反射,这里介绍的就是第二种方法。
当我们在drawable目录下添加图片时,ADT会自动以该图片的文件名(不包括扩展名)为静态变量名,建立索引,打开gen目录下的R.java文件,可以发现,所有图片的索引都是R.drawable类下的静态变量。
我们就可通过Web Service返回的文件名(当然,要去掉扩展名的),用Java的反射机制,取到该静态变量的值,就是Resources ID,这样我们在程序中就可以直接使用了。
public int getBigIconResourceId() {
R.drawable drawables=new R.drawable();
int resId=0x7f02000b;
try {
Field field=R.drawable.class.getField("big_1");
resId=(Integer)field.get(drawables);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return resId;
}

Android应用权限详解

一、Android应用的四种安装方式

1、通过系统应用PackageInstaller.apk进行安装,安装过程中会让用户确认
2、系统程序安装:在开机的时候自动进行安装/system/app下面的APK文件,没有安装界面
3、通过Google 的Android market进行安装,安装界面就是Market的界面,APK从网络获得
4、使用adb(Android Debug Bridge)进行安装,没有用户界面

二、PackageInstaller应用剖析

源代码目录$源码目录$ \packages\apps\PackageInstaller
1、Manifest.xml
1.1、package="com.android.packageinstaller"
应用程序的安装包名,在/data/data、DDMS中看到的进程的名称都是这个属性的值,R文件就生成在这个包下
1.2、<original-package android:name="com.android.packageinstaller" />
一般用于指定源码的目录,不对R文件的生成构成影响,这个值如果与<manifest>中的Package的属性值一致,则在<alication>注册的activity、service、provider、receiver等内容必须使用全包名,而不能使用.ClassName的方式进行引用
1.3、权限
允许程序安装应用
允许程序删除应用
允许应用清除应用缓存
允许应用访问电话状态
允许应用清除应用的用户数据
1.4、<application android:allowBackup="false">
是否允许备份应用的数据,默认是true,当备份数据的时候,它的数据会被备份下来。如果设为false,那么绝对不会备份应用的数据,即使是备份整个系统。
1.5、<activity android:excludeFromRecents="true">
任务是否在发起这个活动应该被排除在最近使用的应用程序的列表("最近的应用程序")。那是,当这个活动是根活动的一个新任务,这个属性决定了任务不应该出现在列表中最近的应用程序。设置"true"如果这个任务应该被排除在名单,设置"false"如果它应该被包括。默认值为"false"。

三、Android应用权限大全

访问登记属性
android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限
获取错略位置
android.permission.ACCESS_COARSE_LOCATION,通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米
获取精确位置
android.permission.ACCESS_FINE_LOCATION,通过GPS芯片接收卫星的定位信息,定位精度达10米以内
访问定位额外命令
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS,允许程序访问额外的定位提供者指令
获取模拟定位信息
android.permission.ACCESS_MOCK_LOCATION,获取模拟定位信息,一般用于帮助开发者调试应用
获取网络状态
android.permission.ACCESS_NETWORK_STATE,获取网络信息状态,如当前的网络连接是否有效
访问Surface Flinger
android.permission.ACCESS_SURFACE_FLINGER,Android平台上底层的图形显示支持,一般用于游戏或照相机预览界面和底层模式的屏幕截图
获取WiFi状态
android.permission.ACCESS_WIFI_STATE,获取当前WiFi接入的状态以及WLAN热点的信息
账户管理
android.permission.ACCOUNT_MANAGER,获取账户验证信息,主要为GMail账户信息,只有系统级进程才能访问的权限
验证账户
android.permission.AUTHENTICATE_ACCOUNTS,允许一个程序通过账户验证方式访问账户管理ACCOUNT_MANAGER相关信息
电量统计
android.permission.BATTERY_STATS,获取电池电量统计信息
绑定小插件
android.permission.BIND_APPWIDGET,允许一个程序告诉appWidget服务需要访问小插件的数据库,只有非常少的应用才用到此权限
绑定设备管理
android.permission.BIND_DEVICE_ADMIN,请求系统管理员接收者receiver,只有系统才能使用
绑定输入法
android.permission.BIND_INPUT_METHOD ,请求InputMethodService服务,只有系统才能使用
绑定RemoteView
android.permission.BIND_REMOTEVIEWS,必须通过RemoteViewsService服务来请求,只有系统才能用
绑定壁纸
android.permission.BIND_WALLPAPER,必须通过WallpaperService服务来请求,只有系统才能用
使用蓝牙
android.permission.BLUETOOTH,允许程序连接配对过的蓝牙设备
蓝牙管理
android.permission.BLUETOOTH_ADMIN,允许程序进行发现和配对新的蓝牙设备
变成砖头
android.permission.BRICK,能够禁用手机,非常危险,顾名思义就是让手机变成砖头
应用删除时广播
android.permission.BROADCAST_PACKAGE_REMOVED,当一个应用在删除时触发一个广播
收到短信时广播
android.permission.BROADCAST_SMS,当收到短信时触发一个广播
连续广播
android.permission.BROADCAST_STICKY,允许一个程序收到广播后快速收到下一个广播
WAP PUSH广播
android.permission.BROADCAST_WAP_PUSH,WAP PUSH服务收到后触发一个广播
拨打电话
android.permission.CALL_PHONE,允许程序从非系统拨号器里输入电话号码
通话权限
android.permission.CALL_PRIVILEGED,允许程序拨打电话,替换系统的拨号器界面
拍照权限
android.permission.CAMERA,允许访问摄像头进行拍照
改变组件状态
android.permission.CHANGE_COMPONENT_ENABLED_STATE,改变组件是否启用状态
改变配置
android.permission.CHANGE_CONFIGURATION,允许当前应用改变配置,如定位
改变网络状态
android.permission.CHANGE_NETWORK_STATE,改变网络状态如是否能联网
改变WiFi多播状态
android.permission.CHANGE_WIFI_MULTICAST_STATE,改变WiFi多播状态
改变WiFi状态
android.permission.CHANGE_WIFI_STATE,改变WiFi状态
清除应用缓存
android.permission.CLEAR_APP_CACHE,清除应用缓存
清除用户数据
android.permission.CLEAR_APP_USER_DATA,清除应用的用户数据
底层访问权限
android.permission.CWJ_GROUP,允许CWJ账户组访问底层信息
手机优化大师扩展权限
android.permission.CELL_PHONE_MASTER_EX,手机优化大师扩展权限
控制定位更新
android.permission.CONTROL_LOCATION_UPDATES,允许获得移动网络定位信息改变
删除缓存文件
android.permission.DELETE_CACHE_FILES,允许应用删除缓存文件
删除应用
android.permission.DELETE_PACKAGES,允许程序删除应用
电源管理
android.permission.DEVICE_POWER,允许访问底层电源管理
应用诊断
android.permission.DIAGNOSTIC,允许程序到RW到诊断资源
禁用键盘锁
android.permission.DISABLE_KEYGUARD,允许程序禁用键盘锁
转存系统信息
android.permission.DUMP,允许程序获取系统dump信息从系统服务
状态栏控制
android.permission.EXPAND_STATUS_BAR,允许程序扩展或收缩状态栏
工厂测试模式
android.permission.FACTORY_TEST,允许程序运行工厂测试模式
使用闪光灯
android.permission.FLASHLIGHT,允许访问闪光灯
强制后退
android.permission.FORCE_BACK,允许程序强制使用back后退按键,无论Activity是否在顶层
访问账户Gmail列表
android.permission.GET_ACCOUNTS,访问GMail账户列表
获取应用大小
android.permission.GET_PACKAGE_SIZE,获取应用的文件大小
获取任务信息
android.permission.GET_TASKS,允许程序获取当前或最近运行的应用
允许全局搜索
android.permission.GLOBAL_SEARCH,允许程序使用全局搜索功能
硬件测试
android.permission.HARDWARE_TEST,访问硬件辅助设备,用于硬件测试
注射事件
android.permission.INJECT_EVENTS,允许访问本程序的底层事件,获取按键、轨迹球的事件流
安装定位提供
android.permission.INSTALL_LOCATION_PROVIDER,安装定位提供
安装应用程序
android.permission.INSTALL_PACKAGES,允许程序安装应用
内部系统窗口
android.permission.INTERNAL_SYSTEM_WINDOW,允许程序打开内部窗口,不对第三方应用程序开放此权限
访问网络
android.permission.INTERNET,访问网络连接,可能产生GPRS流量
结束后台进程
android.permission.KILL_BACKGROUND_PROCESSES,允许程序调用killBackgroundProcesses(String).方法结束后台进程
管理账户
android.permission.MANAGE_ACCOUNTS,允许程序管理AccountManager中的账户列表
管理程序引用
android.permission.MANAGE_APP_TOKENS,管理创建、摧毁、Z轴顺序,仅用于系统
高级权限
android.permission.MTWEAK_USER,允许mTweak用户访问高级系统权限
社区权限
android.permission.MTWEAK_FORUM,允许使用mTweak社区权限
软格式化
android.permission.MASTER_CLEAR,允许程序执行软格式化,删除系统配置信息
修改声音设置
android.permission.MODIFY_AUDIO_SETTINGS,修改声音设置信息
修改电话状态
android.permission.MODIFY_PHONE_STATE,修改电话状态,如飞行模式,但不包含替换系统拨号器界面
格式化文件系统
android.permission.MOUNT_FORMAT_FILESYSTEMS,格式化可移动文件系统,比如格式化清空SD卡
挂载文件系统
android.permission.MOUNT_UNMOUNT_FILESYSTEMS,挂载、反挂载外部文件系统
允许NFC通讯
android.permission.NFC,允许程序执行NFC近距离通讯操作,用于移动支持
永久Activity
android.permission.PERSISTENT_ACTIVITY,创建一个永久的Activity,该功能标记为将来将被移除
处理拨出电话
android.permission.PROCESS_OUTGOING_CALLS,允许程序监视,修改或放弃播出电话
读取日程提醒
android.permission.READ_CALENDAR,允许程序读取用户的日程信息
读取联系人
android.permission.READ_CONTACTS,允许应用访问联系人通讯录信息
屏幕截图
android.permission.READ_FRAME_BUFFER,读取帧缓存用于屏幕截图
读取收藏夹和历史记录
com.android.browser.permission.READ_HISTORY_BOOKMARKS,读取浏览器收藏夹和历史记录
读取输入状态
android.permission.READ_INPUT_STATE,读取当前键的输入状态,仅用于系统
读取系统日志
android.permission.READ_LOGS,读取系统底层日志
读取电话状态
android.permission.READ_PHONE_STATE,访问电话状态
读取短信内容
android.permission.READ_SMS,读取短信内容
读取同步设置
android.permission.READ_SYNC_SETTINGS,读取同步设置,读取Google在线同步设置
读取同步状态
android.permission.READ_SYNC_STATS,读取同步状态,获得Google在线同步状态
重启设备
android.permission.REBOOT,允许程序重新启动设备
开机自动允许
android.permission.RECEIVE_BOOT_COMPLETED,允许程序开机自动运行
接收彩信
android.permission.RECEIVE_MMS,接收彩信
接收短信
android.permission.RECEIVE_SMS,接收短信
接收Wap Push
android.permission.RECEIVE_WAP_PUSH,接收WAP PUSH信息
录音
android.permission.RECORD_AUDIO,录制声音通过手机或耳机的麦克
排序系统任务
android.permission.REORDER_TASKS,重新排序系统Z轴运行中的任务
结束系统任务
android.permission.RESTART_PACKAGES,结束任务通过restartPackage(String)方法,该方式将在外来放弃
发送短信
android.permission.SEND_SMS,发送短信
设置Activity观察其
android.permission.SET_ACTIVITY_WATCHER,设置Activity观察器一般用于monkey测试
设置闹铃提醒
com.android.alarm.permission.SET_ALARM,设置闹铃提醒
设置总是退出
android.permission.SET_ALWAYS_FINISH,设置程序在后台是否总是退出
设置动画缩放
android.permission.SET_ANIMATION_SCALE,设置全局动画缩放
设置调试程序
android.permission.SET_DEBUG_APP,设置调试程序,一般用于开发
设置屏幕方向
android.permission.SET_ORIENTATION,设置屏幕方向为横屏或标准方式显示,不用于普通应用
设置应用参数
android.permission.SET_PREFERRED_APPLICATIONS,设置应用的参数,已不再工作具体查看addPackageToPreferred(String) 介绍
设置进程限制
android.permission.SET_PROCESS_LIMIT,允许程序设置最大的进程数量的限制
设置系统时间
android.permission.SET_TIME,设置系统时间
设置系统时区
android.permission.SET_TIME_ZONE,设置系统时区
设置桌面壁纸
android.permission.SET_WALLPAPER,设置桌面壁纸
设置壁纸建议
android.permission.SET_WALLPAPER_HINTS,设置壁纸建议
发送永久进程信号
android.permission.SIGNAL_PERSISTENT_PROCESSES,发送一个永久的进程信号
状态栏控制
android.permission.STATUS_BAR,允许程序打开、关闭、禁用状态栏
访问订阅内容
android.permission.SUBSCRIBED_FEEDS_READ,访问订阅信息的数据库
写入订阅内容
android.permission.SUBSCRIBED_FEEDS_WRITE,写入或修改订阅内容的数据库
显示系统窗口
android.permission.SYSTEM_ALERT_WINDOW,显示系统窗口
更新设备状态
android.permission.UPDATE_DEVICE_STATS,更新设备状态
使用证书
android.permission.USE_CREDENTIALS,允许程序请求验证从AccountManager
使用SIP视频
android.permission.USE_SIP,允许程序使用SIP视频服务
使用振动
android.permission.VIBRATE,允许振动
唤醒锁定
android.permission.WAKE_LOCK,允许程序在手机屏幕关闭后后台进程仍然运行
写入GPRS接入点设置
android.permission.WRITE_APN_SETTINGS,写入网络GPRS接入点设置
写入日程提醒
android.permission.WRITE_CALENDAR,写入日程,但不可读取
写入联系人
android.permission.WRITE_CONTACTS,写入联系人,但不可读取
写入外部存储
android.permission.WRITE_EXTERNAL_STORAGE,允许程序写入外部存储,如SD卡上写文件
写入Google地图数据
android.permission.WRITE_GSERVICES,允许程序写入Google Map服务数据
写入收藏夹和历史记录
com.android.browser.permission.WRITE_HISTORY_BOOKMARKS,写入浏览器历史记录或收藏夹,但不可读取
读写系统敏感设置
android.permission.WRITE_SECURE_SETTINGS,允许程序读写系统安全敏感的设置项
读写系统设置
android.permission.WRITE_SETTINGS,允许读写系统设置项
编写短信
android.permission.WRITE_SMS,允许编写短信
写入在线同步设置
android.permission.WRITE_SYNC_SETTINGS,写入Google在线同步设置

Android 广播大全 Intent Action 事件

Intent.ACTION_AIRPLANE_MODE_CHANGED;
Intent.ACTION_BATTERY_CHANGED;
Intent.ACTION_BATTERY_LOW;
Intent.ACTION_BATTERY_OKAY;
Intent.ACTION_BOOT_COMPLETED;
Intent.ACTION_CAMERA_BUTTON;
Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
Intent.ACTION_CONFIGURATION_CHANGED;
Intent.ACTION_DATE_CHANGED;
Intent.ACTION_DEVICE_STORAGE_LOW;
Intent.ACTION_DEVICE_STORAGE_OK;
Intent.ACTION_DOCK_EVENT;
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE;
Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
Intent.ACTION_GTALK_SERVICE_CONNECTED;
Intent.ACTION_GTALK_SERVICE_DISCONNECTED;
Intent.ACTION_HEADSET_PLUG;
Intent.ACTION_INPUT_METHOD_CHANGED;
Intent.ACTION_LOCALE_CHANGED;
Intent.ACTION_MANAGE_PACKAGE_STORAGE;
Intent.ACTION_MEDIA_BAD_REMOVAL;
Intent.ACTION_MEDIA_BUTTON;
Intent.ACTION_MEDIA_CHECKING;
Intent.ACTION_MEDIA_EJECT;
Intent.ACTION_MEDIA_MOUNTED;
Intent.ACTION_MEDIA_NOFS;
Intent.ACTION_MEDIA_REMOVED;
Intent.ACTION_MEDIA_SCANNER_FINISHED;
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE;
Intent.ACTION_MEDIA_SCANNER_STARTED;
Intent.ACTION_MEDIA_SHARED;
Intent.ACTION_MEDIA_UNMOUNTABLE;
Intent.ACTION_MEDIA_UNMOUNTED
Intent.ACTION_NEW_OUTGOING_CALL;
Intent.ACTION_PACKAGE_ADDED;
Intent.ACTION_PACKAGE_CHANGED;
Intent.ACTION_PACKAGE_DATA_CLEARED;
Intent.ACTION_PACKAGE_INSTALL;
Intent.ACTION_PACKAGE_REMOVED;
Intent.ACTION_PACKAGE_REPLACED;
Intent.ACTION_PACKAGE_RESTARTED;
Intent.ACTION_POWER_CONNECTED;
Intent.ACTION_POWER_DISCONNECTED;
Intent.ACTION_PROVIDER_CHANGED;
Intent.ACTION_REBOOT;
Intent.ACTION_SCREEN_OFF;
Intent.ACTION_SCREEN_ON;
Intent.ACTION_SHUTDOWN;
Intent.ACTION_TIMEZONE_CHANGED;
Intent.ACTION_TIME_CHANGED;
Intent.ACTION_TIME_TICK;
Intent.ACTION_UID_REMOVED;
Intent.ACTION_UMS_CONNECTED;
Intent.ACTION_UMS_DISCONNECTED;
Intent.ACTION_USER_PRESENT;
Intent.ACTION_WALLPAPER_CHANGED;

BroadCastReceiver 简介

BroadCastReceiver 源码位于: framework/base/core/java/android.content.BroadcastReceiver.java
广播接收者( BroadcastReceiver )用于接收广播 Intent ,广播 Intent 的发送是通过调用 Context.sendBroadcast() 、 Context.sendOrderedBroadcast() 来实现的。通常一个广播 Intent 可以被订阅了此 Intent 的多个广播接收者所接收。
广播是一种广泛运用的在应用程序之间传输信息的机制 。而 BroadcastReceiver 是对发送出来的广播进行过滤接收并响应的一类组件;
来自普通应用程序,如一个应用程序通知其他应用程序某些数据已经下载完毕。
BroadcastReceiver 自身并不实现图形用户界面,但是当它收到某个通知后, BroadcastReceiver 可以启动 Activity 作为响应,或者通过 NotificationMananger 提醒用户,或者启动 Service 等等。
BroadCastReceiver 的机制
1. 机制
在 Android 里面有各种各样的广播,比如电池的使用状态,电话的接收和短信的接收都会产生一个广播,应用程序开发者也可以监听这些广播并做出程序逻辑的处理。如图:
2. 实现
用接收短信举例:
第一种方式 :
实现
public class MyBroadcastReceiver extends BroadcastReceiver {
String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED" ;
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals( SMS_RECEIVED )) {
}
}
}
系统注册:在 AndroidManifest.xml 中注册
< receiver android:name = ".MyBroadcastReceiver" >
< intent-filter android:priority = "1000" >
< action android:name = " android.provider.Telephony.SMS_RECEIVED" />
</ intent-filter >
</ receiver > 当然了需要权限 :
< uses-permission android:name = "android.permission.RECEIVE_SMS" />
< uses-permission android:name = "android.permission.SEND_SMS" />
第二种方式:
private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
}
};
代码中注册:
IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED " );
registerReceiver( mBatteryInfoReceiver , intentFilter);
3. 生命周期
描述了 Android 中广播的生命周期,其次它并不像 Activity 一样复杂,运行原理很简单如下图:
生命周期只有十秒左右,如果在 onReceive() 内做超过十秒内的事情,就会报错 。
每次广播到来时 , 会重新创建 BroadcastReceiver 对象 , 并且调用 onReceive() 方法 , 执行完以后 , 该对象即被销毁 . 当 onReceive() 方法在 10 秒内没有执行完毕, Android 会认为该程序无响应 . 所以在
BroadcastReceiver 里不能做一些比较耗时的操作 , 否侧会弹出 ANR(Application No
Response) 的对话框 . 。(如图):
怎么用好 BroadcastReceiver ?
如果需要完成一项比较耗时的工作 , 应该通过发送 Intent 给 Service, 由 Service 来完成 . 这里不能使用子线程来解决 , 因为 BroadcastReceiver 的生命周期很短 , 子线程可能还没有结束
BroadcastReceiver 就先结束了 .BroadcastReceiver 一旦结束 , 此时 BroadcastReceiver 的
所在进程很容易在系统需要内存时被优先杀死 , 因为它属于空进程 ( 没有任何活动组件的进程 ). 如果它的宿主进程被杀死 , 那么正在工作的子线程也会被杀死 . 所以采用子线程来解决是不可靠的 .
广播类型及广播的收发
广播类型
普通广播 (Normal broadcasts)
发送一个广播,所以监听该广播的广播接收者都可以监听到改广播。
异步广播 , 当处理完之后的Intent ,依然存在,这时候registerReceiver(BroadcastReceiver, IntentFilter) 还能收到他的值,直到你把它去掉 , 不能将处理结果传给下一个接收者 , 无法终止广播 .
有序广播 (Ordered broadcasts)
按照接收者的优先级顺序接收广播 , 优先级别在 intent-filter 中的 priority 中声明 ,-1000 到
1000 之间 , 值越大 , 优先级越高 . 可以终止广播意图的继续传播 . 接收者可以篡改内容 .
广播的收发
该组件接收被广播的 intent,Context 可以通过 sendBroadcast() 和 sendOrderedBroadcast()
方法实现广播的发送 .
首先在需要发送信息的地方 ,把要发送的信息和用于过滤的信息 ( 如 Action 、 Category) 装入一个 Intent 对象 ,然后通过调用 Context.sendBroadcast() 、 sendOrderBroadcast() 或 sendStickyBroadcast() 方法,把 Intent 对象以广播方式发送出去。
使用 sendBroadcast() 或 sendStickyBroadcast() 方法发出去的 Intent ,所有满足条件的 BroadcastReceiver 都会随机地执行其 onReceive() 方法
普通广播的发送和接收:
sendBroadcast(intent);
Intent intent = new Intent( "cn.lenovo.yangguangf " );
sendBroadcast(intent);
priority :这个是 AndroidManifest.xml 中 intent-filter 的参数。
< receiver android:name = ".MyBroadcastReceiver" >
< intent-filter android:priority = "1000" >
< action android:name = "cn.lenovo.yangguangfu" />
</ intent-filter >
</ receiver >
sendOrderedBroadcast(intent, receiverPermission);
1 ,他决定该广播的级别,级别数值是在 -1000 到 1000 之间 , 值越大 , 优先级越高;
2 ,同级别接收是先后是随机的;级别低的收到广播;
3 ,在 android 系统中只要监听该广播的接收者,都能够收到 sendBroadcast(intent) 发出的广播 ;
3 ,不能截断广播的继续传播,
4 ,实验现象,在这个方法发来的广播中,代码注册方式中,收到的广播的先后和注明优先级最高的他们的先后是随机。如果都没有优先级,代码注册收到为最先。
有序广播的发送和接收:
sendOrderedBroadcast(intent, receiverPermission);
sendOrderedBroadcast(intent, receiverPermission, resultReceiver,
scheduler, initialCode, initialData, initialExtras)
意图,广播,所有匹配的这一意图将接收机接收广播。
receiverPermission 这是权限,一个接收器必须持以接收您的广播。如果为 null ,不经许可的要求。
resultReceiver 您自己 BroadcastReceiver 来当作最后的广播接收器。
调度自定义处理程序,用以安排 resultReceiver 回调 ; 如果为 null 将语境中的主线程举行。
initialCode 一种结果代码的初始值。通常为 Activity.RESULT_OK 。这个值是 -1 ;为其他 int 型也可以,如 0,1,2 ;
initialData 一种结果数据的初始值。通常情况下为空 , 是 String 类型 ;
initialExtras 一种结果额外的初始值。通常情况下为空 , 是 Bundle;
intent The Intent to broadcast; all receivers matching this Intent will receive the broadcast.
receiverPermission String naming a permissions that a receiver must hold in order to receive your broadcast. If null, no permission is required.
resultReceiver Your own BroadcastReceiver to treat as the final receiver of the broadcast.
scheduler A custom Handler with which to schedule the resultReceiver callback; if null it will be scheduled in the Context's main thread.
initialCode An initial value for the result code. Often Activity.RESULT_OK.
initialData An initial value for the result data. Often null.
initialExtras An initial value for the result extras. Often null.
1, 该广播的级别有级别之分,级别数值是在 -1000 到 1000 之间 , 值越大 , 优先级越高;
2, 同级别接收是先后是随机的,再到级别低的收到广播;
3, 同级别接收是先后是随机的,如果先接收到的把广播截断了,同级别的例外的接收者是无法收到该广播的。( abortBroadcast() )
4 ,能截断广播的继续传播,高级别的广播收到该广播后,可以决定把该钟广播是否截断掉。
5 ,实验现象,在这个方法发来的广播中,代码注册方式中,收到广播先后次序为:注明优先级的、代码注册的、没有优先级的;如果都没有优先级,代码注册收到为最先。
异步广播的发送和接收:
sendStickyBroadcast(intent);
当处理完之后的Intent ,依然存在,直到你把它去掉。
发这个广播需要权限<uses-permission android:name="android.permission.BROADCAST_STICKY" />
去掉是用这个方法removeStickyBroadcast(intent); 但别忘了在执行这个方法的应用里面 AndroidManifest.xml 同样要加上面的权限;
sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,
initialCode, initialData, initialExtras)
这个方法具有有序广播的特性也有异步广播的特性;
发送这个广播要: <uses-permission android:name="android.permission.BROADCAST_STICKY" /> 这个权限。才能使用这个方法。如果您并不拥有该权限,将抛出 SecurityException 的。
实验现象( sendStickyOrderedBroadcast ()中),在这个方法发来的广播中,代码注册方式中,收到广播先后次序为:注明优先级的、代码注册的、没有优先级的;如果都没有优先级,代码注册收到为最先。
广播注册与注销
代码中注册广播:
注册广播方法一: registerReceiver(BroadcastReceiver receiver, IntentFilter filter) ,第一个参数是我们要处理广播的 BroadcastReceiver (广播接收者,可以是系统的,也可以是自定义的);第二个参数是意图过滤器。
注册广播方法二: registerReceiver(receiver, filter, broadcastPermission, scheduler) ,第一个参数是 BroadcastReceiver (广播接收者,可以是系统的,也可以是自定义的);第二个参数是意图过滤器;第三个参数是广播权限;第四个参数是 Hander ;
注意:权限重复现象,如果功能清单文件里注册了权限,在该方法再注册,则 receiver 无法收到广播,如果 功能清单文件里没有注册了权限,该方法注册也无法收到。当该方法没有注册权限,功能清单里注册的时候, receiver 能收到广播。
总结:在 Activity 中代码注册广播建议在: onResume() 中注册;
思维拓展: 1 ,如果在代码调用 registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 十次( receiver , filter 的参数是同一参数),那么是否当该广播发送来的时候会收到十次呢?
2 ,注销是否也要注销十次才能把广播全部注销呢?
系统中注册广播:(在 AndroidManifest.xml 中 )
< receiver android:name = ".MyBroadcastReceiver" >
< intent-filter android:priority = "900" >
< action android:name = "cn.lenovo.yangguangfu" />
</ intent-filter >
</ receiver >
有时候还要根据发送广播是否指定权限,来决定是否要权限;
广播注销
在 Activity 中代码注销广播建议在: onPuase() 中注销;
不要这这里面注销 Activity.onSaveInstanceState(), 因为这个方法是保存 Intent 状态的。
BroadCastReceiver 的 API
abortBroadcast ():
这个方法可以截获由 sendOrderedBroadcast () 发送来的 广播,让其它广播接收者无法收到这个广播。
clearAbortBroadcast ()
这个方法是针对上面的 abortBroadcast() 方法的,用于取消截获广播。这样它的下一级广播接收者就能够收到该广播了。
getAbortBroadcast ()
这个方法作用是:判断是否调用了 abortBroadcast (),如果先调用 abortBroadcast (),接着再调用 getAbortBroadcast (),将返回 true; 如果在调用 abortBroadcast() 、 clearAbortBroadcast ()
getAbortBroadcast (),将返回 false;
public final boolean getDebugUnregister ()
Since: API Level 1
Return the last value given to setDebugUnregister(boolean) .
getResultCode ()
如果用下面四个方法发送得广播,返回码为: -1 ;
如果用下面两个方法发送得广播,返回码为:根据你设置 initialCode 的数字是多少就是多少;
getResultData ()
得到发送广播时设置的 initialData 的数据;
getResultExtras (boolean makeMap)
If true then a new empty Map will be made for you if the current Map is null; if false you should be prepared to receive a null Map.
得到由
sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,
中 initialExtras 传入的参数。
实验:我用上面两个方法发了 initialExtras (这个一个 Bundle )传入的参数时,只要不为空,那么 makeMap 是否为 true 和 false 都能够得到数据。
isInitialStickyBroadcast ()
Returns true if the receiver is currently processing the initial value of a sticky broadcast -- that is, the value that was last broadcast and is currently held in the sticky cache, so this is not directly the result of a broadcast right now.
如果广播接收者是目前处理的一个宿主的广播的初始值,将返回 true , - 也就是说,这个值是最后的广播出的值,目前正在举行的宿主缓存,所以这并不是直接导致了现在的广播。
实验:在第三个应用中调用这个方法,无论你用哪种方式发送广播,这个方法得到的总是 false ;在发送广播 的 resultReceiver 广播接收者里面调用,得到的也是 false ;
isOrderedBroadcast ()
sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,
initialCode, initialData, initialExtras)
上面这个方法发送时,得到的是 true;
判断是否是有序广播;
onReceive (Context context, Intent intent)
public IBinder peekService (Context myContext, Intent service)
Provide a binder to an already-running service. This method is synchronous and will not start the target service if it is not present, so it is safe to call from onReceive.
Parameters:
myContext The Context that had been passed to onReceive(Context, Intent)
service The Intent indicating the service you wish to use. See Context.startService(Intent) for more information.
setDebugUnregister (boolean debug)
Control inclusion of debugging help for mismatched calls to {@ Context#registerReceiver(BroadcastReceiver, IntentFilter) Context.registerReceiver()}. If called with true, before given to registerReceiver(), then the callstack of the following Context.unregisterReceiver()
call is retained, to be printed if a later incorrect unregister call is made. Note that doing this requires retaining information about the BroadcastReceiver for

任务栏

Broadcast Receiver顾名思义是指广播接收器,它和事件处理机制类似,只不过事件处理机制是程序组件级别的(例如,某个按钮单击事件),而广播事件处理机制是系统级别的。我们可是使用Intent来启动一个程序组件,还可以实用sendBroadcast()犯非法来发起一个系统级别的时间广播来传递消息。我们可以在应用程序中实现Broadcast Receiver 来监听和响应这些广播的Intent.
事件的广播比较简单同样还是构建Intent对象。然后调用sendBroadcast()方法将广播发出。时间的接收是通过定义一个继承BroadcastReceiver的类来实现的。继承后覆盖其onReceive()方法,在该方法中相依时间。
Android系统中定义了很多标准的Broadcast Action 来响应系统广播事件。例如,ACTION_TIME_CHANGED(时间改变触发器),ACTION_BOOT_COMPLETED(系统启动完成后触发),ACTION_PACKAGE_ADDED(添加包时触发),ACTION_BATTERY_CHANGED(电量低时触发)。当然,我们也可以自己定义Broadcast Receiver接收广播事件。
下面以一个Notification的实例来演示一下 Broadcast Receiver组件并没有提供可视化的界面来显示广播信息,这里我们使用Notification和NotificationManager来实现可视化的信息显示,通过使用它们我们可以显示广播信息的内容,图标以及振动等信息。
package com.braodcastR;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class BroadcastReceiverTActivity extends Activity {
private Button but;
private final static String MY_ACTION="com.broadcastR.action.MY_ACTION";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
but=(Button)findViewById(R.id.button1);
but.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.setAction(MY_ACTION);
sendBroadcast(intent);
}});
}
}
该类负责发起广播
package com.braodcastR;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i=new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setClass(context, NotificatinActivity.class);
context.startActivity(i);
}
}
该类负责接收广播后启动另一个Activity来显示通知
package com.braodcastR;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class NotificatinActivity extends Activity {
private Button but;
private Notification n;
private NotificationManager mm;
private static final int ID=1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.nt);
but=(Button)findViewById(R.id.button1);
String service=Context.NOTIFICATION_SERVICE;
mm=(NotificationManager)getSystemService(service);
n=new Notification();
int icon=n.icon=R.drawable.icon;
String tickerText="Text NOTIFICATION";
Long when=System.currentTimeMillis();
Notification nn=new Notification(icon,tickerText,when);
Intent intent=new Intent(this,BroadcastReceiverTActivity.class);
PendingIntent pi=PendingIntent.getActivity(this, 0, intent, 0);
nn.setLatestEventInfo(this,"My Title", "My Content", pi);
mm.notify(ID, nn);
but.setOnClickListener(canListener);
}
private OnClickListener canListener=new OnClickListener(){
@Override
public void onClick(View v) {
mm.cancel(ID);
}};
}
该类负责显示通知
最后是在AndroidManifest.xml配置文件中声明广播接收组件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.braodcastR"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".BroadcastReceiverTActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="MyReceiver">
<intent-filter>
<action android:name="com.broadcastR.action.MY_ACTION"></action>
</intent-filter>
</receiver>
<activity android:name=".NotificatinActivity"
android:label="@string/app_name">
</activity>
</application>

任务栏闹钟示例

下面以一个简单的AlarmManager例子来演示一下用Android来实现一个闹钟,可以实用AlarmManager来实现,它提供了一种系统级的提示服务,允许你安排在将来某个时间执行一个服务,AlarmManager对象一般不直接实例化而是通过Context.getSystemService(Context.ALARM_SERVICE)方法获得。
package com.amarmT;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class AlarmManagerTActivity extends Activity {
private Button setbut,canbut;
private static final String BC_ACTION="com.amarmT.action.BC_ACTION";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setbut=(Button)findViewById(R.id.setbut);
canbut=(Button)findViewById(R.id.canbut);
final AlarmManager am=(AlarmManager)getSystemService(ALARM_SERVICE);
Intent intent=new Intent(AlarmManagerTActivity.this,MyReceiver.class);
intent.setAction(BC_ACTION);
intent.putExtra("msg","go to bed");
final PendingIntent pi=PendingIntent.getBroadcast(AlarmManagerTActivity.this, 0, intent,0);
final long time=System.currentTimeMillis();
setbut.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
am.setRepeating(AlarmManager.RTC_WAKEUP, time, 2*1000, pi);
}});
canbut.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
am.cancel(pi);
}});
}
该类为两个按钮添加单击监听事件,一个用于设置闹钟,一个用于取消闹钟。
package com.amarmT;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg=intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
Log.i("msg", msg);
}
}
该类用于接收广播显示信息
最后是配置文件中注册
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.amarmT"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AlarmManagerTActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="MyReceiver">
<action android:name="com.amarmT.action.BC_ACTION"></action>
</receiver>
</application>
</manifest>

任务栏常驻图标示例

private void initNotification()
{
int notificationID = 10;
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.clock, "TestActivity已经起动",
System.currentTimeMillis());
notification.flags |= Notification.FLAG_ONGOING_EVENT; // 将此通知放到通知栏的"Ongoing"即"正在运行"组中
notification.flags |= Notification.FLAG_NO_CLEAR; // 表明在点击了通知栏中的"清除通知"后,此通知不清除,经常与FLAG_ONGOING_EVENT一起使用
notification.flags |= Notification.FLAG_SHOW_LIGHTS; // set LED on
notification.ledARGB = android.R.color.holo_blue_bright; // LED 颜色;
notification.ledOnMS = 5000; // LED 亮时间
Intent intent = new Intent(this, TestActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
notification.setLatestEventInfo(this, "TestActivity", "17:50执行下个任务", pendingIntent);
notificationManager.notify(notificationID, notification);
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initNotification();
}

Android Notification通知详解

根据activity的生命周期,在activity不显示时,会执行onStop函数(比如按下home键),所以你在onStop函数(按退出键除外)里面把notification放在通知栏里,再此显示时,把notification从通知栏里去掉。或者,只要程序在运行就一直显示通知栏图标。
下面对Notification类中的一些常量,字段,方法简单介绍一下:
常量:
DEFAULT_ALL 使用所有默认值,比如声音,震动,闪屏等等
DEFAULT_LIGHTS 使用默认闪光提示
DEFAULT_SOUNDS 使用默认提示声音
DEFAULT_VIBRATE 使用默认手机震动
【说明】:加入手机震动,一定要在manifest.xml中加入权限:
以上的效果常量可以叠加,即通过
notification.defaults =DEFAULT_SOUND|DEFAULT_VIBRATE;
notification.defaults |= DEFAULT_SOUND (最好在真机上测试,震动效果模拟器上没有)
FLAG_AUTO_CANCEL 该通知能被状态栏的清除按钮给清除掉
FLAG_NO_CLEAR 该通知能被状态栏的清除按钮给清除掉
FLAG_ONGOING_EVENT 通知放置在正在运行
FLAG_INSISTENT 是否一直进行,比如音乐一直播放,知道用户响应
常用字段:
contentIntent 设置PendingIntent对象,点击时发送该Intent
defaults 添加默认效果
flags 设置flag位,例如FLAG_NO_CLEAR等
icon 设置图标
sound 设置声音
tickerText 显示在状态栏中的文字
when 发送此通知的时间戳
NotificationManager常用方法介绍:
public void cancelAll() 移除所有通知(只是针对当前Context下的Notification)
public void cancel(int id) 移除标记为id的通知 (只是针对当前Context下的所有Notification)
public void notify(String tag ,int id, Notification notification) 将通知加入状态栏,标签为tag,标记为id
public void notify(int id, Notification notification) 将通知加入状态栏,标记为id
?
package com.ljq.activity;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
clearNotification();
}
@Override
protected void onStop() {
showNotification();
super.onStop();
}
@Override
protected void onStart() {
clearNotification();
super.onStart();
}
* 在状态栏显示通知
*/
private void showNotification(){
NotificationManager notificationManager = (NotificationManager)
this.getSystemService(android.content.Context.NOTIFICATION_SERVICE);
Notification notification =new Notification(R.drawable.icon,
"督导系统", System.currentTimeMillis());
notification.flags |= Notification.FLAG_ONGOING_EVENT; // 将此通知放到通知栏的"Ongoing"即"正在运行"组中
notification.flags |= Notification.FLAG_NO_CLEAR; // 表明在点击了通知栏中的"清除通知"后,此通知不清除,经常与FLAG_ONGOING_EVENT一起使用
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.ledARGB = Color.BLUE;
notification.ledOnMS =5000; //闪光时间,毫秒
CharSequence contentTitle ="督导系统标题"; // 通知栏标题
CharSequence contentText ="督导系统内容"; // 通知栏内容
Intent notificationIntent =new Intent(MainActivity.this, MainActivity.class); // 点击该通知后要跳转的Activity
PendingIntent contentItent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, contentTitle, contentText, contentItent);
notificationManager.notify(0, notification);
}
?
private void clearNotification(){
NotificationManager notificationManager = (NotificationManager) this
.getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(0);
}
}

Looper详解

线程中创建Handler或者调用有创建Handler的接口,需要先通过Looper进行相应操作才能正常进行。
Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用,从消息队列里取消息,处理消息。
注:写在Looper.loop()之后的代码不会被立即执行,当调用后 mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。Looper对象通过MessageQueue 来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
以下是Android API中的一个典型的Looper thread实现:
class LooperThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
public void handleMessage(Message msg)
{
}
};
Looper.loop();
}
另,Activity的MainUI线程默认是有消息队列的。所以在Activity中新建Handler时,不需要先调用Looper.prepare()。
Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用,从消息队列里取消息,处理消息。
注:写在Looper.loop()之后的代码不会被立即执行,当调用后 mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。Looper对象通过MessageQueue 来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
以下是Android API中的一个典型的Looper thread实现:
class LooperThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
public void handleMessage(Message msg)
{
}
};
Looper.loop();
}
另,Activity的MainUI线程默认是有消息队列的。所以在Activity中新建Handler时,不需要先调用Looper.prepare()。
Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用,从消息队列里取消息,处理消息。
注:写在Looper.loop()之后的代码不会被立即执行,当调用后 mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。Looper对象通过MessageQueue 来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
以下是Android API中的一个典型的Looper thread实现:
class LooperThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
public void handleMessage(Message msg)
{
}
};
Looper.loop();
}
Activity的MainUI线程默认是有消息队列的。所以在Activity中新建Handler时,不需要先调用Looper.prepare()。

Android 中我们常用的布局详解

1.LinearLayout ( 线性布局 ) :(里面只可以有一个控件,并且不能设计这个控件的位置,控件会放到左上角)
线性布局分为水平线性和垂直线性二者的属性分别为: android:orientation= " horizontal " android:orientation= "vertical" 。
2.RelativeLayout ( 相对布局 ) : (里面可以放多个控件,但是一行只能放一个控件)
附加几类 RelativeLayout 的属性供大家参考:
第一类 : 属性值为 true 或 false
android:layout_centerHrizontal 水平居中
android:layout_centerVertical 垂直居中
android:layout_centerInparent 相对于父元素完全居中
android:layout_alignParentBottom 贴紧父元素的下边缘
android:layout_alignParentLeft 贴紧父元素的左边缘
android:layout_alignParentRight 贴紧父元素的右边缘
android:layout_alignParentTop 贴紧父元素的上边缘
android:layout_alignWithParentIfMissing 若找不到兄弟元素以父元素做参照物
第二类:属性值必须为 id 的引用名“ @id/id-name ”
android:layout_below 在某元素的下方
android:layout_above 在某元素的上方
android:layout_toLeftOf 在某元素的左边
android:layout_toRightOf 在某元素的右边
android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐
第三类:属性值为具体的像素值,如 30dip , 40px
android:layout_marginBottom 离某元素底边缘的距离
android:layout_marginLeft 离某元素左边缘的距离
android:layout_marginRight 离某元素右边缘的距离
android:layout_marginTop 离某元素上边缘的距离
3.TableLayout ( 表格布局 ) : (这个要和TableRow配合使用,很像html里面的table)
这个表格布局不像HTML中的表格那样灵活,只能通过 TableRow 属性来控制它的行而列的话里面有几个控件就是几列(一般情况)。 如:
<TableLayout>
<EditText></EditText>
<EditText></EditText>
<EditText></EditText>
<EditText></EditText>
表示两行两列的一个表格。
android:gravity="center" 书面解释是权重比。其时就是让它居中显示。它还可以动态添加里面的每行每列。如下代码所示:
TableLayout tableLayout = (TableLayout) findViewById(R.id.table01);
TableRow tableRow = new TableRow(this);
TextView temp = new TextView(this);
temp.setText("text的值");
tableRow.addView(temp);
android:stretchColumns="1,2,3,4" 它的意思就是自动拉伸1,2,3,4列。
4.AbsoluteLayout ( 绝对布局 ) : (里面可以放多个控件,并且可以自己定义控件的x,y的位置)
5.FrameLayout ( 帧布局 ) :(里面可以放多个控件,不过控件的位置都是相对位置)
在它里面的控件都是按后面的一个控件叠加在前一个控件上来显示的,所有元素都被放置在最左上角。 如:
<FrameLayout android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1">
<ImageView android:id="@+id/iv1" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:visibility="invisible"
android:src="@drawable/lotusleaf"></ImageView>
<ImageView android:id="@+id/f1" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:src="@drawable/frog_right"
android:visibility="invisible"></ImageView>
</FrameLayout>
表示的是id为f1的控件叠加在id为iv1的控件上面显示
(LinearLayout 和 RelativeLayout 应该又是其中用的较多的两种。AbsoluteLayout 比较少用,因为它是按屏幕的绝对位置来布局的如果屏幕大小发生改变的话控件的位置也发生了改变。这个就相当于HTML中的绝对布局一样,一般不推荐使用 )

注意事项

1 、各布局不要乱用各自的属性。比如把属于 AbsoluteLayout 布局的android:layout_x和android:layout_y用到 LinearLayout 布局或 RelativeLayout 布局,或者把 RelativeLayout 布局的 below , rightof 等属性应用到其他布局中。这样做虽然不会报错,但这是白浪费感情的工作,根本达不到我们需要的效果。
2 、关于android:layout_width="fill_parent" 和 android:layout_height="wrap_content" ,这是对每个布局宽和高的设置。 wrap_content 可表示随着其中控件的不同而改变这个布局的宽度或高度,类似于自动设置宽和高, fill_parent 使布局填充整个屏幕,另外还有一种 match_parent ,它本质上和 fill_parent 一样,并从 API Level8 开始替代 fill_parent 。

TextView 的属性

android:autoLink //设置是否当文本为URL链接/email/电话号码/map时,文本显示为可点击的链接。可选值(none/web /email/phone/map/all)
android:autoText //如果设置,将自动执行输入值的拼写纠正。此处无效果,在显示输入法并输入的时候起作用
android:bufferType //指定getText()方式取得的文本类别。选项editable 类似于StringBuilder可追加字符,也就是说getText后可调用append方法设置文本内容。spannable 则可在给定的字符区域使用样式
android:capitalize //设置英文字母大写类型。此处无效果,需要弹出输入法才能看得到,参见EditView此属性说明
android:cursorVisible //设定光标为显示/隐藏,默认显示
android:digits //设置允许输入哪些字符。如“1234567890.+-*/% ()”
android:drawableBottom //在text的下方输出一个drawable,如图片。如果指定一个颜色的话会把text的背景设为该颜色,并且同时和background使用时覆盖后者
android:drawableLeft //在text的左边输出一个drawable,如图片
android:drawablePadding //设置text与drawable(图片)的间隔,与drawableLeft、 drawableRight、drawableTop、drawableBottom一起使用,可设置为负数,单独使用没有效果
android:drawableRight //在text的右边输出一个drawable
android:drawableTop //在text的正上方输出一个drawable
android:editable //设置是否可编辑
android:editorExtras //设置文本的额外的输入数据
android:ellipsize //设置当文字过长时,该控件该如何显示。有如下值设置:”start”—?省略号显示在开头;”end” ——省略号显示在结尾;”middle”—-省略号显示在中间;”marquee” ——以跑马灯的方式显示(动画横向移动)
android:freezesText //设置保存文本的内容以及光标的位置
android:gravity //设置文本位置,如设置成“center”,文本将居中显示
android:hintText //为空时显示的文字提示信息,可通过textColorHint设置提示信息的颜色。此属性在 EditView中使用,但是这里也可以用
android:imeOptions //附加功能,设置右下角IME动作与编辑框相关的动作,如actionDone右下角将显示一个“完成”,而不设置默认是一个回车符号。这个在EditView中再详细说明,此处无用
android:imeActionId //设置IME动作ID
android:imeActionLabel //设置IME动作标签
android:includeFontPadding //设置文本是否包含顶部和底部额外空白,默认为true
android:inputMethod //为文本指定输入法,需要完全限定名(完整的包名)。例如:com.google.android.inputmethod.pinyin,但是这里报错找不到
android:inputType //设置文本的类型,用于帮助输入法显示合适的键盘类型。在EditView中再详细说明,这里无效果
android:linksClickable //设置链接是否点击连接,即使设置了autoLink
android:marqueeRepeatLimit //在ellipsize指定marquee的情况下,设置重复滚动的次数,当设置为 marquee_forever时表示无限次
android:ems //设置TextView的宽度为N个字符的宽度。这里测试为一个汉字字符宽度
android:maxEms //设置TextView的宽度为最长为N个字符的宽度。与ems同时使用时覆盖ems选项
android:maxLength //限制显示的文本长度,超出部分不显示
android:lines //设置文本的行数,设置两行就显示两行,即使第二行没有数据
android:maxLines //设置文本的最大显示行数,与width或者layout_width结合使用,超出部分自动换行,超出行数将不显示
android:minLines //设置文本的最小行数,与lines类似
android:lineSpacingExtra //设置行间距
android:lineSpacingMultiplier //设置行间距的倍数。如”$2
android:numeric //如果被设置,该TextView有一个数字输入法。此处无用,设置后唯一效果是TextView有点击效果,此属性在EdtiView将详细说明
android:password //以小点”.”显示文本 android:phoneNumber设置为电话号码的输入方式
android:privateImeOptions //设置输入法选项,此处无用,在EditText将进一步讨论
android:scrollHorizontally //设置文本超出TextView的宽度的情况下,是否出现横拉条
android:selectAllOnFocus //如果文本是可选择的,让他获取焦点而不是将光标移动为文本的开始位置或者末尾位置。TextView中设置后无效果
android:shadowColor //指定文本阴影的颜色,需要与shadowRadius一起使用
android:shadowDx //设置阴影横向坐标开始位置
android:shadowDy //设置阴影纵向坐标开始位置
android:shadowRadius //设置阴影的半径。设置为0.1就变成字体的颜色了,一般设置为3.0的效果比较好
android:singleLine //设置单行显示。如果和layout_width一起使用,当文本不能全部显示时,后面用“…”来表示。
android:text="test_ singleLine
android:singleLine="true" android:layout_width="20dp" 将只显示“t…”。
如果不设置singleLine或者设置为false,文本将自动换行
android:text 设置显示文本
android:textAppearance 设置文字外观。如 ?android:attr/textAppearanceLargeInverse 引用的是系统自带的一个外观,
?表示系统是否有这种外观,否则使用默认的外观。可设置的值如下:
textAppearanceButton/textAppearanceInverse/textAppearanceLarge/textAppearanceLargeInverse/textAppearanceMedium/textAppearanceMediumInverse/textAppearanceSmall/textAppearanceSmallInverse
android:textColor //设置文本颜色
android:textColorHighlight //被选中文字的底色,默认为蓝色
android:textColorHint //设置提示信息文字的颜色,默认为灰色。与hint一起使用。
android:textColorLink //文字链接的颜色.
android:textScaleX //设置文字之间间隔,默认为$2。
android:textSize //设置文字大小,推荐度量单位”sp”,如”15sp”
android:textStyle //设置字形[bold(粗体) 0, italic(斜体) 1, bolditalic(又粗又斜) 2] 可以设置一个或多个,用“|”隔开
android:typeface //设置文本字体,必须是以下常量值之一:normal 0, sans 1, serif 2, monospace(等宽字体) 3]
android:height //设置文本区域的高度,支持度量单位:px(像素)/dp/sp/in/mm(毫米)
android:maxHeight //设置文本区域的最大高度
android:minHeight //设置文本区域的最小高度
android:width //设置文本区域的宽度,支持度量单位:px(像素)/dp/sp/in/mm(毫米),与layout_width 的区别看这里
android:maxWidth //设置文本区域的最大宽度
android:minWidth //设置文本区域的最小宽度 android布局属性详解 RelativeLayout用到的一些重要的属性:第一类:属性值为true或false
android:layout_centerHrizontal //水平居中
android:layout_centerVertical //垂直居中
android:layout_centerInparent //相对于父元素完全居中
android:layout_alignParentBottom //贴紧父元素的下边缘
android:layout_alignParentLeft //贴紧父元素的左边缘
android:layout_alignParentRight //贴紧父元素的右边缘
android:layout_alignParentTop //贴紧父元素的上边缘
android:layout_alignWithParentIfMissing //如果对应的兄弟元素找不到的话就以父元素做参照物 第二类:属性值必须为id的引用名“@id/id-name”
android:layout_below //在某元素的下方
android:layout_above //在某元素的的上方
android:layout_toLeftOf //在某元素的左边
android:layout_toRightOf //在某元素的右边
android:layout_alignTop //本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft //本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom //本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight //本元素的右边缘和某元素的的右边缘对齐 第三类:属性值为具体的像素值,如30dip,40px
android:layout_marginBottom //离某元素底边缘的距离
android:layout_marginLeft //离某元素左边缘的距离
android:layout_marginRight //离某元素右边缘的距离
android:layout_marginTop //离某元素上边缘的距离 EditText的android:hint 设置EditText为空时输入框内的提示信息 
android:gravity //属性是对该view 内容的限定.比如一个button 上面的text. 你可以设置该text 在view的靠左,靠右等位置.以button为例,android:gravity="right"则button上面的文字靠右 android:layout_gravity android:layout_gravity是用来设置该view相对与起父view 的位置.比如一个button 在linearlayout里,你想把该button放在靠左、靠右等位置就可以通过该属性设置.以button为例,android:layout_gravity="right"则button靠右 android:layout_alignParentRight 使当前控件的右端和父控件的右端对齐。这里属性值只能为true或false,默认false。 android:scaleType: android:scaleType是控制图片如何resized/moved来匹对ImageView的size。ImageView.ScaleType / android:scaleType值的意义区别: CENTER /center 按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示 CENTER_CROP / centerCrop 按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽) CENTER_INSIDE / centerInside 将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽 FIT_CENTER / fitCenter 把图片按比例扩大/缩小到View的宽度,居中显示 FIT_END / fitEnd 把图片按比例扩大/缩小到View的宽度,显示在View的下部分位置 FIT_START / fitStart 把图片按比例扩大/缩小到View的宽度,显示在View的上部分位置 FIT_XY / fitXY 把图片不按比例扩大/缩小到View的大小显示 MATRIX / matrix 用矩阵来绘制,动态缩小放大图片来显示。 ** 要注意一点,Drawable文件夹里面的图片命名是不能大写的

Edittext 的属性

EditText继承关系:View-->TextView-->EditText。
EditText 的属性很多,这里介绍几个:
android:layout_gravity="center_vertical" //设置控件显示的位置:默认top,这里居中显示,
还有bottom
android:hint="请输入数字!" //设置显示在空间上的提示信息
android:numeric="integer" //设置只能输入整数,如果是小数则是:decimal
android:singleLine="true" //设置单行输入,一旦设置为true,则文字不会自动换行。
android:password="true" //设置只能输入密码
android:textColor = "#ff$200" //字体颜色
android:textStyle="bold" //字体,bold, italic, bolditalic
android:textSize="20dip" //大小
android:capitalize = "characters" //以大写字母写
android:textAlign="center" //EditText没有这个属性,但TextView有,居中
android:textColorHighlight="#cccccc" //被选中文字的底色,默认为蓝色
android:textColorHint="#ffff00" //设置提示信息文字的颜色,默认为灰色
android:textScaleX="1.5" //控制字与字之间的间距
android:typeface="monospace" //字型,normal, sans, serif, monospace
android:background="@null" //空间背景,这里没有,指透明
android:layout_weight="1" //权重,控制控件之间的地位,在控制控件显示的大小时有用
android:textAppearance="?android:attr/textAppearanceLargeInverse"

1. EditText默认不弹出软件键盘

方法一:
在 AndroidMainfest.xml中选择哪个activity,设置windowSoftInputMode属性为 adjustUnspecified|stateHidden
android:windowSoftInputMode="adjustUnspecified|stateHidden"
方法二:
让 EditText失去焦点,使用EditText的clearFocus方法
edit.clearFocus();
方法三:
强制隐藏Android输入法窗口
例如:EditText edit=(EditText)findViewById(R.id.edit);
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edit.getWindowToken(),0);

2. EditText始终不弹出软件键盘

例:EditText edit=(EditText)findViewById(R.id.edit);
edit.setInputType(InputType.TYPE_NULL);
Button 继承自 VIEW , VIEW 有的属性它都能用 <?xml version="1.0" encoding="utf-8"?>
<item android:state_pressed="true"> (这里的样式是当按钮被按下时的显示)
<gradient
android:startColor="@drawable/gray"
android:endColor="@drawable/white"
android:angle="*"/>
<stroke
android:width="*dp"
android:color="@drawable/teal"/>
<corners
android:radius="*dp"/>
<padding
android:left="**dp"
android:top="*dp"
android:right="**dp"
android:bottom="*dp"/>
<item android:state_focused="true">(这里的样式是移动到按钮时的显示)
<gradient
android:startColor="@drawable/silver"
android:endColor="@drawable/springgreen"
android:angle="*"/>
<stroke
android:width="*dp"
android:color="@drawable/teal"/>
<corners
android:radius="*dp"/>
<padding
android:left="**dp"
android:top="*dp"
android:right="**dp"
android:bottom="*dp"/>
<item> (这里的样式是按钮正常时的显示)
<gradient
android:startColor="@drawable/silver"
android:endColor="@drawable/snow"
android:angle="*"/>
<stroke
android:width="*dp"
android:color="@drawable/teal"/>
<corners
android:radius="*dp"/>
<padding
android:left="**dp"
android:top="*dp"
android:right="**dp"
android:bottom="*dp"/>
注:
<padding
android:left="**dp"
android:top="*dp"
android:right="**dp"
android:bottom="*dp" />
这里 left 和 right 控制的是 Button 上的字体与按钮的左边缘和右边缘的距离,也就是控制按钮是长还是短;这里的 top 和 bottom 控制的是 Button 上的字体与按钮的上边缘和下边缘的距离,也就是控制按钮时高还是矮。
Shape 样式圆滑效果:
<padding android:left="*dp" android:top="*dp"
android:right="*dp" android:bottom="*dp"/>
</shape>
CheckBox
RadioGroup
Spinner
TimePicker
ScrollView
ProgressBar
RatingBar
ImageView
ImageButton android:background="#00000000" //设置背景图空白的部分直接透视背景
ImageSwicher&Gallery
GradView
Tab
Menu

属性代码设置

RelativeLayout,顾名思义,就是以“相对”位置/对齐 为基础的布局方式。android.widget.RelativeLayout 有个 继承自android.view.ViewGroup.LayoutParams 的内嵌类 LayoutParams,使用这个类的实例调用 RelativeLayout.addView 就可以实现“相对布局”。他比LinearLyout更灵活的为控件布置位置.
RelativeLayout 的属性:
android:layout_above 将该控件的底部置于给定ID的控件之上;
android:layout_below 将该控件的底部置于给定ID的控件之下;
android:layout_toLeftOf 将该控件的右边缘与给定ID的控件左边缘对齐;
android:layout_toRightOf 将该控件的左边缘与给定ID的控件右边缘对齐;
android:layout_alignBaseline 将该控件的baseline与给定ID的baseline对齐;
android:layout_alignTop 将该控件的顶部边缘与给定ID的顶部边缘对齐;
android:layout_alignBottom 将该控件的底部边缘与给定ID的底部边缘对齐;
android:layout_alignLeft 将该控件的左边缘与给定ID的左边缘对齐;
android:layout_alignRight 将该控件的右边缘与给定ID的右边缘对齐;
android:layout_alignParentTop 如果为true,将该控件的顶部与其父控件的顶部对齐;
android:layout_alignParentBottom 如果为true,将该控件的底部与其父控件的底部对齐;
android:layout_alignParentLeft 如果为true,将该控件的左部与其父控件的左部对齐;
android:layout_alignParentRight 如果为true,将该控件的右部与其父控件的右部对齐;
android:layout_centerHorizontal 如果为true,将该控件的置于水平居中;
android:layout_centerVertical 如果为true,将该控件的置于垂直居中;
android:layout_centerInParent 如果为true,将该控件的置于父控件的中央;
android:layout_marginTop 上偏移的值;
android:layout_marginBottom 下偏移的值;
android:layout_marginLeft   左偏移的值;
android:layout_marginRight   右偏移的值;
example:
android:layout_below = "@id/***"
android:layout_alignBaseline = "@id/***"
android:layout_alignParentTop = true
android:layout_marginLeft = “10px”
以下是代码中的用法:
package com.farproc.RLTest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.*;
import android.view.*;
public class RLTest extends Activity {
private RelativeLayout rl;
private Button btn1;
private Button btn2;
private Button btn3;
private Button btn4;
private static final int ID_BTN1 = 1;
private static final int ID_BTN2 = 2;
private static final int ID_BTN3 = 3;
private static final int ID_BTN4 = 4;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
rl = new RelativeLayout(this);
btn1 = new Button(this);
btn1.setText("----------------------");
btn1.setId(ID_BTN1);
RelativeLayout.LayoutParams lp1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp1.addRule(RelativeLayout.ALIGN_WITH_PARENT_TOP);
lp1.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
rl.addView(btn1, lp1 );
btn2 = new Button(this);
btn2.setText("|\n|\n|\n|\n|\n|");
btn2.setId(ID_BTN2);
RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp2.addRule(RelativeLayout.POSITION_BELOW, ID_BTN1);
lp2.addRule(RelativeLayout.ALIGN_LEFT, ID_BTN1);
rl.addView(btn2, lp2);
btn3 = new Button(this);
btn3.setText("|\n|\n|\n|\n|\n|");
btn3.setId(ID_BTN3);
RelativeLayout.LayoutParams lp3 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp3.addRule(RelativeLayout.POSITION_BELOW, ID_BTN1);
lp3.addRule(RelativeLayout.POSITION_TO_RIGHT, ID_BTN2);
lp3.addRule(RelativeLayout.ALIGN_RIGHT, ID_BTN1);
rl.addView(btn3,lp3);
btn4 = new Button(this);
btn4.setText("--------------------------------------------");
btn4.setId(ID_BTN4);
RelativeLayout.LayoutParams lp4 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lp4.addRule(RelativeLayout.POSITION_BELOW, ID_BTN2);
lp4.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
rl.addView(btn4,lp4);
setContentView(rl);
}
}

Activity 显示背景详解

activity跳转动画

在style中设置,点下一页效果是同步下滑Fall,进入下一页按返回是往左push动画。
第一步:进入下一个页面执行的操作是,当前的页面隐退(activityOpenExitAnimation),将要出现的画面进来(activityOpenEnterAnimation),当前的页面在后层,下一个页面在前层。
第二步:进入了第二个页面按返回键:当前的页面隐退(activityCloseEnterAnimation),之前的页面进来(activityCloseExitAnimation),当前的页面在后层,前一个页面在前层。
定义好了这四个动画之后,需要定义一个继承至android:style/Animation.Activity的风格,而这个风格还不能直接应用在activity上,因为这些动画要生成在windowAnimationStyle才可以应用在Activity主题上。
style.xml
<style mce_bogus="1" name="ThemeActivity">
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="AnimationActivity" parent="@android:style/Animation.Activity" mce_bogus="1">
<item name="android:activityOpenEnterAnimation">@anim/open_enter</item>
<item name="android:activityOpenExitAnimation">@anim/open_exit</item>
<item name="android:activityCloseEnterAnimation">@anim/close_enter</item>
<item name="android:activityCloseExitAnimation">@anim/close_exit</item>
</style>
open_enter.xml
<translate android:fromYDelta="-100%" android:toYDelta="0" android:duration="1000" />
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="1000"/>
open_exit.xml
<translate android:fromYDelta="0" android:toYDelta="100%" android:duration="1000" />
<alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="1000"/>
close_enter.xml
<translate android:fromXDelta="-100%" android:toXDelta="0" android:duration="1000"/>
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="1000"/>
close_exit.xml
<translate android:fromXDelta="0" android:toXDelta="100%" android:duration="1000"/>
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.com.bvin.demo.anim"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<!-- android:theme="@style/ThemeActivity" 将应用于所有Activity -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/ThemeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".NextActivity"
android:theme="@style/ThemeActivity">
<!-- android:theme="@style/ThemeActivity" 用于当前Activity -->
</activity>
</application>

Activit 切换时屏幕空白

有些性能低的机器,在切换activity时候出现白屏一段时候后才显示正确的视图,高性能的机器可能太快看不到,但是事实是存在的,
特别是当你新开一个进程的时候,A进程的activity跳转到B进程的Activity是绝对会出现白屏一段时间的:
解决方案如下
1. style.xml中中增加
<style name="AppTheme" parent="android:Theme.Light">
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
2 acvitiy 的属性中加上
android:theme="@style/AppTheme"

Android窗口背景的优化

视图有背景,每个窗口也是有背景的。每一Activity是一个窗口,每一个Activity都有不同得背景。界面的绘画顺序如下:窗口——跟视图 ——子视图。
当我们的跟视图已经覆盖了整个窗口的时候 ,程序还是会画一个透明的窗口的背景,而这个背景用户是看不到的,需要想办法让程序在这样的情况下去掉窗口背景,节约画窗口背景的时间提高效率。
删除窗口背景的方法:
一、java代码中
public void onCreate(Bundle icicle){
super.onCreate(icicle);
setContentView(R.layout.mainview);
getWindow().setBackgroundDrawable(null);
...
二、在xml文件中
首先在res/values/style.xml 文件中定义如下:
然后在AndroidManifest.xml文件中 找到要去掉背景的activity 书写代码如下:
...

Activity界面透明

<item name="android:windowIsTranslucent">true</item>,

Activity 切换动画

[html] view plaincopy
<style name="AppTheme" parent="AppBaseTheme">
<!-- <item name="android:windowAnimationStyle">@style/Animation.Activity.Style</item> -->
<item name="android:windowAnimationStyle">@style/Animation.Activity.Translucent.Style</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
<style name="Animation.Activity.Style" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/base_stay_orig</item>
<item name="android:activityCloseEnterAnimation">@anim/base_stay_orig</item>
<item name="android:activityCloseExitAnimation">@anim/base_slide_right_out</item>
<item name="android:taskOpenEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:taskOpenExitAnimation">@anim/base_stay_orig</item>
<item name="android:taskCloseEnterAnimation">@anim/base_stay_orig</item>
<item name="android:taskCloseExitAnimation">@anim/base_slide_right_out</item>
<item name="android:taskToFrontEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:taskToFrontExitAnimation">@anim/base_stay_orig</item>
<item name="android:taskToBackEnterAnimation">@anim/base_stay_orig</item>
<item name="android:taskToBackExitAnimation">@anim/base_slide_right_out</item>
</style>
<style name="Animation.Activity.Translucent.Style" parent="@android:style/Animation.Translucent">
<item name="android:windowEnterAnimation">@anim/base_slide_right_in</item>
<item name="android:windowExitAnimation">@anim/base_slide_right_out</item>
</style>

控件移动

方法一

button.layout(newLeft, newTop, newRight,newBottom);
这种方法有个缺陷,当同一个viewgroup中有控件更新(界面刷新)时,移动的控件会复位,即回到一开始的位置。

方法二

另外一种方法是调用MarginLayoutParams.setMargins(),重新设置控件位置参数来实现控件移动效果。
这种方式比较适合RelativeLayout、FrameLayout,AbsoluteLayout,
对于LinearLayout,因为最后增加的控件总在最下或最右,所以达不到移动效果,TableLayout也不行。
FrameLayout.LayoutParams btnLp = (FrameLayout.LayoutParams)button.getLayoutParams();
btnLp.setMargins(newLeft, newTop, newRight, newBottom);
button.requestLayout();
在xml中不能设置居中等属性,否则不起作用。

获取res下 Layout对象

LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
layout = inflater.inflate(R.layout.layout2, null); //layout定义是: View layout
inflate获取出来的对象是View,所有layout布局,均是继承View,做向上转型完全没有问题.
具体是那一种layout就通过强制类型转换,转换成某种类型.
RelativeLayout rlayout = (RelativeLayout) layout;

TextView自动换行

Android项目中的文字排版有时参差不齐的情况非常严重
textview自动换行导致混乱的原因了----半角字符与全角字符混乱所致!
一般情况下,我们输入的数字、字母以及英文标点都是半角,所以占位无法确定。
它们与汉字的占位大大的不同,由于这个原因,导致很多文字的排版都是参差不齐的。
有以下两种办法可以解决这个问题:

1. 将textview中的字符全角化

即将所有的数字、字母及标点全部转为全角字符,使它们与汉字同占两个字节,这样就可以避免由于占位导致的排版混乱问题了。 半角转为全角的代码如下,只需调用即可。
Java代码
* 半角转换为全角
*
* @param input
* @return
*/
public static String ToDBC(String input) {
char[] c = input.toCharArray();
for (int i = 0; i < c.length; i++) {
if (c[i] == 12288) {
c[i] = (char) 32;
continue;
}
if (c[i] > 65280 && c[i] < 65375)
c[i] = (char) (c[i] - 65248);
}
return new String(c);
}

2. 去除特殊字符或将所有中文标号替换为英文标号

利用正则表达式将所有特殊字符过滤,或利用replaceAll()将中文标号替换为英文标号。则转化之后,则可解决排版混乱问题。
Java代码
* 去除特殊字符或将所有中文标号替换为英文标号
*
* @param str
* @return
*/
public static String stringFilter(String str) {
str = str.replaceAll("【", "[").replaceAll("】", "]")
.replaceAll("!", "!").replaceAll(":", ":");// 替换中文标号
String regEx = "[『』]"; // 清除掉特殊字符
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(str);
return m.replaceAll("").trim();
}

Android TextView 换行问题

单行显示

如下设置可只用单行显示
android:singleLine="true"//单行显示
android:ellipsize="end"//省略号出现在末尾

多行时优化自动换行

Android的TextView在显示文字的时候有个问题就是一行还没显示满就跳到下一行,原因是:
1) TextView在显示中文的时候 标点符号不能显示在一行的行首和行尾,如果一个标点符号刚好在一行的行尾,该标点符号就会连同前一个字符跳到下一行显示;
2)一个英文单词不能被显示在两行中( TextView在显示英文时,标点符号是可以放在行尾的,但英文单词也不能分开 );
如果只是想让标点符号可以显示在行尾,有一个简单的方法就是在标点符号后加一个空格,则该标点符号就可以显示在行尾了;
如果想要两端对齐的显示效果,有两种方法:
1)修改Android源代码;将frameworks/base/core/java/android/text下的StaticLayout.java文件中的如下代码:
[java] view plaincopy
if (c == ' ' || c == '/t' ||
((c == '.' || c == ',' || c == ':' || c == ';') &&
(j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
(j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
((c == '/' || c == '-') &&
(j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
(c >= FIRST_CJK && isIdeographic(c, true) &&
j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
okwidth = w;
ok = j + 1;
if (fittop < oktop)
oktop = fittop;
if (fitascent < okascent)
okascent = fitascent;
if (fitdescent > okdescent)
okdescent = fitdescent;
if (fitbottom > okbottom)
okbottom = fitbottom;
}
去掉后标点符号可以显示在行首和行尾,英文单词也可以被分开在两行中显示。

2)自定义View显示文本

自定义View的步骤:
1)继承View类或其子类,例子继承了TextView类;
2)写构造函数,通过XML获取属性(这一步中可以自定义属性,见例程);
3)重写父类的某些函数,一般都是以on开头的函数,例子中重写了onDraw()和onMeasure()函数;
=========================StartCustomTextView.java=============================
[java] view plaincopy
public class StartCustomTextView extends TextView {
public static int m_iTextHeight; //文本的高度
public static int m_iTextWidth;//文本的宽度
private Paint mPaint = null;
private String string="";
private float LineSpace = 0;//行间距
private int left_Margin;
private int right_Margin;
private int bottom_Margin;
public StartCustomTextView(Context context, AttributeSet set)
{
super(context,set);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
TypedArray typedArray = context.obtainStyledAttributes(set, R.styleable.CYTextView);
int width = displayMetrics.widthPixels;
left_Margin = 29;
right_Margin = 29;
bottom_Margin = 29;
width = width - left_Margin -right_Margin;
float textsize = typedArray.getDimension(R.styleable.CYTextView_textSize, 34);
int textcolor = typedArray.getColor(R.styleable.CYTextView_textColor, getResources().getColor(android.R.color.white));
float linespace = typedArray.getDimension(R.styleable.CYTextView_lineSpacingExtra, 15);
int typeface = typedArray.getColor(R.styleable.CYTextView_typeface, 0);
typedArray.recycle();
m_iTextWidth=width;
LineSpace=linespace;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(textcolor);
mPaint.setTextSize(textsize);
switch(typeface){
case 0:
mPaint.setTypeface(Typeface.DEFAULT);
break;
case 1:
mPaint.setTypeface(Typeface.SANS_SERIF);
break;
case 2:
mPaint.setTypeface(Typeface.SERIF);
break;
case 3:
mPaint.setTypeface(Typeface.MONOSPACE);
break;
default:
mPaint.setTypeface(Typeface.DEFAULT);
break;
}
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
char ch;
int w = 0;
int istart = 0;
int m_iFontHeight;
int m_iRealLine=0;
int x=2;
int y=30;
Vector m_String=new Vector();
FontMetrics fm = mPaint.getFontMetrics();
m_iFontHeight = (int) Math.ceil(fm.descent - fm.top) + (int)LineSpace;//计算字体高度(字体高度+行间距)
for (int i = 0; i < string.length(); i++)
{
ch = string.charAt(i);
float[] widths = new float[1];
String srt = String.valueOf(ch);
mPaint.getTextWidths(srt, widths);
if (ch == '\n'){
m_iRealLine++;
m_String.addElement(string.substring(istart, i));
istart = i + 1;
w = 0;
}else{
w += (int) (Math.ceil(widths[0]));
if (w > m_iTextWidth){
m_iRealLine++;
m_String.addElement(string.substring(istart, i));
istart = i;
i--;
w = 0;
}else{
if (i == (string.length() - 1)){
m_iRealLine++;
m_String.addElement(string.substring(istart, string.length()));
}
}
}
}
m_iTextHeight=m_iRealLine*m_iFontHeight+2;
canvas.setViewport(m_iTextWidth, m_iTextWidth);
for (int i = 0, j = 0; i < m_iRealLine; i++, j++)
{
canvas.drawText((String)(m_String.elementAt(i)), x, y+m_iFontHeight * j, mPaint);
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int measuredHeight = measureHeight(heightMeasureSpec);
int measuredWidth = measureWidth(widthMeasureSpec);
this.setMeasuredDimension(measuredWidth, measuredHeight);
LayoutParams layout = new LinearLayout.LayoutParams(measuredWidth,measuredHeight);
layout.leftMargin= left_Margin;
layout.rightMargin= right_Margin;
layout.bottomMargin= bottom_Margin;
this.setLayoutParams(layout);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int measureHeight(int measureSpec)
{
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
initHeight();
int result = m_iTextHeight;
if (specMode == MeasureSpec.AT_MOST){
result = specSize;
}else if (specMode == MeasureSpec.EXACTLY){
}
return result;
}
private void initHeight()
{
m_iTextHeight=0;
FontMetrics fm = mPaint.getFontMetrics();
int m_iFontHeight = (int) Math.ceil(fm.descent - fm.top) + (int)LineSpace;
int line=0;
int istart=0;
int w=0;
for (int i = 0; i < string.length(); i++)
{
char ch = string.charAt(i);
float[] widths = new float[1];
String srt = String.valueOf(ch);
mPaint.getTextWidths(srt, widths);
if (ch == '\n'){
line++;
istart = i + 1;
w = 0;
}else{
w += (int) (Math.ceil(widths[0]));
if (w > m_iTextWidth){
line++;
istart = i;
i--;
w = 0;
}else{
if (i == (string.length() - 1)){
line++;
}
}
}
}
m_iTextHeight=(line)*m_iFontHeight+2;
}
private int measureWidth(int measureSpec)
{
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
int result = 500;
if (specMode == MeasureSpec.AT_MOST){
result = specSize;
}else if (specMode == MeasureSpec.EXACTLY){
result = specSize;
}
return result;
}
public void SetText(String text)
{
string = text;
}
}
=======================attrs.xml===============================
该文件是自定义的属性,放在工程的res/values下
[html] view plaincopy
<resources>
<attr name="textwidth" format="integer"/>
<attr name="typeface">
<enum name="normal" value="0"/>
<enum name="sans" value="1"/>
<enum name="serif" value="2"/>
<enum name="monospace" value="3"/>
</attr>
<declare-styleable name="CYTextView">
<attr name="textwidth" />
<attr name="textSize" format="dimension"/>
<attr name="textColor" format="reference|color"/>
<attr name="lineSpacingExtra" format="dimension"/>
<attr name="typeface" />
</declare-styleable>
</resources>
=======================main.xml==========================
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:Android="http://schemas.android.com/apk/res/android"
Android:layout_width="320px"
Android:layout_height="320px"
Android:background="#ffffffff"
>
<LinearLayout
xmlns:Android="http://schemas.android.com/apk/res/android"
Android:orientation="vertical"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent">
<com.cy.CYTextView.CYTextView
xmlns:cy="http://schemas.Android.com/apk/res/ com.cy.CYTextView "
Android:id="@+id/mv"
Android:layout_height="wrap_content"
Android:layout_width="wrap_content"
cy :textwidth="320"
cy :textSize="24sp"
cy :textColor="#aa000000"
cy :lineSpacingExtra="15sp"
cy :typeface="serif">
</com. cy .CYTextView.CYTextView>
</LinearLayout>
</ScrollView>
蓝色代码即为自定义View,其中以cy命名空间开头的属性是自定义属性;
=======================Main.java=============================
[java] view plaincopy
public class Main extends Activity {
CYTextView mCYTextView;
String text = "Android提供了精巧和有力的组件化模型构建用户的UI部分。主要是基于布局类:View和 ViewGroup。在此基础上,android平台提供了大量的预制的View和xxxViewGroup子 类,即布局(layout)和窗口小部件(widget)。可以用它们构建自己的UI。";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
mCYTextView = (CYTextView)findViewById(R.id.mv);
mCYTextView.SetText(text);
}
}

文字滚动

跑马灯效果就是当文字超过控件所能容纳的空间时,在控件内滚动的效果。

文字滚动示例一

要实现这样的效果需要在布局文件中加上:
android:singleLine=”true”
android:ellipsize="marquee"
android:focusableInTouchMode="true"
android:focusable="true"
需要注意的是:
layout_width="50px" //要写成固定值,不能是wrap_content或者fill_parent,而且要比text长度长。
另外还可以设置滚动的次数
android:marqueeRepeatLimit="100";
android:marqueeRepeatLimit="marquee_forever" //表示一直滚动。
但是这种跑马灯只有在控件获得焦点时在能滚动,要想让控件里的内容一直滚动就要定制该控件,重写里面的三个方法:
[java] view plaincopy
package cn.etzmico.marqueetest;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.Button;
public class MarqueeButton extends Button {
public MarqueeButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
if(focused)
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if(hasWindowFocus)
super.onWindowFocusChanged(hasWindowFocus);
}
@Override
public boolean isFocused() {
return true;
}
}
在布局文件里使用这个控件了:
[java] view plaincopy
<cn.easymobi.application.memorytest.MarqueeButton
android:layout_width=”216dip”
android:layout_height=”wrap_content”
android:id=”@+id/btSecond”
android:background=”@drawable/button_test2″
android:layout_marginTop=”15dip”
android:text=”@string/calculate”
android:ellipsize=”marquee”
android:gravity=”center”
android:textColor=”@color/white”
android:textStyle=”bold”
android:focusable=”true”
android:marqueeRepeatLimit=”marquee_forever”
android:focusableInTouchMode=”true”
android:scrollHorizontally=”true”
android:singleLine=”true”
android:paddingLeft=”50dip”
android:paddingRight=”50dip”
android:textSize=”20dip”

文字滚动示例二

TextView实现文字过长时省略部分或者滚动显示
TextView中有个ellipsize属性,作用是当文字过长时,该控件该如何显示,解释如下:
1.android:ellipsize="start"
--省略号显示在开头
2.android:ellipsize="end"
--省略号显示在结尾
3.android:ellipsize="middle"
--省略号显示在中间
4.android:ellipsize="marquee"
--以跑马灯的方式显示(动画横向移动)
文字左右滚动三个属性:
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
Android中我们为了实现文本的滚动可以在ScrollView中嵌入一个TextView,其实TextView自己也可以实现多行滚动的,毕竟ScrollView必须只能有一个直接的子类布局。只要在layout中简单设置几个属性就可以轻松实现。
<TextView
android:id="@+id/tvCWJ"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical" <!--垂直滚动条-->
android:singleLine="false" <!--实现多行-->
android:maxLines="15" <!--最多不超过15行-->
android:textColor="#FF0000"
< TextView
android:id = "@+id/app_shortcontent"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:singleLine = "true"
android:textColor = "#FFFFFFFF"
android:scrollHorizontally = "true"
android:focusable = "true"
android:ellipsize = "marquee"
android:marqueeRepeatLimit ="marquee_forever" />
当然我们为了让TextView动起来,还需要用到TextView的setMovementMethod方法设置一个滚动实例,代码如下
TextView tv =(TextView)findViewById(R.id.tvCWJ);
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
TextView 自动滚动的实现方法,下面介绍两种方法:
一、在代码中实现:
textView .setEllipsize(TextUtils.TruncateAt.MARQUEE);
textView .setSingleLine(true);
textView .setMarqueeRepeatLimit(6);
二、在XML中实现:
<TextView android:id="@+id/TextView01"android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="text"
android:marqueeRepeatLimit="marquee_forever"
android:ellipsize="marquee"
android:scrollHorizontally="true"
android:width="150dip">
当textView 获取焦点后,才会自动滚动。
不管是手动还是自动 你要实现滚动就得加scrollview
到时候可以通过handle来调用scrollview的scrollTo方法实现滚动
Handler mHandler = new Handler();
private Runnable mScrollToBottom = new Runnable() {
public void run() {
mScrollView.scrollTo(0, offset);
}
};
onTouch里面
mHandler.post(mScrollToBottom);
补充 ====================
补充:
1,把 textview的 Ellipsize 设置成 marquee(上面有说)
2,把 Deprecated的 Single line 设置成 true
3,设置textview的Marquee repeat limit 属性(滚动回数,默认是无数回)

示例三

android用TextView实现文字滚动效果
如果不设置文字字体的颜色,而是使用系统默认的白色字体,滚动时屏幕会变暗,解决办法设置字体的颜色就可以了。
XML内textview代码:
[java]
<TextView
android:id="@+id/textview1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:textColor="#FFFFFF"
android:focusable = "true"
JAVA代码里加上监听器:
[java]
tv = (TextView) findViewById(R.id.textview1);
tv.setMovementMethod(ScrollingMovementMethod.getInstance());

Android fill_parent、wrap_content和match_parent的区别

三个属性都用来适应视图的水平或垂直大小,一个以视图的内容或尺寸为基础的布局比精确地指定视图范围更加方便。
1)fill_parent
设置一个构件的布局为fill_parent将强制性地使构件扩展,以填充布局单元内尽可能多的空间。这跟Windows控件的dockstyle属性大体一致。设置一个顶部布局或控件为fill_parent将强制性让它布满整个屏幕。
2) wrap_content
设置一个视图的尺寸为wrap_content将强制性地使视图扩展以显示全部内容。以TextView和ImageView控件为例,设置为wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。设置一个视图的尺寸为wrap_content大体等同于设置Windows控件的Autosize属性为True。
3)match_parent
Android2.2中match_parent和fill_parent是一个意思 .两个参数意思一样,match_parent更贴切,于是从2.2开始两个词都可以用。那么如果考虑低版本的使用情况你就需要用fill_parent了

Android中获取字符串长度、宽度(所占像素宽度)

计算出当前绘制出来的字符串有多宽,可以这么来!
方法1:
Paint pFont = new Paint();
Rect rect = new Rect();
pFont.getTextBounds(str, 0, 1, rect);
strwid = rect.width();
strhei = rect.height();
方法2:
strwid = paintHead.measureText(str);
public static float GetTextWidth(String text, float Size) { //第一个参数是要计算的字符串,第二个参数是字体大小
TextPaint fontPaint = new TextPaint();
fontPaint.setTextSize(Size);
return fontPaint.measureText(text);
}

Android默认字体大小、高度、宽度

android 的默认字体大小为12.0
private static Paint sPaint = new Paint();
* 获得默认字体的大小
*/
float f=sPaint.getTextSize(); //12.0
* 获得默认字体的高度
*/
public static int FONT_HEIGHT;
static {
FontMetrics sF = sPaint.getFontMetrics();
FONT_HEIGHT = (int) Math.ceil(sF.descent - sF.top) + 2;
}
* 获得默认字体的宽度
*/
float sWidth = sPaint.measureText(aStr);

Android 字体设置

Android 对中文字体支持很不好,需要加入相应的字体库
(1)创建布局Layout
LinearLayout linearLayout=newLinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
setContentView(linearLayout);
(2)针对正常字体
normal=newTextView(this);
normal.setText("Normal Font FYI");
normal.setTextSize(20.0f);
normal.setTypeface(Typeface.DEFAULT,Typeface.NORMAL);
linearLayout.addView(normal,newLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
(3)针对粗体字体
bold=newTextView(this);
bold.setText("Bold Font FYI");
bold.setTextSize(20.0f);
bold.setTextColor(Color.BLUE);
bold.setTypeface(Typeface.DEFAULT_BOLD, Typeface.BOLD);
linearLayout.addView(bold,newLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
(4)针对斜体字体
italic=newTextView(this);
italic.setTextSize(20f);
italic.setText("Italic Font FYI");
italic.setTextColor(Color.RED);
italic.setTypeface(Typeface.MONOSPACE,Typeface.ITALIC);
linearLayout.addView(italic,newLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
(5)针对粗斜体字体
italic_bold=newTextView(this);
italic_bold.setTextSize(20f);
italic_bold.setText("Italic & Bold Font FYI");
italic_bold.setTextColor(Color.YELLOW);
italic_bold.setTypeface(Typeface.MONOSPACE,Typeface.BOLD_ITALIC);
linearLayout.addView(italic_bold,newLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
(6)针对中文仿“粗体”
chinese=newTextView(this);
chinese.setText("中文粗体显示效果");
chinese.setTextColor(Color.MAGENTA);
chinese.setTextSize(20.0f);
chinese.setTypeface(Typeface.DEFAULT_BOLD, Typeface.BOLD);
tp=chinese.getPaint();
tp.setFakeBoldText(true);
linearLayout.addView(chinese,newLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
(7)自定义创建字型
custom=newTextView(this);
typeface=Typeface.createFromAsset(getAssets(),"font/MgOpenCosmeticaBold.ttf");
custom.setTypeface(typeface);
custom.setText("Custom Font FYI");
custom.setTextSize(20.0f);
custom.setTextColor(Color.CYAN);
linearLayout.addView(custom,newLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));

Android getWidth和getMeasuredWidth 区别详解

getWidth 得到的事某个View的实际尺寸。
getMeasuredWidth 得到的是某个View想要在parent view里面占的大小
在一个类初始化时,即在构造函数当中我们是得不到View的实际大小的。感兴趣的朋友可以试一下,getWidth()和getMeasuredWidth()得到的结果都是0.但是我们可以从onDraw()方法里面的到控件的大小。
这两个所得到的结果的单位是像素即pixel。
getWidth(): 得到的是view在父Layout中布局好后的宽度值,如果没有父布局,那么默认的父布局就是真个屏幕。也许不好理解通过一个例子来说明一下:
[java] view plaincopyprint?
public class Test extends Activity {
private LinearLayout mBackgroundLayout;
private TextViewTest mTextViewTest;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBackgroundLayout = new MyLayout(this);
mBackgroundLayout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));
mTextViewTest = new TextViewTest(this);
mBackgroundLayout.addView(mTextViewTest);
setContentView(mBackgroundLayout);
}
public class MyLayout extends LinearLayout{
public MyLayout(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.i("Tag", "--------------");
View mView=getChildAt(0);
mView.measure(0, 0);
}
}
public class TextViewTest extends TextView {
public TextViewTest(Context context) {
super(context);
setText("test test ");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("Tag", "width: " + getWidth() + ",height: " + getHeight());
Log.i("Tag", "MeasuredWidth: " + getMeasuredWidth()
+ ",MeasuredHeight: " + getMeasuredHeight());
}
}
}
在LinearLayout里添加的一个TextView控件,如果此时要得到对TextView获得getWidth(),那么是在TextView添加到Layout后再去获取值,并不单单的是对TextView本身宽度的获取。
getMeasuredWidth():
The width of this view as measured in the most recent call to measure(). This should be used during measurement and layout calculations only.
得到的是最近一次调用measure()方法测量后得到的是View的宽度,它仅仅用在测量和Layout的计算中。
所以此方法得到的是View的内容占据的实际宽度。
你如果想从一个简单的例子中得到他们的不同,下面将对上面的例子做一下修改。
[java] view plaincopyprint?
public class Test extends Activity {
private TextViewTest mTextViewTest;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextViewTest = new TextViewTest(this);
setContentView(mTextViewTest);
}
public class TextViewTest extends TextView {
public TextViewTest(Context context) {
super(context);
setText("test test ");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
measure(0, 0);
Log.i("Tag", "width: " + getWidth() + ",height: " + getHeight());
Log.i("Tag", "MeasuredWidth: " + getMeasuredWidth()
+ ",MeasuredHeight: " + getMeasuredHeight());
}
}
}
getWidth(): View在设定好布局后整个View的宽度。
getMeasuredWidth(): 对View上的内容进行测量后得到的View内容占据的宽度,前提是你必须在父布局的onLayout()方法或者此View的onDraw()方法里调用measure(0,0);(measure中的参数的值你自己可以定义),否则你得到的结果和getWidth()得到的结果是一样的。

PendingIntent 详解

1. public static final int FLAG_ONE_SHOT = 1<<30;
只执行一次,执行后自动取消
* Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
* {@link #getService}: this
* PendingIntent can only be used once. If set, after
* {@link #send()} is called on it, it will be automatically
* canceled for you and any future attempt to send through it will fail.
*/
2. public static final int FLAG_NO_CREATE = 1<<29;
不存在则返回空
* Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
* {@link #getService}: if the described PendingIntent does not already
* exist, then simply return null instead of creating it.
*/
3. public static final int FLAG_CANCEL_CURRENT = 1<<28;
如存在,则先取消,再增加新的
* Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
* {@link #getService}: if the described PendingIntent already exists,
* the current one is canceled before generating a new one. You can use
* this to retrieve a new PendingIntent when you are only changing the
* extra data in the Intent; by canceling the previous pending intent,
* this ensures that only entities given the new data will be able to
* launch it. If this assurance is not an issue, consider
* {@link #FLAG_UPDATE_CURRENT}.
*/
4. public static final int FLAG_UPDATE_CURRENT = 1<<27;
如存在,则只更新一下,并不重新增加
* Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
* {@link #getService}: if the described PendingIntent already exists,
* then keep it but its replace its extra data with what is in this new
* Intent. This can be used if you are creating intents where only the
* extras change, and don't care that any entities that received your
* previous PendingIntent will be able to launch it with your new
* extras even if they are not explicitly given to it.
*/
PendingIntent的内部机制
2 PendingIntent 简介
在Android中,PendingIntent用来表达一种“留待日后处理”的意思。从这个角度来说,PendingIntent可以被理解为一种特殊的异步处理机制。不过,单就命名而言,PendingIntent其实具有一定误导性,因为它既不继承于Intent,也不包含Intent,它的核心可以粗略地汇总成四个字——“异步激发”。
这种异步激发常常是要跨进程执行的。比如说A进程作为发起端,它可以从系统“获取”一个PendingIntent,然后A进程可以将PendingIntent对象通过binder机制“传递”给B进程,再由B进程在未来某个合适时机,“回调”PendingIntent对象的send()动作,完成激发。
在Android系统中,最适合做集中性管理的组件就是AMS(Activity Manager Service)啦,所以它义不容辞地承担起管理所有PendingIntent的职责。
当发起端获取PendingIntent时,其实是需要同时提供若干intent的。这些intent和PendingIntent只是配套的关系,而不是聚合的关系,它们会被缓存在AMS中。日后,一旦处理端将PendingIntent的“激发”语义传递到AMS,AMS就会尝试找到与这个PendingIntent对应的若干intent,并递送出去。
2 PendingIntent 细节
2.1 发起端获取PendingIntent
我们先要理解,所谓的“发起端获取PendingIntent”到底指的是什么。难道只是简单new一个PendingIntent对象吗?当然不是。此处的“获取”动作其实还含有向AMS“注册”intent的语义。
在PendingIntent.java文件中,我们可以看到有如下几个比较常见的静态函数:
public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags)
public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)
public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags)
public static PendingIntent getActivities(Context context, int requestCode, Intent[] intents, int flags)
public static PendingIntent getActivities(Context context, int requestCode, Intent[] intents, int flags, Bundle options)
它们就是我们常用的获取PendingIntent的动作了。
坦白说,这几个函数的命名可真不怎么样,所以我们简单解释一下。上面的getActivity()的意思其实是,获取一个PendingIntent对象,而且该对象日后激发时所做的事情是启动一个新activity。也就是说,当它异步激发时,会执行类似Context.startActivity()那样的动作。相应地,getBroadcast()和getService()所获取的PendingIntent对象在激发时,会分别执行类似Context..sendBroadcast()和Context.startService()这样的动作。至于最后两个getActivities(),用得比较少,激发时可以启动几个activity。
我们以getActivity()的代码来说明问题:
public static PendingIntent getActivity(Context context, int requestCode,
Intent intent, int flags, Bundle options)
{
String packageName = context.getPackageName();
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver()) : null;
try
{
intent.setAllowFds(false);
IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
flags, options);
return target != null ? new PendingIntent(target) : null;
}
catch (RemoteException e)
{}
return null;
}
其中那句new PendingIntent(target)创建了PendingIntent对象,其重要性自不待言。然而,这个对象的内部核心其实是由上面那个getIntentSender()函数得来的。而这个IIntentSender核心才是我们真正需要关心的东西。
此处的IIntentSender对象是个binder代理,它对应的binder实体是AMS中的PendingIntentRecord对象。PendingIntent对象构造之时,IIntentSender代理作为参数传进来,并记录在PendingIntent的mTarget域。日后,当PendingIntent执行异步激发时,其内部就是靠这个mTarget域向AMS传递语义的。
PendingIntent常常会经由binder机制,传递到另一个进程去。而binder机制可以保证,目标进程得到的PendingIntent的mTarget域也是合法的IIntentSender代理,而且和发起端的IIntentSender代理对应着同一个PendingIntentRecord实体。]
2.2 AMS里的PendingIntentRecord
那么PendingIntentRecord里又有什么信息呢?它的定义截选如下:
class PendingIntentRecord extends IIntentSender.Stub
{
final ActivityManagerService owner;
final Key key; // 最关键的key域
final int uid;
final WeakReference<PendingIntentRecord> ref;
boolean sent = false;
boolean canceled = false;
String stringName;
. . . . . .
}
请注意其中那个key域。这里的Key是个PendingIntentRecord的内嵌类,其定义截选如下:
final static class Key
{
final int type;
final String packageName;
final ActivityRecord activity;
final String who;
final int requestCode;
final Intent requestIntent; // 注意!
final String requestResolvedType;
final Bundle options;
Intent[] allIntents; // 注意!记录了当初获取PendingIntent时,用户所指定的所有intent
String[] allResolvedTypes;
final int flags;
final int hashCode;
. . . . . .
. . . . . .
}
请注意其中的allIntents[]数组域以及requestIntent域。前者记录了当初获取PendingIntent时,用户所指定的所有intent(虽然一般情况下只会指定一个intent,但类似getActivities()这样的函数还是可以指定多个intent的),而后者可以粗浅地理解为用户所指定的那个intent数组中的最后一个intent。现在大家应该清楚异步激发时用到的intent都存在哪里了吧。
Key的构造函数截选如下:
Key(int _t, String _p, ActivityRecord _a, String _w,
int _r, Intent[] _i, String[] _it, int _f, Bundle _o)
{
type = _t;
packageName = _p;
activity = _a;
who = _w;
requestCode = _r;
requestIntent = _i != null ? _i[_i.length-1] : null; // intent数组中的最后一个
requestResolvedType = _it != null ? _it[_it.length-1] : null;
allIntents = _i; // 所有intent
allResolvedTypes = _it;
flags = _f;
options = _o;
. . . . . .
}
Key不光承担着记录信息的作用,它还承担“键值”的作用。
2.3 AMS中的PendingIntentRecord总表
在AMS中,管理着系统中所有的PendingIntentRecord节点,所以需要把这些节点组织成一张表:
final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>
mIntentSenderRecords
这张哈希映射表的键值类型就是刚才所说的PendingIntentRecord.Key。
以后每当我们要获取PendingIntent对象时,PendingIntent里的mTarget是这样得到的:AMS会先查mIntentSenderRecords表,如果能找到符合的PendingIntentRecord节点,则返回之。如果找不到,就创建一个新的PendingIntentRecord节点。因为PendingIntentRecord是个binder实体,所以经过binder机制传递后,客户进程拿到的就是个合法的binder代理。
2.4 AMS里的getIntentSender()函数
getActivity()的原型:
public static PendingIntent getActivity(Context context, int requestCode,
Intent intent, int flags, Bundle options) //context参数是调用方的上下文。
requestCode是个简单的整数,起区分作用。
intent是异步激发时将发出的intent。
flags可以包含一些既有的标识,比如FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT等等。不少同学对这个域不是很清楚,我们后文会细说。
options可以携带一些额外的数据。
getActivity()的代码很简单,其参数基本上都传给了getIntentSender()。
IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(. . . . . .)
getIntentSender()的原型大体是这样的:
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes,
int flags, Bundle options) throws RemoteException;
其参数比getActivity()要多一些,我们逐个说明。
type参数表明PendingIntent的类型。getActivity()和getActivities()动作里指定的类型值是INTENT_SENDER_ACTIVITY,getBroadcast()和getService()和动作里指定的类型值分别是INTENT_SENDER_BROADCAST和INTENT_SENDER_SERVICE。另外,在Activity.java文件中,我们还看到一个createPendingResult()函数,这个函数表达了发起方的activity日后希望得到result回馈的意思,所以其内部调用getIntentSender()时指定的类型值为INTENT_SENDER_ACTIVITY_RESULT。
packageName参数表示发起端所属的包名。
token参数是个指代回馈目标方的代理。这是什么意思呢?我们常用的getActivity()、getBroadcast()和getService()中,只是把这个参数简单地指定为null,表示这个PendingIntent激发时,是不需要发回什么回馈的。不过当我们希望获取类型为INTENT_SENDER_ACTIVITY_RESULT的PendingIntent时,就需要指定token参数了。具体可参考createPendingResult()的代码:
public PendingIntent createPendingResult(int requestCode, Intent data, int flags)
{
String packageName = getPackageName();
try
{
data.setAllowFds(false);
IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY_RESULT,
packageName,
mParent == null ? mToken : mParent.mToken,
mEmbeddedID, requestCode, new Intent[] { data },
null, flags, null);
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
return null;
}
看到了吗?传入的token为Activity的mToken或者其mParent.mToken。说得简单点儿,AMS内部可以根据这个token找到其对应的ActivityRecord,日后当PendingIntent激发时,AMS可以根据这个ActivityRecord确定出该向哪个目标进程的哪个Activity发出result语义。
resultWho参数和token参数息息相关,一般也是null啦。在createPendingResult()中,其值为Activity的mEmbeddedID字符串。
requestCode参数是个简单的整数,可以在获取PendingIntent时由用户指定,它可以起区分的作用。
intents数组参数是异步激发时希望发出的intent。对于getActivity()、getBroadcast()和getService()来说,都只会指定一个intent而已。只有getActivities()会尝试一次传入若干intent。
resolvedTypes参数基本上和intent是相关的。一般是这样得到的:
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
这个值常常和intent内部的mData URI有关系,比如最终的值可能是URI对应的MIME类型。
flags参数可以指定PendingIntent的一些行为特点。它的取值是一些既有的比特标识的组合。目前可用的标识有:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT等等。有时候,flags中还可以附带若干FILL_IN_XXX标识。我们把常见的标识定义列举如下:
【PendingIntent中】
public static final int FLAG_ONE_SHOT = 1<<30;
public static final int FLAG_NO_CREATE = 1<<29;
public static final int FLAG_CANCEL_CURRENT = 1<<28;
public static final int FLAG_UPDATE_CURRENT = 1<<27;
【Intent中】
public static final int FILL_IN_ACTION = 1<<0;
public static final int FILL_IN_DATA = 1<<1;
public static final int FILL_IN_CATEGORIES = 1<<2;
public static final int FILL_IN_COMPONENT = 1<<3;
public static final int FILL_IN_PACKAGE = 1<<4;
public static final int FILL_IN_SOURCE_BOUNDS = 1<<5;
public static final int FILL_IN_SELECTOR = 1<<6;
public static final int FILL_IN_CLIP_DATA = 1<<7;
这些以FILL_IN_打头的标志位,主要是在intent对象的fillIn()函数里起作用:
public int fillIn(Intent other, int flags)
我们以FILL_IN_ACTION为例来说明,当我们执行类似srcIntent.fillIn(otherIntent, ...)的句子时,如果otherIntent的mAction域不是null值,那么fillIn()会在以下两种情况下,用otherIntent的mAction域值为srcIntent的mAction域赋值:
1) 当srcIntent的mAction域值为null时;
2) 如果fillIn的flags参数里携带了FILL_IN_ACTION标志位,那么即便srcIntent的mAction已经有值了,此时也会用otherIntent的mAction域值强行替换掉srcIntent的mAction域值。
其他FILL_IN_标志位和FILL_IN_ACTION的处理方式类似,我们不再赘述。
options参数可以携带一些额外数据。
2.4.1 getIntentSender()函数
getIntentSender()函数摘录如下:
public IIntentSender getIntentSender(int type, String packageName,
IBinder token, String resultWho,
int requestCode, Intent[] intents,
String[] resolvedTypes,
int flags, Bundle options)
{
. . . . . .
. . . . . .
int callingUid = Binder.getCallingUid();
. . . . . .
if (callingUid != 0 && callingUid != Process.SYSTEM_UID)
{
int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
UserId.getUserId(callingUid));
if (!UserId.isSameApp(callingUid, uid))
{
. . . . . .
throw new SecurityException(msg);
}
}
. . . . . .
return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(),
token, resultWho, requestCode, intents, resolvedTypes, flags, options);
. . . . . .
}
getIntentSender()函数中有一段逐条判断intents[]的代码,我用伪代码checkIntents(intents)来表示,这部分对应的实际代码如下:
for (int i=0; i<intents.length; i++)
{
Intent intent = intents[i];
if (intent != null)
{
if (intent.hasFileDescriptors())
{
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (type == ActivityManager.INTENT_SENDER_BROADCAST &&
(intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)
{
throw new IllegalArgumentException("Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
}
intents[i] = new Intent(intent);
}
}
这段代码说明在获取PendingIntent对象时,intent中是不能携带文件描述符的。而且如果这个PendingIntent是那种要发出广播的PendingIntent,那么intent中也不能携带FLAG_RECEIVER_BOOT_UPGRADE标识符。“BOOT_UPGRADE”应该是“启动并升级”的意思,它不能使用PendingIntent。
getIntentSender()中最核心的一句应该是调用getIntentSenderLocked()的那句。
2.4.2 getIntentSenderLocked()函数
getIntentSenderLocked()的代码截选如下:
【frameworks/base/services/java/com/android/server/am/ActivityManagerService.java】
IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid,
IBinder token, String resultWho,
int requestCode, Intent[] intents,
String[] resolvedTypes, int flags,
Bundle options)
{
. . . . . .
. . . . . .
. . . . . .
PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName,
activity, resultWho,
requestCode, intents,
resolvedTypes, flags, options);
WeakReference<PendingIntentRecord> ref;
ref = mIntentSenderRecords.get(key);
PendingIntentRecord rec = ref != null ? ref.get() : null;
if (rec != null)
{
if (!cancelCurrent)
{
if (updateCurrent)
{
if (rec.key.requestIntent != null) {
rec.key.requestIntent.replaceExtras(intents != null ?
intents[intents.length - 1] : null);
}
if (intents != null) {
intents[intents.length-1] = rec.key.requestIntent;
rec.key.allIntents = intents;
rec.key.allResolvedTypes = resolvedTypes;
} else {
rec.key.allIntents = null;
rec.key.allResolvedTypes = null;
}
}
return rec;
}
rec.canceled = true;
mIntentSenderRecords.remove(key);
}
if (noCreate)
{
return rec;
}
rec = new PendingIntentRecord(this, key, callingUid);
mIntentSenderRecords.put(key, rec.ref);
if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT)
{
if (activity.pendingResults == null)
{
activity.pendingResults = new HashSet<WeakReference<PendingIntentRecord>>();
}
activity.pendingResults.add(rec.ref);
}
return rec;
}
上面这段代码主要做的事情有:
1) 将传进来的多个参数信息整理成一个PendingIntentRecord.Key对象(key);
2) 尝试从mIntentSenderRecords总表中查找和key相符的PendingIntentRecord节点;
3) 根据flags参数所含有的意义,对得到的PendingIntentRecord进行加工。有时候修改之,有时候删除之。
4) 如果在总表中没有找到对应的PendingIntentRecord节点,或者根据flags的语义删除了刚找到的节点,那么此时的默认行为是创建一个新的PendingIntentRecord节点,并插入总表。除非flags中明确指定了FLAG_NO_CREATE,此时不会创建新节点。
2.4.3 FLAG
从getIntentSenderLocked()的代码中,我们终于明白了flags中那些特定比特值的意义了,flags比特值基本上都是在围绕着mIntentSenderRecords总表说事的。
其中,FLAG_CANCEL_CURRENT的意思是,当我们获取PendingIntent时,如果可以从总表中查到一个相符的已存在的PendingIntentRecord节点的话,那么需要把这个节点从总表中清理出去。
而在没有指定FLAG_CANCEL_CURRENT的大前提下,如果用户指定了FLAG_UPDATE_CURRENT标识,那么会用新的intents参数替掉刚查到的PendingIntentRecord中的旧intents。
而不管是刚清理了已存在的PendingIntentRecord,还是压根儿就没有找到符合的PendingIntentRecord,只要用户没有明确指定FLAG_NO_CREATE标识,系统就会尽力创建一个新的PendingIntentRecord节点,并插入总表。
至于FLAG_ONE_SHOT标识嘛,它并没有在getIntentSenderLocked()中露脸儿。它的名字是“FLAG_ONE_SHOT”,也就是“只打一枪”的意思,那么很明显,这个标识起作用的地方应该是在“激发”函数里。
在最终的激发函数(sendInner())里,我们可以看到下面的代码:
【frameworks/base/services/java/com/android/server/am/PendingIntentRecord.java】
int sendInner(int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission,
IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options)
{
synchronized(owner) {
if (!canceled)
{
sent = true;
if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
owner.cancelIntentSenderLocked(this, true);
canceled = true;
}
. . . . . .
. . . . . .
}
}
return ActivityManager.START_CANCELED;
}
意思很简单,一进行激发就把相应的PendingIntentRecord节点从总表中清理出去,而且把PendingIntentRecord的canceled域设为true。这样,以后即便外界再调用send()动作都没用了,因为再也无法进入if (!canceled)判断了。
2.4.4 将PendingIntentRecord节点插入总表
接下来getIntentSenderLocked()函数new了一个PendingIntentRecord节点,并将之插入mIntentSenderRecords总表中。
2.5 PendingIntent的激发动作
下面我们来看PendingIntent的激发动作。在前文我们已经说过,当需要激发PendingIntent之时,主要是通过调用PendingIntent的send()函数来完成激发动作的。PendingIntent提供了多个形式的send()函数,然而这些函数的内部其实调用的是同一个send(),其函数原型如下:
public void send(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler, String requiredPermission)
throws CanceledException
该函数内部最关键的一句是:
int res = mTarget.send(code, intent, resolvedType,
onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null,
requiredPermission);
这个mTarget域对应着AMS中的某个PendingIntentRecord。
我们看一下PendingIntentRecord一侧的send()函数,其代码如下:
public int send(int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission)
{
return sendInner(code, intent, resolvedType, finishedReceiver,
requiredPermission, null, null, 0, 0, 0, null);
}
其中sendInner()才是真正做激发动作的函数。
sendInner()完成的主要逻辑动作有:
1) 如果当前PendingIntentRecord节点已经处于canceled域为true的状态,那么说明这个节点已经被取消掉了,此时sendInner()不会做任何实质上的激发动作,只是简单地return ActivityManager.START_CANCELED而已。
2) 如果当初在创建这个节点时,使用者已经指定了FLAG_ONE_SHOT标志位的话,那么此时sendInner()会把这个PendingIntentRecord节点从AMS中的总表中摘除,并且把canceled域设为true。而后的操作和普通激发时的动作是一致的,也就是说也会走下面的第3)步。
3) 关于普通激发时应执行的逻辑动作是,根据当初创建PendingIntentRecord节点时,用户指定的type类型,进行不同的处理。这个type其实就是我们前文所说的INTENT_SENDER_ACTIVITY、INTENT_SENDER_BROADCAST、INTENT_SENDER_SERVICE等类型啦,大家如有兴趣,可自己参考本文一开始所说的getActivity()、getBroadcast()、getService()等函数的实现代码。
现在还有一个问题是,既然我们在当初获取PendingIntent时,已经指定了日后激发时需要递送的intent(或intent数组),那么为什么send()动作里还有一个intent参数呢?它们的关系又是什么呢?
PendingIntent机制的设计者是希望给激发端一个修改“待激发的intent”的机会。
比如当初我们获取PendingIntent对象时,如果在flags里设置了FILL_IN_ACTION标志位,那么就说明我们允许日后在某个激发点,用新的intent的mAction域值,替换掉我们最初给的intent的mAction域值。如果一开始没有设置FILL_IN_ACTION标志位,而且在最初的intent里已经有了非空的mAction域值的话,那么即使在激发端又传入了新intent,它也不可能修改用新intent的mAction域值替换旧intent的mAction域值。
当获取PendingIntent对象时,我们向AMS端传递了一个intent数组,虽然一般情况下这个数组里只有一个intent元素,但有时候我们也是有可能一次性传递多个intent的。比如getActivities()函数就可以一次传递多个intent。可是现在激发动作send()却只能传递一个intent参数,这该如何处理呢?答案很简单,所传入的intent只能影响已有的intent数组的最后一个intent元素。大家可以看看sendInner里allIntents[allIntents.length-1] = finalIntent;一句。
以下是一个简单的INTENT_SENDER_BROADCAST型PendingIntentRecord,激发动作就是发送一个广播:
owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType,
finishedReceiver, code, null, null,
requiredPermission, (finishedReceiver != null),
false, UserId.getUserId(uid));

实现开启和关闭android移动网络

开启和关闭移动数据网络有两种方法:
一种是通过操作系统的数据库改变APN(网络接入点),从而实现开启和关闭移动数据网络,
另一种是通过反射调用系统(ConnectivityManager)的setMoblieDataEnabled方法,通过操作该方法开启和关闭系统移动数据,同时也可以通过反射调用getMoblieDataEnabled方法获取当前的开启和关闭状态。

第一种方式:通过APN的方式开启和关闭

1. 匹配类:
public final class APNMatchTools
{
public static String CMWAP = "cmwap";
public static String CMNET = "cmnet";
public static String GWAP_3 = "3gwap";
public static String GNET_3 = "3gnet";
public static String UNIWAP = "uniwap";
public static String UNINET = "uninet";
public static String CTWAP = "ctwap";
public static String CTNET = "ctnet";
public static String matchAPN(String currentName)
{
if ("".equals(currentName) || null == currentName)
{
return "";
}
currentName = currentName.toLowerCase();
if (currentName.startsWith(CMNET))
return CMNET;
else if (currentName.startsWith(CMWAP))
return CMWAP;
else if (currentName.startsWith(GNET_3))
return GNET_3;
else if (currentName.startsWith(GWAP_3))
return GWAP_3;
else if (currentName.startsWith(UNINET))
return UNINET;
else if (currentName.startsWith(UNIWAP))
return UNIWAP;
else if (currentName.startsWith(CTWAP))
return CTWAP;
else if (currentName.startsWith(CTNET))
return CTNET;
else if (currentName.startsWith("default"))
return "default";
else
return "";
}
}
开启和关闭APN的方法在ApnSwitchTest类中实现,如下:
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
public class ApnSwitchTest extends Activity
{
Uri uri = Uri.parse("content://telephony/carriers/preferapn");
public void openAPN()
{
List<APN> list = getAPNList();
for (APN apn : list)
{
ContentValues cv = new ContentValues();
cv.put("apn", APNMatchTools.matchAPN(apn.apn));
cv.put("type", APNMatchTools.matchAPN(apn.type));
getContentResolver().update(uri, cv, "_id=?", new String[]
{
apn.id
});
}
}
public void closeAPN()
{
List<APN> list = getAPNList();
for (APN apn : list)
{
ContentValues cv = new ContentValues();
cv.put("apn", APNMatchTools.matchAPN(apn.apn) + "close");
cv.put("type", APNMatchTools.matchAPN(apn.type) + "close");
getContentResolver().update(uri, cv, "_id=?", new String[]
{
apn.id
});
}
}
public static class APN
{
String id;
String apn;
String type;
}
private List<APN> getAPNList()
{
String projection[] =
{
"_id, apn, type, current"
};
Cursor cr = getContentResolver().query(uri, projection, null, null, null);
List<APN> list = new ArrayList<APN>();
while (cr != null && cr.moveToNext())
{
Log.d("ApnSwitch", "id" + cr.getString(cr.getColumnIndex("_id")) + " \n" + "apn"
+ cr.getString(cr.getColumnIndex("apn")) + "\n" + "type"
+ cr.getString(cr.getColumnIndex("type")) + "\n" + "current"
+ cr.getString(cr.getColumnIndex("current")));
APN a = new APN();
a.id = cr.getString(cr.getColumnIndex("_id"));
a.apn = cr.getString(cr.getColumnIndex("apn"));
a.type = cr.getString(cr.getColumnIndex("type"));
list.add(a);
}
if (cr != null)
cr.close();
return list;
}
}
在AndroidManifext.xml文件中添加访问权限<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />

第二种方式: 通过getMoblieDataEnabled和setMoblieDataEnabled

通过APN的方式就是修改数据库,关闭APN其实就是给它随便匹配一个错误的APN。
当你通过这个方式关闭APN后,你在通过手机上的快捷开关开启移动数据网络时,是没效果的,也就是说开启不了,除非你再用同样的方法开启APN。
这说明快捷开关其实并不是通过这个方式来开启和关闭移动网络的,开启和关闭移动网络,通过Logcat可以看到是调用系统的getMoblieDataEnabled和setMoblieDataEnabled,但这个两个方法不能直接调用,必须通过反射机制调用
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
public class MobileDataSwitchTest extends Activity
{
public void setMobileDataStatus(Context context,boolean enabled)
{
ConnectivityManager conMgr = (ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
Class<?> conMgrClass = null;
Field iConMgrField = null;
Object iConMgr = null;
Class<?> iConMgrClass = null;
Method setMobileDataEnabledMethod = null;
try
{
conMgrClass = Class.forName(conMgr.getClass().getName());
iConMgrField = conMgrClass.getDeclaredField("mService");
iConMgrField.setAccessible(true);
iConMgr = iConMgrField.get(conMgr);
iConMgrClass = Class.forName(iConMgr.getClass().getName());
setMobileDataEnabledMethod = iConMgrClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
setMobileDataEnabledMethod.setAccessible(true);
setMobileDataEnabledMethod.invoke(iConMgr, enabled);
}
catch(ClassNotFoundException e)
{
e.printStackTrace();
}
catch(NoSuchFieldException e)
{
e.printStackTrace();
}
catch(SecurityException e)
{
e.printStackTrace();
}
catch(NoSuchMethodException e)
{
e.printStackTrace();
}
catch(IllegalArgumentException e)
{
e.printStackTrace();
}
catch(IllegalAccessException e)
{
e.printStackTrace();
}
catch(InvocationTargetException e)
{
e.printStackTrace();
}
}
public boolean getMobileDataStatus(String getMobileDataEnabled)
{
ConnectivityManager cm;
cm = (ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
Class cmClass = cm.getClass();
Class[] argClasses = null;
Object[] argObject = null;
Boolean isOpen = false;
try
{
Method method = cmClass.getMethod(getMobileDataEnabled, argClasses);
isOpen = (Boolean)method.invoke(cm, argObject);
}catch(Exception e)
{
e.printStackTrace();
}
return isOpen;
}
}
在AndroidMannifest.xml文件里添加访问权限
通过上面的代码可知,
当开启移动网络时调用setMobileDataStatus(context,true),关闭时调用setMobileDataStatus(context,false),
通过getMobileDataStatus(String getMobileDataEnabled)方法返回的布尔值判断当移动数据网络前状态的开启和关闭

Android:通过WebView显示网页

WebView可以使得网页轻松的内嵌到app里,还可以直接跟js相互调用。
webview有两个方法:setWebChromeClient 和 setWebClient
setWebClient:主要处理解析,渲染网页等浏览器做的事情
setWebChromeClient:辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等
WebViewClient用于处理各种通知、请求事件
在AndroidManifest.xml设置访问网络权限:
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webView"
一:加载本地/Web资源
example.html 存放在assets文件夹内
调用WebView的loadUrl()方法,
加载本地资源
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///android_asset/example.html");
加载web资源:
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("http://baidu.com");
二:在程序内打开网页
创建一个自己的WebViewClient,通过setWebViewClient关联
package com.example.testopen;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MainActivity extends Activity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
init();
}
private void init(){
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("http://baidu.com");
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
}
}
三:
如果访问的页面中有Javascript,则webview必须设置支持Javascript
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
四:
如果希望浏览的网页后退而不是退出浏览器,需要WebView覆盖URL加载,让它自动生成历史访问记录,那样就可以通过前进或后退访问已访问过的站点。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode==KeyEvent.KEYCODE_BACK)
{
if(webView.canGoBack())
{
webView.goBack();//返回上一页面
return true;
}
else
{
System.exit(0);//退出程序
}
}
return super.onKeyDown(keyCode, event);
}
五:判断页面加载过程
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
} else {
}
}
});
六:缓存的使用
优先使用缓存
webView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
不使用缓存:
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

调用系统浏览器访问网页

1)、Activity文件BrowserIntentActivity.java:
package com.android.browserintent;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.view.View.OnKeyListener;
public class BrowserIntentActivity extends Activity {
private EditText mUrlText;
private Button mGoButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mUrlText = (EditText)findViewById(R.id.url_field);
mGoButton = (Button)findViewById(R.id.go_button);
mGoButton.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
openNetPage();
}
});
mUrlText.setOnKeyListener(new OnKeyListener(){
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_ENTER){
openNetPage();
return true;
}
return false;
}
});
}
private void openNetPage(){
Uri uri = Uri.parse(mUrlText.getText().toString());
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
BrowserIntentActivity.this.startActivity(intent);
}
}
2)、布局文件main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<EditText
android:id="@+id/url_field"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:inputType="textUri"
android:imeOptions="actionGo"
android:layout_weight="9"
<Button
android:id="@+id/go_button"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Go"

UI组件

ActionBarSherlock
一个功能强大的ActionBar组件(不仅仅是ActionBar)。 Android 4.0+上使用native action bar, 低版本的Android模拟实现。强大到无语了,必须关注的一个开源组件。
SlidingMenu
一款优秀的侧滑菜单组件。 Google+, YouTube和Facebook在它们的应用中采用的侧滑菜单流行开来, 国内也有很多的应用如网易新闻等也采用了这种菜单方式。 侧滑菜单组件很好几种, 尤以此款最为好用。 看看官方网站上的知名用户就知道了。
Android-Universal-Image-Loader
强大灵活的加载、缓存、显示图片的库。
Android-ViewPagerIndicator
滑动页面几乎成了现在国内的应用的标配了,尤其在你第一次安装/启动应用时, 总会有几张介绍页面让你滑动显示。 用它吧,不会辜负你的。
Android-PullToRefresh
你不陌生, 在列表或者视图的顶部或者底部拖动可以刷新数据和页面。可以和ListView、ExpandableListView、GridView、WebView、ScrollView、HorizontalScrollView、ViewPager配合使用。
picasso
强大的图像下载和缓存库。
Android-Bootstrap
bootstrap风格的按钮。 使用Font Awesome图标字体。
facebook-android-sdk
Facebook提供的SDK, 可以在应用中集成Facebook.
android-bootstrap
不同于上面的Android-Bootstrap。 此项目是一个Android应用程序的模版, 相当于一个脚手架的功能。
AndroidStaggeredGrid
想做瀑布流式的应用吗?用它吧。
cardslib
一种卡片式的布局控件。 记得以前的有道词典的默认页就是这种布局。
ScreenScreen
PhotoView
ImageView扩展, 支持放缩和手势。
ListViewAnimations
为ListView增加动画, 还提供滑动删除选定项的功能。 类似Android的邮件的删除操作。
ion
又一个图像异步加载库。至少推荐三个了, 轮子不少。
android-menudrawer
又一个滑动菜单的组件。
android-swipelistview
又一个ListView扩展, 支持滑动操作。
android-pulltorefresh
又一个拖动刷新的组件。到此为止说了好多又了。 因为有些功能确实有多个不错的实现。
Crouton
Toast之外的另以选择。
StickyListHeaders
header可以固定的组件。 看下图。
drag-sort-listview
在ListItem拖动重排序。 开发者已经放弃维护了但是关注者依然不少。
FreeFlow
comcast出品的一个布局引擎。 FreeFlow让你很容易的定制布局和漂亮的转换动画。
看到comcast好激动, 因为他是笔者公司最大的客户。
FadingActionBar
折叠风格的ActionBar. 说起ActionBar, 第一选择不是ActionBarSherlock么?
android-viewflow
可以水平滚动的视图。
android-flip
可以实现类似Flipboard一样的翻页效果。
android-times-square
square出品的日期选择组件。
ScreenshotScreenshot
circular-progress-button
环形进度条按钮。
android-viewbadger
为视图增加badger。可以做的更好看点。
AndroidViewAnimations
漂亮的视图动画。
Android-SlideExpandableListView
有一个可以滑动的ExpandableListView组件。
pinned-section-listview
GroupName滑动到顶端时会固定不动直到另外一个GroupName到达顶端的ExpandListView
android-process-button
很酷的一个组件,可以在按钮下部显示漂亮的进度条,就像GMAIL一样。
android-betterpickers
更好的时间、日期库。
StaggeredGridView
瀑布流。类似Pinterest。
ProgressWheel
环形进度条。
Calligraphy
在Android中更容易的使用字体。
android-satellite-menu
类似Path一样的菜单。
android-actionbarstylegenerator
action bar风格在线制作。
SuperToasts
扩展的Toast.
GlassActionBar
毛玻璃效果的ction bar.
GraphView
可以产生放缩的线图和直方图。
StickyGridHeaders
可以固定header和分区的Grid。
BlurEffectForAndroidDesign
模糊效果的演示。
ArcMenu
又一个类似Path的环形菜单。
cropper
图像剪切和旋转。
Android-AppMsg
chromeview
Android webview组件的实现, 但是基于最新的Chromium 代码。
StandOut
很容易的创建弹出窗口。
android-iconify
集成FontAwesome 。
android-gif-drawable
看名字就知道了。
NumberProgressBar
漂亮的带数字的进度条。
Android-SwipeToDismiss
演示ListView滑动删除。
Shimmer-android
闪烁发光的文本框。
ScreenShotScreenShot
HoloColorPicker
Holo风格的颜色选择器。
android-crop
图像裁剪
android-edittext-validator
文本框校验
MultiChoiceAdapter
ListView 支持多选。
PinterestLikeAdapterView
瀑布流。
0101
MPAndroidChart
非常不错的图表工具。
Android-Action-Bar-Icons
图标资源。
Android-UndoBar
android-pdfview
AndroidCharts
图表控件
Android-Charts
国人实现的图表控件

内存优化

10月14日-16日,由CSDN和创新工场联合主办的MDCC 2015中国移动开发者大会将在北京新云南皇冠假日酒店隆重召开,现在抢注大会门票,即享多重好礼!在平台与技术iOS专场议题全方位揭秘之后,平台与技术Android专场也有新动作!与会讲师——腾讯Android应用开发工程师 胡凯围绕着“Android内存优化之OOM”进行了非常深度的技术分享。
腾讯Android应用开发工程师 胡凯
以下为正文:
Android的内存优化是性能优化中很重要的一部分,而避免OOM又是内存优化中比较核心的一点。这是一篇关于内存优化中如何避免OOM的总结性概要文章,内容大多都是和OOM有关的实践总结概要。理解错误或是偏差的地方,还请多包涵指正,谢谢!
(一)Android的内存管理机制
Google在Android的官网上有这样一篇文章,初步介绍了Android是如何管理应用的进程与内存分配:http://developer.android.com/training/articles/memory.html。 Android系统的Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色,Android系统没有为内存提供交换区,它使用 paging与 memory-mapping(mmapping)的机制来管理内存,下面简要概述一些Android系统中重要的内存管理基础概念。
1)共享内存
Android系统通过下面几种方式来实现共享内存:
Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动,并载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,然后在新的进程中加载并运行应用程序的代码。这就使得大多数的RAM pages被用来分配给framework的代码,同时促使RAM资源能够在应用的所有进程之间进行共享。
大多数static的数据被mmapped到一个进程中。这不仅仅让同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被paged out。常见的static数据包括Dalvik Code、app resources、so文件等。
大多数情况下,Android通过显式的分配共享内存区域(例如ashmem或gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。比如,Window Surface在App与Screen Compositor之间使用共享的内存,Cursor Buffers在Content Provider与Clients之间共享内存。
2)分配与回收内存
每一个进程的Dalvik Heap都反映了使用内存的占用范围。这就是通常逻辑意义上提到的Dalvik Heap Size,它可以随着需要进行增长,但是增长行为会有一个系统为它设定上限。
逻辑上讲的Heap Size和实际物理意义上使用的内存大小是不对等的,Proportional Set Size(PSS)记录了应用程序自身占用以及与其他进程进行共享的内存。
Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发GC操作,从而腾出更多空闲的内存空间。在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型,最近分配的对象会存放在Young Generation区域。当这个对象在该区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的GC操作。例如,刚分配到Young Generation区域的对象通常更容易被销毁回收,同时在Young Generation区域的GC操作速度会比Old Generation区域的GC操作速度更快(如图1所示)。
每一个Generation的内存区域都有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阀值时,会触发GC操作,以便腾出空间来存放其他新的对象(如图2所示)。
通常情况下,GC发生的时候,所有的线程都是会被暂停的。执行GC所占用的时间和它发生在哪一个Generation也有关系,Young Generation中的每次GC操作时间是最短的,Old Generation其次,Permanent Generation最长。执行时间的长短也和当前Generation中的对象数量有关,遍历树结构查找20000个对象比起遍历50个对象自然是要慢很多的。
3)限制应用的内存
为了整个系统的内存控制需要,Android系统为每一个应用程序都设置一个硬性的Dalvik Heap Size最大限制阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果你的应用占用内存空间已经接近这个阈值,此时再尝试分配内存的话,很容易引发OutOfMemoryError错误。
ActivityManager.getMemoryClass()可以用来查询当前应用的Heap Size阈值,这个方法会返回一个整数,表明应用的Heap Size阈值是多少MB(Megabates)。
4)应用切换操作
Android系统并不会在用户切换应用的时候执行交换内存操作。Android会把那些不包含Foreground组件的应用进程放到LRU Cache中。例如,当用户开始启动一个应用时,系统会为它创建一个进程。但是当用户离开此应用,进程不会立即被销毁,而是被放到系统的Cache当中。如果用户后来再切换回到这个应用,此进程就能够被马上完整地恢复,从而实现应用的快速切换。
如果你的应用中有一个被缓存的进程,这个进程会占用一定的内存空间,它会对系统的整体性能有影响。因此,当系统开始进入Low Memory的状态时,它会由系统根据LRU的规则与应用的优先级,内存占用情况以及其他因素的影响综合评估之后决定是否被杀掉。
对于那些非foreground的进程,Android系统是如何判断Kill掉哪些进程的问题,请参考Processes and Threads。
(二)OOM(Out Of Memory)
前面我们提到过使用getMemoryClass()的方法可以得到Dalvik Heap的阈值。简要地获取某个应用的内存占用情况可以参考下面的示例(更多内存查看的知识,可以参考Google官方教程: Investigating Your RAM Usage)
1)查看内存使用情况
通过命令行查看内存详细占用情况
2)发生OOM的条件
关于Native Heap、Dalvik Heap、PSS等内存管理机制比较复杂,这里就不展开详细描述。简单的说,通过不同的内存分配方式(malloc/mmap/JNIEnv/etc)对不同的对象(Bitmap/etc)进行操作,会因为Android系统版本的差异而产生不同的行为,对Native Heap与Dalvik Heap以及OOM的判断条件都会有所影响。在2.x的系统上,我们常常可以看到Heap Size的total值,明显超过了通过getMemoryClass()获取到的阈值而不会发生OOM的情况。那么,针对2.x与4.x的Android系统,到底如何判断会发生OOM呢?
Android 2.x系统GC LOG中的dalvik allocated + external allocated + 新分配的大小 >= getMemoryClass()值的时候就会发生OOM。 例如,假设有这么一段Dalvik输出的GC LOG:GC_FOR_MALLOC free 2K, 13% free 32586K/37455K, external 8989K/10356K, paused 20ms,那么32586+8989+(新分配23975)=65550>64M时,就会发生OOM。
Android 4.x的系统废除了external的计数器,类似Bitmap的分配改到Dalvik的Java Heap中申请。只要allocated + 新分配的内存 >= getMemoryClass()的时候就会发生OOM,如图7所示(注:虽然图示演示的是ART运行环境,但是统计规则还是和Dalvik保持一致)。
(三)如何避免OOM总结
前面介绍了一些基础的内存管理机制以及OOM的基础知识,那么在实践操作当中,有哪些指导性的规则可以参考呢?归纳下来,可以从四个方面着手,首先是减小对象的内存占用,其次是内存对象的重复利用,然后是避免对象的内存泄露,最后是内存使用策略优化。
减小对象的内存占用
避免OOM的第一步就是要尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。
1)使用更加轻量的数据结构
例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。图8演示了HashMap的简要工作原理,相比起Android专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
关于更多ArrayMap/SparseArray的讨论,请参考《 Android性能优化典范(三)》的前三个段落。
2)避免在Android里面使用Enum
Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。
3)减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
4)使用更小的图片
在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
内存对象的重复利用
大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里创建对象池,然后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减少对象的重复创建,从而降低内存的分配与回收(如图9所示)。
在Android上面最常用的一个缓存算法是LRU(Least Recently Use),简要操作原理如图10所示。
1)复用系统自带的资源
Android系统本身内置了很多的资源,比如字符串、颜色、图片、动画、样式以及简单布局等,这些资源都可以在应用程序中直接引用。这样做不仅能减少应用程序的自身负重,减小APK的大小,还可以在一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异、不符合需求的情况,还是需要应用程序自身内置进去。
2)注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用,如图11所示。
3)Bitmap对象的复用
在ListView与GridView等显示大量图片的控件里,需要使用LRU的机制来缓存处理好的Bitmap,如图12所示。
利用inBitmap的高级特性提高Android系统在Bitmap分配与释放执行效率(注:3.0以及4.4以后存在一些使用限制上的差异)。使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,如图13所示。
使用inBitmap需要注意几个限制条件:
在SDK 11 -> 18之间,重用的Bitmap大小必须是一致的。例如给inBitmap赋值的图片大小为100-100,那么新申请的Bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的Bitmap大小必须小于或者等于已经赋值过的Bitmap大小。
新申请的Bitmap与旧的Bitmap必须有相同的解码格式。例如大家都是8888的,如果前面的Bitmap是8888,那么就不能支持4444与565格式的Bitmap了。我们可以创建一个包含多种典型可重用Bitmap的对象池,这样后续的Bitmap创建都能够找到合适的“模板”去进行重用,如图14所示。
另外,在2.x的系统上,尽管Bitmap是分配在Native层,但还是无法避免被计算到OOM的引用计数器里。这里提示一下,不少应用会通过反射vBitmapFactory.Options里面的inNativeAlloc来达到扩大使用内存的目的,但是如果大家都这么做,对系统整体会造成一定的负面影响,建议谨慎采纳。
4)避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
5)StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
避免对象的内存泄露
内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,GC就会更容易被触发,容易出现内存抖动,从而引起性能问题(如图15所示)。
最新的LeakCanary开源控件,可以很好的帮助我们发现内存泄露的情况,更多关于LeakCanary的介绍,请看 这里( 中文使用说明)。另外也可以使用传统的MAT工具查找内存泄露,请参考 这里( 便捷的中文资料)。
1)注意Activity的泄漏
通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:
内部类引用导致Activity的泄漏
最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。
2)考虑使用Application Context而不是Activity Context
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。
3)注意临时Bitmap对象的及时回收
虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
需要特别留意的是Bitmap类里面提供的createBitmap()方法,如图16所示:
这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,才能够执行source bitmap的recycle方法。
4)注意监听器的注销
在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。
5)注意缓存容器中的对象泄漏
有时候,我们为了提高对象的复用性把某些对象放到缓存容器中,可是如果这些对象没有及时从容器中清除,也是有可能导致内存泄漏的。例如,针对2.3的系统,如果把drawable添加到缓存容器,因为drawable与View的强应用,很容易导致activity发生泄漏。而从4.0开始,就不存在这个问题。解决这个问题,需要对2.3系统上的缓存drawable做特殊封装,处理引用解绑的问题,避免泄漏的情况。
6)注意WebView的泄漏
Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,请看 这里。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
7)注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。
内存使用策略优化
1)谨慎使用large heap
正如前面提到的,Android设备根据硬件与软件的设置差异而存在不同大小的内存空间,他们为应用程序设置了不同大小的Heap限制阈值。你可以通过调用getMemoryClass()来获取应用的可用Heap大小。在一些特殊的情景下,你可以通过在manifest的application标签下添加largeHeap=true的属性来为应用声明一个更大的heap空间。然后,你可以通过getLargeMemoryClass()来获取到这个更大的heap size阈值。然而,声明得到更大Heap阈值的本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的因为你需要使用更多的内存而去请求一个大的Heap Size。只有当你清楚的知道哪里会使用大量的内存并且知道为什么这些内存必须被保留时才去使用large heap。因此请谨慎使用large heap属性。使用额外的内存空间会影响系统整体的用户体验,并且会使得每次gc的运行时间更长。在任务切换时,系统的性能会大打折扣。另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。
2)综合考虑设备内存阈值与其他因素设计合适的缓存大小
例如,在设计ListView或者GridView的Bitmap LRU缓存的时候,需要考虑的点有:
应用程序剩下了多少可用的内存空间?
有多少图片会被一次呈现到屏幕上?有多少图片需要事先缓存好以便快速滑动时能够立即显示到屏幕?
设备的屏幕大小与密度是多少? 一个xhdpi的设备会比hdpi需要一个更大的Cache来hold住同样数量的图片。
不同的页面针对Bitmap的设计的尺寸与配置是什么,大概会花费多少内存?
页面图片被访问的频率?是否存在其中的一部分比其他的图片具有更高的访问频繁?如果是,也许你想要保存那些最常访问的到内存中,或者为不同组别的位图(按访问频率分组)设置多个LruCache容器。
3)onLowMemory()与onTrimMemory()
Android用户可以随意在不同的应用之间进行快速切换。为了让background的应用能够迅速的切换到forground,每一个background的应用都会占用一定的内存。Android系统会根据当前的系统的内存使用情况,决定回收部分background的应用内存。如果background的应用从暂停状态直接被恢复到forground,能够获得较快的恢复体验,如果background应用是从Kill的状态进行恢复,相比之下就显得稍微有点慢,如图17所示。
onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。
onTrimMemory(int):Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。
TRIM_MEMORY_UI_HIDDEN:你的应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键退出应用,导致应用的UI界面完全不可见。这个时候应该释放一些不可见的时候非必须的资源
当程序正在前台运行的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:
TRIM_MEMORY_RUNNING_MODERATE:你的应用正在运行并且不会被列为可杀死的。但是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。
TRIM_MEMORY_RUNNING_LOW:你的应用正在运行且没有被列为可杀死的。但是设备正运行于更低内存的状态下,你应该释放不用的资源用来提升系统性能。
TRIM_MEMORY_RUNNING_CRITICAL:你的应用仍在运行,但是系统已经把LRU Cache中的大多数进程都已经杀死,因此你应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的LRU缓存中的进程,并且开始杀死那些之前被认为不应该杀死的进程,例如那个包含了一个运行态Service的进程。
当应用进程退到后台正在被Cached的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:
TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态并且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的应用进程并不是处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其他进程了。你应该释放那些容易恢复的资源,以便于你的进程可以保留下来,这样当用户回退到你的应用的时候才能够迅速恢复。
TRIM_MEMORY_MODERATE: 系统正运行于低内存状态并且你的进程已经已经接近LRU名单的中部位置。如果系统开始变得更加内存紧张,你的进程是有可能被杀死的。
TRIM_MEMORY_COMPLETE: 系统正运行于低内存的状态并且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释放任何不影响你的应用恢复状态的资源。
因为onTrimMemory()的回调是在API 14才被加进来的,对于老的版本,你可以使用onLowMemory)回调来进行兼容。onLowMemory相当与TRIM_MEMORY_COMPLETE。
请注意:当系统开始清除LRU缓存中的进程时,虽然它首先按照LRU的顺序来执行操作,但是它同样会考虑进程的内存使用量以及其他因素。占用越少的进程越容易被留下来。
4)资源文件需要选择合适的文件夹进行存放
我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。
5)Try catch某些大内存分配的操作
在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。例如decode bitmap的时候,catch到OOM,可以尝试把采样比例再增加一倍之后,再次尝试decode。
6)谨慎使用static对象
因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象(如图19所示)。
7)特别留意单例对象中不合理的持有
虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
8)珍惜Services资源
如果你的应用需要在后台使用service,除非它被触发并执行一个任务,否则其他时候Service都应该是停止状态。另外需要注意当这个service完成任务之后因为停止service失败而引起的内存泄漏。 当你启动一个Service,系统会倾向为了保留这个Service而一直保留Service所在的进程。这使得进程的运行代价很高,因为系统没有办法把Service所占用的RAM空间腾出来让给其他组件,另外Service还不能被Paged out。这减少了系统能够存放到LRU缓存当中的进程数量,它会影响应用之间的切换效率,甚至会导致系统内存使用不稳定,从而无法继续保持住所有目前正在运行的service。 建议使用IntentService,它会在处理完交代给它的任务之后尽快结束自己。更多信息,请阅读 Running in a Background Service。
9)优化布局层次,减少内存消耗
越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。
10)谨慎使用“抽象”编程
很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。
11)使用nano protobufs序列化数据
Protocol buffers是由Google为序列化结构数据而设计的,一种语言无关,平台无关,具有良好的扩展性。类似XML,却比XML更加轻量,快速,简单。如果你需要为你的数据实现序列化与协议化,建议使用nano protobufs。关于更多细节,请参考 protobuf readme的”Nano version”章节。
12)谨慎使用依赖注入框架
使用类似Guice或者RoboGuice等框架注入代码,在某种程度上可以简化你的代码。
使用RoboGuice之后,代码是简化了不少。然而,那些注入框架会通过扫描你的代码执行许多初始化的操作,这会导致你的代码需要大量的内存空间来mapping代码,而且mapped pages会长时间的被保留在内存中。除非真的很有必要,建议谨慎使用这种技术。
13)谨慎使用多进程
使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术。
一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个应用都运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法得到释放。类似这样的应用可以切分成2个进程:一个用来操作UI,另外一个给后台的Service。
14)使用ProGuard来剔除不需要的代码
ProGuard能够通过移除不需要的代码,重命名类,域与方法等等对代码进行压缩,优化与混淆。使用ProGuard可以使得你的代码更加紧凑,这样能够减少mapping代码所需要的内存空间。
15)谨慎使用第三方libraries
很多开源的library代码都不是为移动网络环境而编写的,如果运用在移动设备上,并不一定适合。即使是针对Android而设计的library,也需要特别谨慎,特别是在你不知道引入的library具体做了什么事情的时候。例如,其中一个library使用的是nano protobufs, 而另外一个使用的是micro protobufs。这样一来,在你的应用里面就有2种protobuf的实现方式。这样类似的冲突还可能发生在输出日志,加载图片,缓存等等模块里面。另外不要为了1个或者2个功能而导入整个library,如果没有一个合适的库与你的需求相吻合,你应该考虑自己去实现,而不是导入一个大而全的解决方案。
16)考虑不同的实现方式来优化内存占用
在某些情况下,设计的某个方案能够快速实现需求,但是这个方案却可能在内存占用上表现的效率不够好。例如:
对于上面这样一个时钟表盘的实现,最简单的就是使用很多张包含指针的表盘图片,使用帧动画实现指针的旋转。但是如果把指针扣出来,单独进行旋转绘制,显然比载入N多张图片占用的内存要少很多。当然这样做,代码复杂度上会有所增加,这里就需要在优化内存占用与实现简易度之间进行权衡了。
设计风格很大程度上会影响到程序的内存与性能,相对来说,如果大量使用类似Material Design的风格,不仅安装包可以变小,还可以减少内存的占用,渲染性能与加载性能都会有一定的提升。
内存优化并不就是说程序占用的内存越少就越好,如果因为想要保持更低的内存占用,而频繁触发执行gc操作,在某种程度上反而会导致应用性能整体有所下降,这里需要综合考虑做一定的权衡。
Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义。

性能优化

1、降低执行时间
这部分包括:缓存、数据存储优化、算法优化、JNI、逻辑优化、需求优化几种优化方式。
(1). 缓存
缓存主要包括对象缓存、IO缓存、网络缓存、DB缓存,对象缓存能减少内存的分配,IO缓存减少磁盘的读写次数,网络缓存减少网络传输,DB缓存较少Database的访问次数。
在内存、文件、数据库、网络的读写速度中,内存都是最优的,且速度数量级差别,所以尽量将需要频繁访问或访问一次消耗较大的数据存储在缓存中。
Android中常使用缓存:
a. 线程池
b. Android图片缓存,Android图片Sdcard缓存,数据预取缓存
c. 消息缓存
通过handler.obtainMessage复用之前的message,如下:
handler.sendMessage(handler.obtainMessage(0, object));
1
handler.sendMessage(handler.obtainMessage(0, object));
d. ListView缓存
e. 网络缓存
数据库缓存http response,根据http头信息中的Cache-Control域确定缓存过期时间。
f. 文件IO缓存
使用具有缓存策略的输入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.对文件、网络IO皆适用。
g. layout缓存
h. 其他需要频繁访问或访问一次消耗较大的数据缓存
(2). 数据存储优化
包括数据类型、数据结构的选择。
a. 数据类型选择
字符串拼接用StringBuilder代替String,在非并发情况下用StringBuilder代替StringBuffer。如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。
64位类型如long double的处理比32位如int慢
使用SoftReference、WeakReference相对正常的强应用来说更有利于系统垃圾回收
final类型存储在常量区中读取效率更高
LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高
b. 数据结构选择
常见的数据结构选择如:
ArrayList和LinkedList的选择,ArrayList根据index取值更快,LinkedList更占内存、随机插入删除更快速、扩容效率更高。一般推荐ArrayList。
ArrayList、HashMap、LinkedHashMap、HashSet的选择,hash系列数据结构查询速度更优,ArrayList存储有序元素,HashMap为键值对数据结构,LinkedHashMap可以记住加入次序的hashMap,HashSet不允许重复元素。
HashMap、WeakHashMap选择,WeakHashMap中元素可在适当时候被系统垃圾回收器自动回收,所以适合在内存紧张型中使用。
Collections.synchronizedMap和ConcurrentHashMap的选择,ConcurrentHashMap为细分锁,锁粒度更小,并发性能更优。Collections.synchronizedMap为对象锁,自己添加函数进行锁控制更方便。
Android也提供了一些性能更优的数据类型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
Sparse系列的数据结构是为key为int情况的特殊处理,采用二分查找及简单的数组存储,加上不需要泛型转换的开销,相对Map来说性能更优。不过我不太明白为啥默认的容量大小是10,是做过数据统计吗,还是说现在的内存优化不需要考虑这些东西,写16会死吗,还是建议大家根据自己可能的容量设置初始值。
(3). 算法优化
这个主题比较大,需要具体问题具体分析,尽量不用O(n*n)时间复杂度以上的算法,必要时候可用空间换时间。
查询考虑hash和二分,尽量不用递归。
(4). JNI
Android应用程序大都通过Java开发,需要Dalvik的JIT编译器将Java字节码转换成本地代码运行,而本地代码可以直接由设备管理器直接执行,节省了中间步骤,所以执行速度更快。不过需要注意从Java空间切换到本地空间需要开销,同时JIT编译器也能生成优化的本地代码,所以糟糕的本地代码不一定性能更优。
这个优化点会在后面单独用一片博客介绍。
(5). 逻辑优化
这个不同于算法,主要是理清程序逻辑,减少不必要的操作。
(6). 需求优化
这个就不说了,对于sb的需求可能带来的性能问题,只能说做为一个合格的程序员不能只是执行者,要学会说NO。不过不能拿这种接口敷衍产品经理哦。
2、异步,利用多线程提高TPS
充分利用多核Cpu优势,利用线程解决密集型计算、IO、网络等操作。
关于多线程可参考:Java线程池
在Android应用程序中由于系统ANR的限制,将可能造成主线程超时操作放入另外的工作线程中。在工作线程中可以通过handler和主线程交互。
3、提前或延迟操作,错开时间段提高TPS
(1) 延迟操作
不在Activity、Service、BroadcastReceiver的生命周期等对响应时间敏感函数中执行耗时操作,可适当delay。
Java中延迟操作可使用ScheduledExecutorService,不推荐使用Timer.schedule;
Android中除了支持ScheduledExecutorService之外,还有一些delay操作,如
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定时等。
(2) 提前操作
对于第一次调用较耗时操作,可统一放到初始化中,将耗时提前。如得到壁纸wallpaperManager.getDrawable();
4、网络优化
更多见 性能优化第四篇——移动网络优化
以下是网络优化中一些客户端和服务器端需要尽量遵守的准则:
a. 图片必须缓存,最好根据机型做图片做图片适配
b. 所有http请求必须添加httptimeout
c. 开启gzip压缩
d. api接口数据以json格式返回,而不是xml或html
e. 根据http头信息中的Cache-Control及expires域确定是否缓存请求结果。
f. 确定网络请求的connection是否keep-alive
g. 减少网络请求次数,服务器端适当做请求合并。
h. 减少重定向次数
i. api接口服务器端响应时间不超过100ms

Android 通过Service单独进程模仿离线推送 Server Push

首先简单阐述一下我对于消息推送的理解,这里拿QQ来举例吧,当我们手机端的QQ离线了,并且退出了QQ应用,但是这时候如果别人给我们发了信息,我们没有上线。服务器会将发送者发送的信息推送过来然后我们发布通知来显示通知我们的用户
原理:
在AndroidManifest.xml中注册Service时,有一个android:process属性这个属性有2种情况,即为.和:两种,其中.代表为此服务开启一个全局的独立进程,如果以:开头则为此服务开启一个为此应用私有的独立进程
编码实现:
ServerPushService文件:
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class ServerPushService extends Service{
private MessageThread messageThread = null;
private Intent messageIntent = null;
private PendingIntent messagePendingIntent = null;
private int messageNotificationID = 1000;
private Notification messageNotification = null;
private NotificationManager messageNotificationManager = null;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
messageNotification = new Notification();
messageNotification.icon = R.drawable.ic_launcher; //通知图片
messageNotification.tickerText = "新消息"; //通知标题
messageNotification.defaults = Notification.DEFAULT_SOUND;
messageNotificationManager = (NotificationManager) getSystemService(this.NOTIFICATION_SERVICE);
messageIntent = new Intent(this,MessageActivity.class);
messagePendingIntent = PendingIntent.getActivity(this, 0, messageIntent, 0);
MessageThread thread = new MessageThread();
thread.isRunning = true;
thread.start();
return super.onStartCommand(intent, flags, startId);
}
* 从服务端获取消息
* @author zhanglei
*
*/
class MessageThread extends Thread{
public boolean isRunning = true;
@Override
public void run() {
while(isRunning){
try {
Thread.sleep(10000);
if(getServerMessage().equals("yes")){
messageNotification.setLatestEventInfo(ServerPushService.this, "您有新消息!", "这是一条新的测试消息", messagePendingIntent);
messageNotificationManager.notify(messageNotificationID, messageNotification);
messageNotificationID++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
* 模拟了服务端的消息。实际应用中应该去服务器拿到message
* @return
*/
public String getServerMessage(){
return "yes";
}
}
注册该service在一个单独的进程中
<!-- 为此应用私有的独立进程 -->
<service
android:name="com.jay.serverpush.ServerPushService"
android:process=":message"
>
</service>
说明:该文件编写了一个service用于后台运行,在manifest里面将该service声明成progress为:开头的,这样在一个单独的进程里面运行,以实现在程序关闭之后达到进程不关闭的目的以此来实现离线推送的目的,编码中的注释很明确,扫描服务器、判断逻辑发布通知等,注释很明确在此不在阐述
开启服务的方式:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.startService(new Intent(this,ServerPushService.class));
}
this.startService(new Intent(this,ServerPushService.class));
通过这句话在第一次进入oncreate方法就开启了单独进程的服务

可缩放移动的ImageView

定义ImageView,实现功能如下:
1.初始化时图片垂直居中显示,拉伸图片宽度至ImageView宽度。
2.使用两根手指放大缩小图片,可设置最大放大倍数,当图片小于ImageView宽度时,在手指离开屏幕时恢复到ImageView宽度。
3.支持双击放大缩小。当图片处于未放大状态时,双击放大至指定倍数,当图片处于放大状态时,双击恢复至未放大状态。
4.图片拖动效果。当图片处于未放大状态时,不可拖动。
5.图片拖动效果。当放大后的高度不超过ImageView时,不可垂直拖动。(由于默认设置拉伸宽度至ImageView宽度,水平方向可不判断)。
6.图片拖动效果。当图片向右拖动时,若左边缘超出左边界,则停止水平拖动。同理上下右边缘,即拖动后不会看到背景留白。
首先讲一下原理,图片放大缩小无非就是使用Matrix类,而这里通过手势控制,那自然是需要监听onTouch事件,所以原理简单来说,就是通过监听onTouch的各种事件来控制Matrix类了。其中具体控制方式如下:
onTouchG&辅助操作
ACTION_DOWNiz 记录初始点,设置本次模式为拖动模式,ScaleType设置成Matrix
ACTION_POINTER_DOWNiz 设置本次模式为缩放模式
ACTION_MOVEj hj[h~7E66^hg7EG&6FY
ACTION_UPj h[>X{?iK{~XXk>ZiY nx{G&
双击7E66Y
ACTION_CANCELYU
现在开始,按功能点一一列代码说明:
首先自定义ImageView,由于和Matrix有关,取名为MatrixImageView.java,继承于ImageView.java
public class MatrixImageView extends ImageView{
private final static String TAG="MatrixImageView";
private GestureDetector mGestureDetector;
private Matrix mMatrix=new Matrix();
private float mImageWidth;
private float mImageHeight;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
MatrixTouchListener mListener=new MatrixTouchListener();
setOnTouchListener(mListener);
mGestureDetector=new GestureDetector(getContext(), new GestureListener(mListener));
setBackgroundColor(Color.BLACK);
setScaleType(ScaleType.FIT_CENTER);
}
由于用到了双击缩放效果,在此引用了手势类 GestureDetector,GestureListener就是继承与SimpleOnGestureListener的手势监听类了。MatrixTouchListener继承与onTouchListner,是Touch事件监听的主体。在构造函数最后一句setScaleType(ScaleType.FIT_CENTER),便是满足功能一的要点。
接下去,重写setImageBitmap方法。
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mMatrix.set(getImageMatrix());
float[] values=new float[9];
mMatrix.getValues(values);
mImageWidth=getWidth()/values[Matrix.MSCALE_X];
mImageHeight=(getHeight()-values[Matrix.MTRANS_Y]*2)/values[Matrix.MSCALE_Y];
}
在此是为了初始化几个重要的变量:
mMatrix:图片的原始Matrix,记录下来作为模板,之后的变化都是在这个Matrix的基础上进行
mImageWidth:图片的真实宽度,注意这个宽度是指图片在ImageView中的真实宽度,非显示宽度也非文件宽度。当我们把图片放入ImageView中时,会根据ImageView的宽高进行一个转换,转换结果记录在Matrix中。我们根据显示宽度与Matrix进行计算获得真实宽度。
mImageHeight:同宽度。
三种宽度的区别(非官方叫法,只为理解)
显示宽度:ImageView中的图片(Bitmap、Drawable)在ImageView中显示的高度,是通过Matrix计算之后的宽度。当放大图片时,这个宽度可能超过ImageView的宽度。
真实宽度:ImageView中的图片在Matrix计算之前的宽度。当ImageView宽为390,图片X轴缩放级别为0.5,一个填充满ImageView的X轴的图片的真实宽度为780。这个宽度和ImageView的ScaleType相关。
文件宽度:文件X轴分辨率,不一定等于真实宽度。
之所以在sitImageView中进行设置,是因为每次ImageView更换图片后这些变量都会跟着改变,我们只需要和当前图片相关的这些变量。若希望在layout中设置图片而不是代码中设置,请把上述代码移至构造函数。
接下来是重点的onTouch事件:
public class MatrixTouchListener implements OnTouchListener{
private static final int MODE_DRAG = 1;
private static final int MODE_ZOOM = 2;
private static final int MODE_UNABLE=3;
float mMaxScale=6;
float mDobleClickScale=2;
private int mMode = 0;//
private float mStartDis;
private Matrix mCurrentMatrix = new Matrix();
private PointF startPoint = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mMode=MODE_DRAG;
startPoint.set(event.getX(), event.getY());
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
reSetMatrix();
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_ZOOM) {
setZoomMatrix(event);
}else if (mMode==MODE_DRAG) {
setDragMatrix(event);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mMode==MODE_UNABLE) return true;
mMode=MODE_ZOOM;
mStartDis = distance(event);
break;
default:
break;
}
return mGestureDetector.onTouchEvent(event);
}
* 计算两个手指间的距离
* @param event
* @return
*/
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
return (float) Math.sqrt(dx * dx + dy * dy);
}
首先可以看到,在ACTION_DOWN和ACTION_POINTER_DOWN中主要是进行当前事件模式的确定。当我们按下一个点时,会触发Down事件,而按下第二个点后,又会触发Action_Pointer_Down事件,在此我们把按下一个点标记为拖动事件,按下两个点标记为缩放事件。先跟踪缩放事件方法setZoomMatrix(event);
* 设置缩放Matrix
* @param event
*/
private void setZoomMatrix(MotionEvent event) {
if(event.getPointerCount()<2) return;
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / mStartDis;// 得到缩放倍数
mStartDis=endDis;//重置距离
mCurrentMatrix.set(getImageMatrix());//初始化Matrix
float[] values=new float[9];
mCurrentMatrix.getValues(values);
scale = checkMaxScale(scale, values);
setImageMatrix(mCurrentMatrix);
}
}
首先我们判断是否点击了两个点,如果不是直接返回。接着,使用distance方法计算出两个点之间的距离。之前在Action_Pointer_Down中已经计算出了初始距离,这里计算出的是移动后的两个点的距离。通过这两个距离我们可以得出本次移动的缩放倍数,但这还没完,我们需要验证这个缩放倍数是否越界了:
* 检验scale,使图像缩放后不会超出最大倍数
* @param scale
* @param values
* @return
*/
private float checkMaxScale(float scale, float[] values) {
if(scale*values[Matrix.MSCALE_X]>mMaxScale)
scale=mMaxScale/values[Matrix.MSCALE_X];
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
return scale;
}
我们获取了Matrix矩阵中保存的数组,在这个数组中,values[Matrix.MSCALE_X](事实上就是数组的第0个)代表了X轴的缩放级别,判断一下图片当前的缩放级别再乘以刚得到的scale后是否回去越界,会的话就将其控制在边界值。之后,以ImageView的中心点为原点,在当前Matrix的基础上进行指定倍数的缩放。
在Move事件中缩放时我们只会阻止超越最大值的缩放,在UP事件中我们会对小于原始缩放值的缩放进行重置。方法reSetMatrix如下。
* 重置Matrix
*/
private void reSetMatrix() {
if(checkRest()){
mCurrentMatrix.set(mMatrix);
setImageMatrix(mCurrentMatrix);
}
}
* 判断是否需要重置
* @return 当前缩放级别小于模板缩放级别时,重置
*/
private boolean checkRest() {
float[] values=new float[9];
getImageMatrix().getValues(values);
float scale=values[Matrix.MSCALE_X];
mMatrix.getValues(values);
return scale<values[Matrix.MSCALE_X];
}
首先获取当前X轴缩放级别(由于默认拉伸宽度至ImageView宽度,缩放级别以X轴为准),再通过模板Matrix得到原始的X轴缩放级别,判断当前缩放级别是否小于模板缩放级别,若小于,则重置成模板缩放级别。
接下去是双击放大缩小图片效果,该功能在手势接口GestureListener中完成,主要代码如下:
private class GestureListener extends SimpleOnGestureListener{
private final MatrixTouchListener listener;
public GestureListener(MatrixTouchListener listener) {
this.listener=listener;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
listener.onDoubleClick();
return true;
}
* 双击时触发
*/
public void onDoubleClick(){
float scale=isZoomChanged()?1:mDobleClickScale;
mCurrentMatrix.set(mMatrix);//初始化Matrix
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
setImageMatrix(mCurrentMatrix);
}
* 判断缩放级别是否是改变过
* @return true表示非初始值,false表示初始值
*/
private boolean isZoomChanged() {
float[] values=new float[9];
getImageMatrix().getValues(values);
float scale=values[Matrix.MSCALE_X];
mMatrix.getValues(values);
return scale!=values[Matrix.MSCALE_X];
}
在构造函数中将onTouchListner传递来进来。在此只重写两个方法:Down和onDoubleTap,只有在Down事件中返回true,onDoubleTap才能正常触发。
在onDoubleClick事件中,首先通过isZoomChanged方法判断当前的缩放级别是否是模板Matrix的缩放级别,是的话将缩放倍数设置为2倍,否的话设置成1倍。在载入模板Matrix,在此基础上做缩放。
最后是图片拖动效果setDragMatrix()
public void setDragMatrix(MotionEvent event) {
if(isZoomChanged()){
float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
if(Math.sqrt(dx*dx+dy*dy)>10f){
startPoint.set(event.getX(), event.getY());
mCurrentMatrix.set(getImageMatrix());
float[] values=new float[9];
mCurrentMatrix.getValues(values);
dx=checkDxBound(values,dx);
dy=checkDyBound(values,dy);
mCurrentMatrix.postTranslate(dx, dy);
setImageMatrix(mCurrentMatrix);
}
}
}
首先是通过isZoomChanged方法判断是否缩放过,若未缩放过则不可拖动(这种情况下图片全貌都可以看到,不需要拖动)。接着,拿当前坐标和按下时记录的startPoint坐标进行计算,得出拖动的距离。需要注意的是,在此需要对拖动距离做一个判断,当其小于10f时不进行拖动,否则会和双击事件冲突(在双击事件前同样会触发Move事件,两者一同执行的话,双击的缩放无法正常工作)。当确定开始拖动的之后,先重置startPoint的坐标,接着,开始验证当前移动的位移量是否合法。
*和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
* @param values
* @param dx
* @return
*/
private float checkDxBound(float[] values,float dx) {
float width=getWidth();
if(mImageWidth*values[Matrix.MSCALE_X]<width)
return 0;
if(values[Matrix.MTRANS_X]+dx>0)
dx=-values[Matrix.MTRANS_X];
else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width))
dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];
return dx;
}
* 和当前矩阵对比,检验dy,使图像移动后不会超出ImageView边界
* @param values
* @param dy
* @return
*/
private float checkDyBound(float[] values, float dy) {
float height=getHeight();
if(mImageHeight*values[Matrix.MSCALE_Y]<height)
return 0;
if(values[Matrix.MTRANS_Y]+dy>0)
dy=-values[Matrix.MTRANS_Y];
else if(values[Matrix.MTRANS_Y]+dy<-(mImageHeight*values[Matrix.MSCALE_Y]-height))
dy=-(mImageHeight*values[Matrix.MSCALE_Y]-height)-values[Matrix.MTRANS_Y];
return dy;
}
以Y轴为例,首先获取ImageView高度,再通过sitImageBitmap方法中获取的图片真实高度和当前Y轴缩放级别计算出当前Y轴的显示高度。如果显示高度小于ImageView高度,表示当前显示的图片还没有ImageView高,在Y轴不需要移动都可看清全貌,Y轴位移量直接返回0。
假如显示高度超过了ImageView高度,获取图片当前在Y轴的位移量(values[Matrix.MTRANS_Y]值),将其加上计算出的位移量后是否大于0,若大于0,表示图片上边缘将会离开ImageView上边缘,需要重新计算位移量。若上述条件不成立,判断当前位移量加上计算后的位移量,是否小于图片显示高度-屏幕高度,若小于表示图片下边缘将离开ImageView下边缘,同样需要重新计算。最后返回计算的Y轴偏移量。X轴同理。最后使用验证过的X、Y轴偏移量,在当前图片Matrix的基础上行进行偏移。
最后贴下完整代码,Demo的话,可以到我的照相机Demo里查看,该功能为相册功能的一部分。
package com.linj.album.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
* @ClassName: MatrixImageView
* @Description: 带放大、缩小、移动效果的ImageView
* @author LinJ
* @date 2015-1-7 上午11:15:07
*
*/
public class MatrixImageView extends ImageView{
private final static String TAG="MatrixImageView";
private GestureDetector mGestureDetector;
private Matrix mMatrix=new Matrix();
private float mImageWidth;
private float mImageHeight;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
MatrixTouchListener mListener=new MatrixTouchListener();
setOnTouchListener(mListener);
mGestureDetector=new GestureDetector(getContext(), new GestureListener(mListener));
setBackgroundColor(Color.BLACK);
setScaleType(ScaleType.FIT_CENTER);
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mMatrix.set(getImageMatrix());
float[] values=new float[9];
mMatrix.getValues(values);
mImageWidth=getWidth()/values[Matrix.MSCALE_X];
mImageHeight=(getHeight()-values[Matrix.MTRANS_Y]*2)/values[Matrix.MSCALE_Y];
}
public class MatrixTouchListener implements OnTouchListener{
private static final int MODE_DRAG = 1;
private static final int MODE_ZOOM = 2;
private static final int MODE_UNABLE=3;
float mMaxScale=6;
float mDobleClickScale=2;
private int mMode = 0;//
private float mStartDis;
private Matrix mCurrentMatrix = new Matrix();
private PointF startPoint = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mMode=MODE_DRAG;
startPoint.set(event.getX(), event.getY());
isMatrixEnable();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
reSetMatrix();
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_ZOOM) {
setZoomMatrix(event);
}else if (mMode==MODE_DRAG) {
setDragMatrix(event);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mMode==MODE_UNABLE) return true;
mMode=MODE_ZOOM;
mStartDis = distance(event);
break;
default:
break;
}
return mGestureDetector.onTouchEvent(event);
}
public void setDragMatrix(MotionEvent event) {
if(isZoomChanged()){
float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
if(Math.sqrt(dx*dx+dy*dy)>10f){
startPoint.set(event.getX(), event.getY());
mCurrentMatrix.set(getImageMatrix());
float[] values=new float[9];
mCurrentMatrix.getValues(values);
dx=checkDxBound(values,dx);
dy=checkDyBound(values,dy);
mCurrentMatrix.postTranslate(dx, dy);
setImageMatrix(mCurrentMatrix);
}
}
}
* 判断缩放级别是否是改变过
* @return true表示非初始值,false表示初始值
*/
private boolean isZoomChanged() {
float[] values=new float[9];
getImageMatrix().getValues(values);
float scale=values[Matrix.MSCALE_X];
mMatrix.getValues(values);
return scale!=values[Matrix.MSCALE_X];
}
* 和当前矩阵对比,检验dy,使图像移动后不会超出ImageView边界
* @param values
* @param dy
* @return
*/
private float checkDyBound(float[] values, float dy) {
float height=getHeight();
if(mImageHeight*values[Matrix.MSCALE_Y]<height)
return 0;
if(values[Matrix.MTRANS_Y]+dy>0)
dy=-values[Matrix.MTRANS_Y];
else if(values[Matrix.MTRANS_Y]+dy<-(mImageHeight*values[Matrix.MSCALE_Y]-height))
dy=-(mImageHeight*values[Matrix.MSCALE_Y]-height)-values[Matrix.MTRANS_Y];
return dy;
}
*和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
* @param values
* @param dx
* @return
*/
private float checkDxBound(float[] values,float dx) {
float width=getWidth();
if(mImageWidth*values[Matrix.MSCALE_X]<width)
return 0;
if(values[Matrix.MTRANS_X]+dx>0)
dx=-values[Matrix.MTRANS_X];
else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width))
dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];
return dx;
}
* 设置缩放Matrix
* @param event
*/
private void setZoomMatrix(MotionEvent event) {
if(event.getPointerCount()<2) return;
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / mStartDis;// 得到缩放倍数
mStartDis=endDis;//重置距离
mCurrentMatrix.set(getImageMatrix());//初始化Matrix
float[] values=new float[9];
mCurrentMatrix.getValues(values);
scale = checkMaxScale(scale, values);
setImageMatrix(mCurrentMatrix);
}
}
* 检验scale,使图像缩放后不会超出最大倍数
* @param scale
* @param values
* @return
*/
private float checkMaxScale(float scale, float[] values) {
if(scale*values[Matrix.MSCALE_X]>mMaxScale)
scale=mMaxScale/values[Matrix.MSCALE_X];
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
return scale;
}
* 重置Matrix
*/
private void reSetMatrix() {
if(checkRest()){
mCurrentMatrix.set(mMatrix);
setImageMatrix(mCurrentMatrix);
}
}
* 判断是否需要重置
* @return 当前缩放级别小于模板缩放级别时,重置
*/
private boolean checkRest() {
float[] values=new float[9];
getImageMatrix().getValues(values);
float scale=values[Matrix.MSCALE_X];
mMatrix.getValues(values);
return scale<values[Matrix.MSCALE_X];
}
* 判断是否支持Matrix
*/
private void isMatrixEnable() {
if(getScaleType()!=ScaleType.CENTER){
setScaleType(ScaleType.MATRIX);
}else {
mMode=MODE_UNABLE;//设置为不支持手势
}
}
* 计算两个手指间的距离
* @param event
* @return
*/
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
return (float) Math.sqrt(dx * dx + dy * dy);
}
* 双击时触发
*/
public void onDoubleClick(){
float scale=isZoomChanged()?1:mDobleClickScale;
mCurrentMatrix.set(mMatrix);//初始化Matrix
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
setImageMatrix(mCurrentMatrix);
}
}
private class GestureListener extends SimpleOnGestureListener{
private final MatrixTouchListener listener;
public GestureListener(MatrixTouchListener listener) {
this.listener=listener;
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
listener.onDoubleClick();
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return super.onSingleTapUp(e);
}
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return super.onFling(e1, e2, velocityX, velocityY);
}
@Override
public void onShowPress(MotionEvent e) {
super.onShowPress(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
}
}
注:当该ImageView在其他ViewGroup中,如常用的ViewPager中时,Move事件会和ViewGroup事件冲突。在划屏进行ViewPager的Item切换时,Viewpager将会通过onInterceptTouchEvent方法拦截掉,返回给ImageView一个Cancel事件,这种情况下需要重写ViewGroup的onInterceptTouchEvent方法。
在MatrixImageView的基础上,以ViewPager作为测试容器做进一步优化。
实现功能
当进行缩放操作时,手势不会同时触发ViewPager的滑动切换Item事件。
当进行拖动操作时,除非图片已经到达左右边界,否则不触发ViewPager的滑动切换Item事件。
当进行拖动操作时,若图片左边缘到达左边界,则可以向左滑动触发ViewPager切换至前一个Item;当图片右边缘到达右边界,则可以向右滑动触发ViewPager的切换至后一个Item。
每个down-up(cancel)事件周期内只执行一种类型的事务操作(缩放、拖动或者ViewPager切换Item),防止多重事务互相干扰。
将事务处理封装到MatrixImageView类内,提供状态接口给ViewPager使用,方便适配多种ViewGroup。
实现原理
当ViewPager内嵌MatrixImageView时,由于MatrixImgaeView在Down事件中返回了true,表明ViewPager将捕获本次完整的Touch事件(Move-Ponit_Down-UP等等),其中最重要的一个事件便是Move事件,因为ViewPager自身需要捕获Move事件在onTouch中进行切换Item操作,MatrixImageView的捕获意味着它将无法响应。不过,ViewPager本身控制着Touch事件的下发操作,每个Touch事件的下发都遵从从上至下层层递归,在MatrixImageView真正获得Move事件前,Move事件必须经过ViewPager的onInterceptTouchEvent和dispatchTouchEvent事件,前者执行拦截操作后者执行下发操作。ViewPager便是在onInterceptTouchEvent中对Move事件进行了过滤,当移动距离超过一定值时,它会拦截掉Move事件,阻止MatrixImageView继续处理Touch事件的权利,转而让自身的onTouch事件处理。于是,我们要做的便是重写onInterceptTouchEvent事件,通过判断MatrixImageView的状态决定是否拦截。
具体实现
由于容器ViewPager在满足条件的时候会拦截掉子View的touch事件,因此需要自定义个ViewPager修改拦截逻辑。当MatriImageView进行缩放和拖动时,我们不希望ViewPager拦截。具体代码如下:
public class AlbumViewPager extends ViewPager implements OnChildMovingListener {
private boolean mChildIsBeingDragged=false;
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
if(mChildIsBeingDragged)
return false;
return super.onInterceptTouchEvent(arg0);
}
@Override
public void startDrag() {
mChildIsBeingDragged=true;
}
@Override
public void stopDrag() {
mChildIsBeingDragged=false;
}
}
public interface OnChildMovingListener{
public void startDrag();
public void stopDrag();
}
通过判断变量mChildIsBeingDragged的值决定是否拦截,而mChildIsBeingDragged的值通过OnChildMovingListener接口由MatriImageView进行设置。别忘了在PagerAdapter的instantiateItem中给MatriImageView设置监听接口
MatrixImageView imageView = (MatrixImageView) imageLayout.findViewById(R.id.image);
imageView.setOnMovingListener(AlbumViewPager.this);
ViewPager的改造便完成了,只需要新增一个变量和实现一个接口,之后对于事件的拦截操作都转到了MatrixImageView中。
接下去看下改造后的MatrixImageView的onTouch方法。
boolean mLeftDragable;
boolean mRightDragable;
boolean mFirstMove=false;
private PointF mStartPoint = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mMode=MODE_DRAG;
mStartPoint.set(event.getX(), event.getY());
isMatrixEnable();
startMove();
checkDragable();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
reSetMatrix();
stopMove();
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_ZOOM) {
setZoomMatrix(event);
}else if (mMode==MODE_DRAG) {
setDragMatrix(event);
}else {
stopMove();
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mMode==MODE_UNABLE) return true;
mMode=MODE_ZOOM;
mStartDis = distance(event);
break;
case MotionEvent.ACTION_POINTER_UP:
break;
default:
break;
}
return mGestureDetector.onTouchEvent(event);
}
其中加红部分的代码都是修改后的代码,逐一分析。
* 子控件开始进入移动状态,令ViewPager无法拦截对子控件的Touch事件
*/
private void startDrag(){
if(moveListener!=null) moveListener.startDrag();
}
* 子控件开始停止移动状态,ViewPager将拦截对子控件的Touch事件
*/
private void stopDrag(){
if(moveListener!=null) moveListener.stopDrag();
}
startDrag和stopDrag方法很简单,就是调用ViewPager传递进来的OnChildMovingListener接口进行mChildIsBeingDragged的设置。当监听到down事件时,表示开始拖动,当接收到up和cancel事件时,表示结束拖动,以这个逻辑来说,ViewGroup将永远无法拦截touch事件,所以我们还需要在其他地方设置stopDrag事件,后面说明。
接下去是在down事件中执行checkDragable方法:
* 根据当前图片左右边缘设置可拖拽状态
*/
private void checkDragable() {
mLeftDragable=true;
mRightDragable=true;
mFirstMove=true;
float[] values=new float[9];
getImageMatrix().getValues(values);
if(values[Matrix.MTRANS_X]>=0)
mRightDragable=false;
if((mImageWidth)*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X]<=getWidth()){
mLeftDragable=false;
}
}
该方法将会重置mLeftDragable、mRightDragable、mFirstMove三个参数的状态。mLeftDragable表示当前状态下的Matrix可以向左拖动,mRightDragable表示当前状态下的Matrix可以向右拖动,mFirstMove为每次完整touch事件(从down到up或cancel)中的第一次拖动操作标志。其中mLeftDragable和mRightDragable都是根据Matrix矩阵的数值计算出来的。
由于前面功能需求的时候说过"每个down-up(cancel)事件周期内只执行一种类型的事务操作(缩放、拖动或者ViewPager切换Item)",因此当进行缩放操作时,就不会再执行切换Item操作了,可以等缩放结束后执行up操作时stopDrag。而Move操作重点就是要识别是切换item还是拖动图片了。查看修改后的setDragMatrix代码
* 设置拖拽状态下的Matrix
* @param event
*/
public void setDragMatrix(MotionEvent event) {
if(isZoomChanged()){
float dx = event.getX() - mStartPoint.x; // 得到x轴的移动距离
float dy = event.getY() - mStartPoint.y; // 得到x轴的移动距离
if(Math.sqrt(dx*dx+dy*dy)>10f){
mStartPoint.set(event.getX(), event.getY());
mCurrentMatrix.set(getImageMatrix());
float[] values=new float[9];
mCurrentMatrix.getValues(values);
dy=checkDyBound(values,dy);
dx=checkDxBound(values,dx,dy);
mCurrentMatrix.postTranslate(dx, dy);
setImageMatrix(mCurrentMatrix);
}
}else {
stopDrag();
}
}
* 和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
* @param values
* @param dx
* @return
*/
private float checkDxBound(float[] values,float dx,float dy) {
float width=getWidth();
if(!mLeftDragable&&dx<0){
if(Math.abs(dx)*0.4f>Math.abs(dy)&&mFirstMove){
stopDrag();
}
return 0;
}
if(!mRightDragable&&dx>0){
if(Math.abs(dx)*0.4f>Math.abs(dy)&&mFirstMove){
stopDrag();
}
return 0;
}
mLeftDragable=true;
mRightDragable=true;
if(mFirstMove) mFirstMove=false;
if(mImageWidth*values[Matrix.MSCALE_X]<width){
return 0;
}
if(values[Matrix.MTRANS_X]+dx>0){
dx=-values[Matrix.MTRANS_X];
}
else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width)){
dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];
}
return dx;
}
处理逻辑是这样的:
1.判断当前缩放级别是否是原始缩放级别(isZoomChanged()),如果未缩放过那将可以直接切换Item,在这直接stopDrag。
2.若进行了缩放,那判断是否累移动了10f,当移动了10f之后计算出x轴和y轴的移动量,并且通过checkDyBound方法计算出y轴的真实移动量
3.进入checkDxBound方法,首先判断当前是否能够左移,如果不能左移而实际的x轴偏移量是向左的,那就返回x的偏移量为0,防止左移。同时,如果当前是第一次移动,那就表示本次不是左移操作,而是向前切换item,于是执行stopDrag方法,令ViewPager拦截掉对MatrixImageView的事件分发。另外在这里加入和Y轴偏移量的对比,是为了防止执行的是垂直方向的滑动而导致stopDrag,ViewPager自身对于X轴偏移量/2小于Y轴偏移量的情况是不当成切换Item意图的,这里设置为*0.4可以保证不冲突。
4.右移同理。
5.当第一次左移和右移判断结果都不是切换Item后,将mLeftDragable和mRightDragable都设置为true,表示可以正常移动了。之后就和单个MatrixImageView的拖动处理一样了。
到此便完成了内嵌到ViewGroup内的MatriImageView的改造。下面还有两点显示优化。
首先在reSetMatrix中加入了新的功能:当缩放后的图片高度未达到ImageView高度时,在up和cancel之后会将其Y轴居中,防止“放大图片-Y轴移动图片-缩小图片”导致图片位置不对称。异常图效果如下:
* 重置Matrix
*/
private void reSetMatrix() {
if(checkRest()){
mCurrentMatrix.set(mMatrix);
setImageMatrix(mCurrentMatrix);
}else {
float[] values=new float[9];
getImageMatrix().getValues(values);
float height=mImageHeight*values[Matrix.MSCALE_Y];
if(height<getHeight()){
float topMargin=(getHeight()-height)/2;
if(topMargin!=values[Matrix.MTRANS_Y]){
mCurrentMatrix.set(getImageMatrix());
mCurrentMatrix.postTranslate(0, topMargin-values[Matrix.MTRANS_Y]);
setImageMatrix(mCurrentMatrix);
}
}
}
}
优化了缩放操作的缩放x轴对称轴选择问题。在"图片放大-移动X轴-缩小图片"时,若直接以ImageView中心点为缩放原点,可能会导致缩放后的图片边缘离开ImageView边界。
出错图效果如下:
* 设置缩放Matrix
* @param event
*/
private void setZoomMatrix(MotionEvent event) {
if(event.getPointerCount()<2) return;
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / mStartDis;// 得到缩放倍数
mStartDis=endDis;//重置距离
mCurrentMatrix.set(getImageMatrix());//初始化Matrix
float[] values=new float[9];
mCurrentMatrix.getValues(values);
scale = checkMaxScale(scale, values);
PointF centerF=getCenter(scale,values);
mCurrentMatrix.postScale(scale, scale,centerF.x,centerF.y);
setImageMatrix(mCurrentMatrix);
}
}
* 获取缩放的中心点。
* @param scale
* @param values
* @return
*/
private PointF getCenter(float scale,float[] values) {
if(scale*values[Matrix.MSCALE_X]<mScale||scale>=1){
return new PointF(getWidth()/2,getHeight()/2);
}
float cx=getWidth()/2;
float cy=getHeight()/2;
if((getWidth()/2-values[Matrix.MTRANS_X])*scale<getWidth()/2)
cx=0;
if((mImageWidth*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X])*scale<getWidth())
cx=getWidth();
return new PointF(cx,cy);
}
通过判断图片宽度,决定是以ImageView中点为X轴缩放原点,还是以左右边缘为缩放原点。

ADB无法连接

需root
su //如果前面显示的符号是$ ,则运行此命令切换到root
setprop service.adb.tcp.port 5555
stop adbd
start adbd

Android进入Recovery模式方法

同时按开机键和音量+,如不能启动,
先开机键按一秒然后马上按上下音量进去
在recovery界面不动,按键不动,等几十秒还是不行,同时按音量上下键和开机键3---5次

Android进入开发者模式

连续点击七次版本号,设置菜单界面出现“开发者者选项”

The content of the adapter has changed but ListView did not receive a notification

The content of the adapter has changed but ListView did not receive a notification
在Android开发过程中,使用了大量的ListView,发现这个错误偶尔会出现。特别是做压力测试的时候,不停的点击刷新,更容易出现这个错误。代码中已经使用了Adapter的notifyDataSetChanged()方法通知UI更新了,但是还是会出现这个错误。究其根本原因,还是线程之间同步的问题。比如,线程1更新了Adapter中的内容,却还没有来得及通知内容已经更新,就又有线程2更新了Adapter中的内容,这个时候如果线程1再通知内容更新,就会出现上述异常了。
解决办法:
对线程进行管理,如果当前Actitivty暂停了,及时停止这些线程。
数据更新后,要及时使用notifyDataSetChanged()方法通知UI,避免出现数据不一致的情况。
数据的更新,最好放在主线程中进行。这样可以使用同步数据更新与通知内容更新部分的代码。
在用到多线程的时候,可以对数据做缓存处理, 比如与ListView绑定的数据存储在ArrayList (dataList), 在线程中先将数据加入到临时ArrayList(tmpList) , 最后在调用notifyDataSetChanged()方法通知UI更新之前, 把临时ArrayList(tmpList)中的数据更新到ArrayList(dataList)中, 然后清空临时ArrayList(tmpList)数据。

import语句

Eclipse自动添加import语句, 使用Ctrl + Shift + o组合, 可以自动查找Java的import语句进行添加;
Android Studio默认是Alt+Enter单个添加import语句, 可以修改IDE, 使其自动添加, 所使用的java库;
位置: Files ->Settings-> IDE Settings-> Editor -> Auto Import
选择: Add unambiguous imports on the fly

android开发新浪微博官网sdk怎么才能分享url

WebpageObject mediaObject = new WebpageObject();
mediaObject.identify = Utility.generateGUID();
mediaObject.title = "我分享了一张\"" + CacheData.photoInfo.getM_nickname() + "\"的照片,快来围观";
mediaObject.description = CacheData.photoInfo.getPhoto_text();
Bitmap bitmap = photo.getDrawingCache().copy(Config.ARGB_8888, false);
Bitmap thumbBmp = Bitmap.createScaledBitmap(bitmap, 64, 64, true);
mediaObject.thumbData = ImageUnit.bmpToByteArray(thumbBmp, true);
mediaObject.actionUrl = CacheData.appInfo.getShareURL() + CacheData.photoInfo.getM_id() + "/" + CacheData.photoInfo.getP_id();
mediaObject.defaultText = "照片分享";
微博分享 auth faild 原因:APPKEY不对
http://open.weibo.com/wiki/Error_code

java.lang.UnsatisfiedLinkError

java.lang.UnsatisfiedLinkError: com.android.tools.fd.runtime.IncrementalClassLoader
java.lang.UnsatisfiedLinkError: com.android.tools.fd.runtime.IncrementalClassLoader$DelegateClassLoader[DexPathList[[dex file "/data/data/com.shiqichuban.android/files/instant-run/dex/slice-
库未找到, so库未打包或未识别, 生成apk时加入相应库;如已加入则clean之后再重新编译
android studio可在build.gradle中加入
task nativeLibsToJar(type: Zip, description: "create a jar archive of the native libs") {
destinationDir file("$projectDir/libs")
baseName "Native_Libs2"
extension "jar"
from fileTree(dir: "libs", include: "**/*.so")
into "lib"
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn(nativeLibsToJar)
}

java.lang.ClassNotFoundException

jar包未导入
http://blog.csdn.net/wulianghuan/article/details/44567001

Conversion to Dalvik format failed with error 1

Java版本选择Java 1.6编译

用release key生成Debug版本

Android Studio
在App build.gradle修改
signingConfigs {
debug {
storeFile file(“key文件名”)
storePassword “密码”
keyAlias “别名”
keyPassword “别名密码”
}
relealse {
storeFile file(“key文件名”)
storePassword “密码”
keyAlias “别名”
keyPassword “别名密码”
}
}
Eclipse修改方法如下:
1. 首先当然是先复制一份正式证书出来作为要修改为的临时调试证书。这里我们这支的keystore 的密码为hahaha,alias为silence,alias的密码为xixixi
2. 修改keystore密码的命令(keytool为JDK带的命令行工具):
keytool -storepasswd -keystore my.keystore
其中,my.keystore是复制出来的证书文件,执行后会提示输入证书的当前密码(hahaha),和新密码(android)以及重复新密码(android)确认。这一步需要将密码改为android。
3. 修改keystore的alias:
keytool -changealias -keystore my.keystore -alias my_name -destalias?androiddebugkey
这一步中,my_name是证书中当前的alias(silence),-destalias指定的是要修改为的alias,这里按规矩来,改为androiddebugkey!这个命令会先后提示输入keystore的密码(android,已经在第二步中进行了修改)和当前alias的密码(xixixi)。
4. 修改alias的密码:
keytool -keypasswd -keystore my.keystore -alias?androiddebugkey
这一步执行后会提示输入keystore密码(android),alias密码(xixixi),然后提示输入新的alias密码(android),同样,按规矩来,改为android!
以上几个操作执行后,my.keystore就是符合规矩的debug keystore了,接着在Eclipse的ADT设置中选中这个custom debug keystore即可,如下:
key.alias= 别名
此别名一开始以为可以随便写,后来多次尝试发现乱写打包一定不成功,此别名要与第一次生成签名时的别名一至,由于签名不是我申请的,申请签名的同事也忘记了签名的别名,如此只好找工具看一下签名的信息了,其实工具不用找了,在jdk中有
直接keytool -list? -v -keystore xxxx.keystore -storepass 密码  签名的信息就有了
查看已有apk的签名 sha1值:
查看三方应用或是系统应用签名,把apk改成rar后缀,然后解压出来,将其中META-INF下的ANDROID.RSA文件(有的不是,反正就是.RSA文件),通过命令 ? keytool -printcert -file META-INF/CERT.RSA查看。如下:
C:\Users\ywj>keytool -printcert -file D:\ForComputer\chromDownload\JinfengAppV2p0\META-INF\ANDROID1.RSA

INSTALL_FAILED_SHARED_USER_INCOMPATIBLE

eclipse编译出来的apk,安装时报出INSTALL_FAILED_SHARED_USER_INCOMPATIBLE的错误
原因:apk的AndroidManifest.xml中声明了android:sharedUserId="android.uid.system",但没有相应的签名
解决方案:
一 解决方法1
把android:sharedUserId="android.uid.system"声明删掉,不过删掉之后可能就无权限进行某些操作
二 解决方法2
1. 找到编译目标系统时的签名证书platform.pk8和platform.x509.pem,在android源码目录build\target\product\security下。
2. 将签名工具(signapk.jar)、签名证书(platform.pk8和platform.x509.pem)及编译出来的apk文件都放到同一目录
3.执行签名命令
signapk.jar platform.x509.pem platform.pk8 Demo.apk signedDemo.apk
4.将signedDemo.apk安装即可
三 解决方法3
直接把目标系统的签名证书platform.pk8和platform.x509.pem(根据android.mk文件的LOCAL_CERTIFICATE 定义,也可能是shared.pk8、media.pk8等)覆盖到build\target\product\security也可,这样就不需要再手工签名了。

Errors running builder 'Integrated External Tool Builder' on project '

解决:
项目—-右键Properties—-Builders—-取消相应项

Android导入项目时Android jar包丢失的解决

解决:
在项目的Properties中,选中Android,把右边的Project Build Target选中相应Android版本

Unable to resolve target 'android-XX'解决

出现 “Unable to resolve target 'android-XX'”,
解决办法进入你的android project跟目录,找到此文件 project.properties(或default.properties),找到target=android-XX出现此错是因为你的android环境跟此处不对应,那么,你只需要将此处的android版本改成你机器上配置的android版本即可,例如target=android-15
进入activity类中,一些重写的方法的地方总是出现 "Remove @Overrideannotation",
解决办法:出现此错误的原因主要是 编译器 版本的 的问题,Java 1.5的编译器默认对父类的方法进行覆盖,采用@Override进行说明;但1.6已经扩展到对接口的方法;所以如果还是以Java 1.5的编译器来编译的话,会出现错误,默认是java1.5的,你需要将它设置到1.6打开你的eclipse,进入 window->Preferences->Java->Compiler 把它改为 Java1.6即可

Android @Override 详解

Android @override 作用

  @Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处:
1、可以当注释用,方便阅读
2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错比如你如果没写@Override而你下面的方法名又写错了,这时你的编译器是可以通过的(它以为这个方法是你的子类中自己增加的方法)
  example:
  在重写父类的onCreate时,在方法前面加上@Override系统可以帮你检查方法的正确性,如果你不加@Override,则编译器将不会检测出错误,而是会认为你新定义了一个方法。

Android @override 错误

现象:
java: 1801: method does not override a method from its superclass @Override
原因:
Eclipse is defaulting to Java 1.5 and you have classes implementing interface methods (which in Java 1.6 can be annotated with @Override, but in Java 1.5 can only be applied to methods overriding a superclass method).
就是说Java 1.5的编译器默认对父类的方法进行覆盖,采用@Override进行说明;但1.6已经扩展到对接口的方法;所以如果还是以Java 1.5的编译器来编译的话,会出现错误。
解决方案:
Go to your project/ide preferences and set the java compiler level to 1.6 and also make sure you select JRE 1.6 to execute your program from eclipse.
java的编译器是1.6版本的,选择编译级别为1.6,但重要的一点是,选择build的android库是1.5的即可,生成的apk程序是可以在1.5内核上跑起来。
解决方法:
在eclipse中 选择Window –> Preferences –> Java –> Compiler 选择1.6 ,如果还不可以,在Compiler中选择onfigure Project Specific Settings 选择编译器版本为1.6

Android开发:@SuppressLint( NewApi )错误详解

Improving Your Code with lint
android带的lint工具提示的,帮助提升代码
如不想用,可以右键点工程,然后在android tools 中,选择 clear lint marker

Java注释Override、Deprecated、SuppressWarnings详解

一、什么是注释

说起注释,得先提一提什么是元数据(metadata)。所谓元数据就是数据的数据。也就是说,元数据是描述数据的。就象数据表中的字段一样,每个字段描述了这个字段下的数据的含义。而J2SE5.0中提供的注释就是java源代码的元数据,也就是说注释是描述java源代码的。在J2SE5.0中可以自定义注释。使用时在@后面跟注释的名字。

二、J2SE5.0中预定义的注释

在J2SE5.0的java.lang包中预定义了三个注释。它们是Override、Deprecated和SuppressWarnings。下面分别解释它们的含义。
Override
这个注释的作用是标识某一个方法是否覆盖了它的父类的方法。那么为什么要标识呢?让我们来看看如果不用Override标识会发生什么事情。
假设有两个类Class1和ParentClass1,用Class1中的myMethod1方法覆盖ParentClass1中的myMethod1方法。
class ParentClass1
{
public void myMethod1() {...}
}
class Class1 extends ParentClass1
{
public void myMethod2() {...}
}
建立Class1的实例,并且调用myMethod1方法
ParentClass1 c1 = new Class1();
c1.myMethod1();
以上的代码可以正常编译通过和运行。但是在写Class1的代码时,误将myMethod1写成了myMethod2,然而在调用时,myMethod1并未被覆盖。因此,c1.myMethod1()调用的还是ParentClass1的myMethod1方法。如程序员并未意识到这一点。因此,这可能会产生bug。
如果我们使用Override来修饰Class1中的myMethod1方法,当myMethod1被误写成别的方法时,编译器就会报错。因此,就可以避免这类错误。
class Class1 extends ParentClass1
{
@Override // 编译器产生一个错误
public void myMethod2()
{...}
}
以上代码编译不能通过,被Override注释的方法必须在父类中存在同样的方法程序才能编译通过。也就是说只有下面的代码才能正确编译。
class Class1 extends ParentClass1
{
@Override
public void myMethod1() {...}
}
Deprecated
这个注释是一个标记注释。所谓标记注释,就是在源程序中加入这个标记后,并不影响程序的编译,但有时编译器会显示一些警告信息。
那么Deprecated注释是什么意思呢?如果你经常使用eclipse等IDE编写java程序时,可能会经常在属性或方法提示中看到这个词。如果某个类成员的提示中出现了个词,就表示这个并不建议使用这个类成员。因为这个类成员在未来的JDK版本中可能被删除。之所以在现在还保留,是因为给那些已经使用了这些类成员的程序一个缓冲期。如果现在就去了,那么这些程序就无法在新的编译器中编译了。
Deprecated注释和这些类成员有关。使用Deprecated标注一个类成员后,这个类成员在显示上就会有一些变化。在eclipse中非常明显。
加上@Deprecated后的类成员在eclipse中有三个地方发生的变化。红色框里面的是变化的部分。
1. 方法定义处
2. 方法引用处
3. 显示的成员列表中
发生这些变化并不会影响编译,只是提醒一下程序员,这个方法以后是要被删除的,最好别用。
Deprecated注释还有一个作用。就是如果一个类从另外一个类继承,并且override被继承类的Deprecated方法,在编译时将会出现一个警告。如test.java的内容如下:
class Class1
{
@Deprecated
public void myMethod(){}
}
class Class2 extends Class1
{
public void myMethod(){}
}
运行javac test.java 出现如下警告:
注意:test.java 使用或覆盖了已过时的 API。
注意:要了解详细信息,请使用 -Xlint:deprecation 重新编译
使用-Xlint:deprecation显示更详细的警告信息:
test.java:4: 警告:[deprecation] Class1 中的 myMethod() 已过时
public void myMethod()
1 警告
这些警告并不会影响编译,只是提醒你一下尽量不要用myMethod方法。
SuppressWarnings
这个世界的事物总是成对出现。即然有使编译器产生警告信息的,那么就有抑制编译器产生警告信息的。
SuppressWarnings注释就是为了这样一个目的而存在的。让我们先看一看如下的代码。
public void myMethod()
{
List wordList = new ArrayList();
wordList.add("foo");
}
这是一个类中的方法。编译它,将会得到如下的警告。
注意:Testannotation.java 使用了未经检查或不安全的操作。
注意:要了解详细信息,请使用 -Xlint:unchecked 重新编译。
这两行警告信息表示List类必须使用范型才是安全的,才可以进行类型检查。如果想不显示这个警告信息有两种方法。一个是将这个方法进行如下改写:
public void myMethod()
{
List<String> wordList = new ArrayList<String>();
wordList.add("foo");
}
另外一种方法就是使用@SuppressWarnings。
@SuppressWarnings (value={"unchecked"})
public void myMethod()
{
List wordList = new ArrayList();
wordList.add("foo");
}
要注意的是SuppressWarnings和前两个注释不一样。这个注释有一个属性。当然,还可以抑制其它警告,如:
@SuppressWarnings (value={"unchecked", "fallthrough"})

三、如何自定义注释

注释的强大之处是它不仅可以使java程序变成自描述的,而且允许程序员自定义注释。注释的定义和接口差不多,只是在interface前面多了一个“@”。
public @interface MyAnnotation
{
}
上面的代码是一个最简单的注释。这个注释没有属性。也可以理解为是一个标记注释。就象Serializable接口一样是一个标记接口,里面未定义任何方法。
当然,也可以定义有属性的注释。
public @interface MyAnnotation
{
String value();
}
可以按如下格式使用MyAnnotation
@MyAnnotation("abc")
public void myMethod()
{
}
看了上面的代码,大家可能有一个疑问。怎么没有使用value,而直接就写”abc”了。那么”abc”到底传给谁了。其实这里有一个约定。如果没有写属性名的值,而这个注释又有value属性,就将这个值赋给value属性,如果没有,就出现编译错误。
除了可以省略属性名,还可以省略属性值。这就是默认值。
public @interface MyAnnotation
{
public String myMethod(){} default “xyz”;
}
可以直接使用MyAnnotation
@MyAnnotation // 使用默认值xyz
public void myMethod()
{
}
也可以这样使用
@MyAnnotation(myMethod=”abc”)
public void myMethod()
{
}
如果要使用多个属性的话。可以参考如下代码。
public @interface MyAnnotation
{
public enum MyEnum{A, B, C}
public MyEnum.value1() {}
public String value2() {}
}
@MyAnnotation(value1=MyAnnotation.MyEnum.A, value2 = “xyz”)
public void myMethod()
{
}
这一节讨论了如何自定义注释。那么定义注释有什么用呢?有什么方法对注释进行限制呢?我们能从程序中得到注释吗?这些疑问都可以从下面的内容找到答案。

四、如何对注释进行注释

这一节的题目读起来虽然有些绕口,但它所蕴涵的知识却对设计更强大的java程序有很大帮助。
在上一节讨论了自定义注释,由此我们可知注释在J2SE5.0中也和类、接口一样。是程序中的一个基本的组成部分。既然可以对类、接口进行注释,那么当然也可以对注释进行注释。
使用普通注释对注释进行注释的方法和对类、接口进行注释的方法一样。所不同的是,J2SE5.0为注释单独提供了4种注释。它们是Target、Retention、Documented和Inherited。下面就分别介绍这4种注释。
Target
这个注释理解起来非常简单。由于target的中文意思是“目标”,因此,我们可能已经猜到这个注释和某一些目标相关。那么这些目标是指什么呢?大家可以先看看下面的代码。
@Target(ElementType.METHOD)
@interface MyAnnotation {}
@MyAnnotation // 错误的使用
public class Class1
{
@MyAnnotation // 正确的使用
public void myMethod1() {}
}
以上代码定义了一个注释MyAnnotation和一个类Class1,并且使用MyAnnotation分别对Class1和myMethod1进行注释。如果编译这段代码是无法通过的。也许有些人感到惊讶,没错啊!但问题就出在@Target(ElementType.METHOD)上,由于Target使用了一个枚举类型属性,它的值是ElementType.METHOD。这就表明MyAnnotation只能为方法注释。而不能为其它的任何语言元素进行注释。因此,MyAnnotation自然也不能为Class1进行注释了。
说到这,大家可能已经基本明白了。原来target所指的目标就是java的语言元素。如类、接口、方法等。当然,Target还可以对其它的语言元素进行限制,如构造函数、字段、参数等。如只允许对方法和构造函数进行注释可以写成:
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@interface MyAnnotation {}
Retention
既然可以自定义注释,当然也可以读取程序中的注释(如何读取注释将在下一节中讨论)。但是注释只有被保存在class文件中才可以被读出来。而Retention就是为设置注释是否保存在class文件中而存在的。下面的代码是Retention的详细用法。
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation1 { }
@Retention(RetentionPolicy.CLASS)
@interface MyAnnotation2 {}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3 {}
其中第一段代码的作用是不将注释保存在class文件中,也就是说象“//”一样在编译时被过滤掉了。第二段代码的作用是只将注释保存在class文件中,而使用反射读取注释时忽略这些注释。第三段代码的作用是即将注释保存在class文件中,也可以通过反射读取注释。
Documented
这个注释和它的名子一样和文档有关。在默认的情况下在使用javadoc自动生成文档时,注释将被忽略掉。如果想在文档中也包含注释,必须使用Documented为文档注释。
@interface MyAnnotation{ }
@MyAnnotation
class Class1
{
public void myMethod() { }
}
使用javadoc为这段代码生成文档时并不将@MyAnnotation包含进去。生成的文档对Class1的描述如下:
class Class1extends java.lang.Object
而如果这样定义MyAnnotation将会出现另一个结果。
@Documented
@interface MyAnnotation {}
生成的文档:
@MyAnnotation // 这行是在加上@Documented后被加上的
class Class1extends java.lang.Object
Inherited
继承是java主要的特性之一。在类中的protected和public成员都将会被子类继承,但是父类的注释会不会被子类继承呢?很遗憾的告诉大家,在默认的情况下,父类的注释并不会被子类继承。如果要继承,就必须加上Inherited注释。
@Inherited
@interface MyAnnotation { }
@MyAnnotation
public class ParentClass {}
public class ChildClass extends ParentClass { }
在以上代码中ChildClass和ParentClass一样都已被MyAnnotation注释了。

五、如何使用反射读取注释

前面讨论了如何自定义注释。但是自定义了注释又有什么用呢?这个问题才是J2SE5.0提供注释的关键。自定义注释当然是要用的。那么如何用呢?解决这个问题就需要使用java最令人兴奋的功能之一:反射(reflect)。
在以前的JDK版本中,我们可以使用反射得到类的方法、方法的参数以及其它的类成员等信息。那么在J2SE5.0中同样也可以象方法一样得到注释的各种信息。
在使用反射之前必须使用import java.lang.reflect.* 来导入和反射相关的类。
如果要得到某一个类或接口的注释信息,可以使用如下代码:
Annotation annotation = TestAnnotation.class.getAnnotation(MyAnnotation.class);
如果要得到全部的注释信息可使用如下语句:
Annotation[] annotations = TestAnnotation.class.getAnnotations();
Annotation[] annotations = TestAnnotation.class.getDeclaredAnnotations();
getDeclaredAnnotations与getAnnotations类似,但它们不同的是getDeclaredAnnotations得到的是当前成员所有的注释,不包括继承的。而getAnnotations得到的是包括继承的所有注释。
如果要得到其它成员的注释,可先得到这个成员,然后再得到相应的注释。如得到myMethod的注释。
Method method = TestAnnotation.class.getMethod("myMethod", null);
Annotation annotation = method.getAnnotation(MyAnnotation.class);
注:要想使用反射得到注释信息,这个注释必须使用
@Retention(RetentionPolicy.RUNTIME)进行注释。
总结
注释是J2SE5.0提供的一项非常有用的功能。EJB3规范就是借助于注释实现的。这样将使EJB3在实现起来更简单,更人性化。还有Hibernate3除了使用传统的方法生成hibernate映射外,也可以使用注释来生成hibernate映射。总之,如果能将注释灵活应用到程序中,将会使你的程序更加简洁和强大。

NDK编译可执行文件在Android L中运行显示error: only position independent executables (PIE) are supported.失败问题解决办法

Android L 即 Android 5.0,代号Lollipop
由于使用了NDK编译的可执行文件在应用中调用,在4.4及之前的版本上一直没出问题。最近由于要测试在Android L上的运行情况发现,当运行该可执行文件时,报如下错误:
error: only position independent executables (PIE) are supported.
PIE这个安全机制从4.1引入,但是Android L之前的系统版本并不会去检验可执行文件是否基于PIE编译出的。因此不会报错。
但是Android L已经开启验证,如果调用的可执行文件不是基于PIE方式编译的,则无法运行。
解决办法非常简单,需要加入相应编译选项,如在使用Android.mk则按照以下加入以下即可。
LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE

package R does not exist错误

原因有以下三种:

1. 资源未正确生成

解决:
查看资源是否有错误,修改相关错误重新编译生成资源即可

2. 文件头中引用的源包名与实际资源所在的包名不一致

特别是修改包名后容易出现此种情况
解决:
修改为一致。
如将文件头中包名为package com.example.test0;
而AndroidManifest.xml中的package="com.example.test1"
则将如将文件头中包名改为与AndroidManifest.xml中一致,即package com.example.test1;

3. 使用的是Android原生的资源

解决:
引用时需添加Android原生的包,如android.R.color.white
[java]
package com.linc.meta;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
private String Name = null;
private String Gender = null;
private int HP = 0;
private String Summary = null;
private String Skill = null;
private List<String> skillList = new ArrayList<String>();
public Person(String name,String gender,int HP,String summary,String skill)
{
this.Name = name;
this.Gender = gender;
this.Summary = summary;
this.HP = HP;
this.Skill = skill;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getGender() {
return Gender;
}
public void setGender(String gender) {
Gender = gender;
}
public int getHP() {
return HP;
}
public void setHP(int hP) {
HP = hP;
}
public String getSummary() {
return Summary;
}
public void setSummary(String summary) {
Summary = summary;
}
public String getSkill() {
return Skill;
}
public void setSkill(String skill) {
Skill = skill;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(Name);
dest.writeString(Gender);
dest.writeString(Summary);
dest.writeInt(HP);
dest.writeString(Skill);
}
public static final Parcelable.Creator<Person> CREATOR = new Creator<Person>()
{
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
private Person(Parcel dest)
{
this.Name = dest.readString();
this.Gender = dest.readString();
this.Summary = dest.readString();
this.HP = dest.readInt();
this.Skill = dest.readString();
this.skillList = dest.readArrayList(String.class.getClassLoader());//class loader 必须要指明
}
}

android.os.BadParcelableException: ClassNotFoundException when unmarshalling

package com.linc.meta;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
private String Name = null;
private String Gender = null;
private int HP = 0;
private String Summary = null;
private String Skill = null;
private List<String> skillList = new ArrayList<String>();
public Person(String name,String gender,int HP,String summary,String skill)
{
this.Name = name;
this.Gender = gender;
this.Summary = summary;
this.HP = HP;
this.Skill = skill;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getGender() {
return Gender;
}
public void setGender(String gender) {
Gender = gender;
}
public int getHP() {
return HP;
}
public void setHP(int hP) {
HP = hP;
}
public String getSummary() {
return Summary;
}
public void setSummary(String summary) {
Summary = summary;
}
public String getSkill() {
return Skill;
}
public void setSkill(String skill) {
Skill = skill;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(Name);
dest.writeString(Gender);
dest.writeString(Summary);
dest.writeInt(HP);
dest.writeString(Skill);
}
public static final Parcelable.Creator<Person> CREATOR = new Creator<Person>()
{
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
private Person(Parcel dest)
{
this.Name = dest.readString();
this.Gender = dest.readString();
this.Summary = dest.readString();
this.HP = dest.readInt();
this.Skill = dest.readString();
this.skillList = dest.readArrayList(String.class.getClassLoader());//class loader 必须要指明
}
}
List中是什么类型就要把类型的class loader引入,否则就会报上述错误。
http://www.happylivelife.com/r/?id=286
http://www.happylivelife.com/r/?id=138
http://img5.pcpop.com/ArticleImages/0x0/2/2084/002084611.jpg
* 声明:本文由其作者或媒体撰写,观点仅代表其本身,不代表本站立场。
编辑
HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com HappyLifeLife.com
 
<< < - > >>
[GIT][*] [HIS][*] [JS][*] [Android][*] [DB][*] [Web][*] [JAVA][*] [C][*] [0][*] [TL][*] [O][*] [3D][*] [PAS][*] [IOS][*] [算法][*] [地球][*] [学习方法][*] [探索][*] [宇宙][*] [Linux][*] [阅读秘诀][*] [考试技巧][*] [...]
天天快乐生活[HappyLifeLife.com]
欢迎来访 快乐空间 热点新闻 我的分享 读书频道 七彩生活 精彩世界 快乐搜索 
ICP备15040518 | ©1999-2018 HappyLiveLife.com 版权所有 | 服务 | 爱新闻 | 爱分享 | 在线搜索 | 招贤纳士
欢迎来访 快乐空间 热点新闻 我的分享 读书频道 七彩生活 精彩世界 快乐搜索 
ICP备15040518 | ©1999-2018 HappyLiveLife.com 版权所有 | 服务 | 爱新闻 | 爱分享 | 在线搜索 | 招贤纳士