- 浏览: 95049 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
-
George_ghc:
Android JNI简单实例(android 调用C/C++代码) -
hu_teye:
楼主,求全部代码。975524295@qq.com 楼主好人 ...
android手动拖动滚动条快速滑动 -
yzx503319102:
正好需要实现这个效果,包括拉动的时候ListView正好显示对 ...
android手动拖动滚动条快速滑动 -
ql15898115822:
作者缺了权限,不要忘了添加权限。<uses-permis ...
Android WakeLock
收藏列表
标题 | 标签 | 来源 | |
自定义ViewGroup实现拖动 跟快速滚动的效果 | |||
自定义的viewGroup 实现以下的效果: 1:跟着手指 移动(上下移动) 2:快速推一下 滑动过去(上下滑动) 我的 代码 两个效果分开来 没有问题 放一起就出错了。。。大侠帮忙看下 上代码: public class Workspace extends ViewGroup implements OnGestureListener { private GestureDetector detector; int move=0;//移动距离 int MAXMOVE=1030;//最大移动距离 private Context Mcontext; AllShelf mAllShelf; private Scroller mScroller; public Workspace(Context context) { super(context); Mcontext=context; //加入 孩子viewGroup mScroller = new Scroller(context); mAllShelf=new AllShelf(context) { @Override public void computeScroll(){ if (mScroller.computeScrollOffset()) { //返回当前滚动X方向的偏移 // Log.d("lay1_computeScroll","mScroller.getCurrX() "+mScroller.getCurrX()); scrollTo(0, mScroller.getCurrY()); postInvalidate(); } } }; this.addView(mAllShelf); detector = new GestureDetector(this); } @Override public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float y = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; } return this.detector.onTouchEvent(ev); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub int childTop=0; final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != View.GONE) { child.setVisibility(View.VISIBLE); child.measure(r - l, b - t); final int childHeight = child.getMeasuredHeight(); child.layout(0, childTop, 320,childTop+ 1460); childTop += 1460; } } } public boolean onDown(MotionEvent e) { // TODO Auto-generated method stub Log.d("onDown","onDown"); return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if(mScroller.isFinished()) { Log.d("onFling","onFling"); // TODO Auto-generated method stub //往上是负数 正数往下 Log.d("onFling","onFling"); int movethis=-(int)(velocityY*0.2); //int movethis; if(-velocityY>0) { movethis=Math.min(MAXMOVE-move, movethis); }else { movethis=Math.max(-move,movethis); } Log.d("movethis,,,,,velocityY,,,,,,"," "+movethis+" "+velocityY+" "+move); mScroller.startScroll(0, move, 0, movethis, 3000); //Log.d("movethis", ""+movethis); move=move+movethis; Log.d("move", ""+move); //mScroller.fling(0,mScroller.getFinalY (),0,-(int)velocityY,0,0,0,MAXMOVE); mAllShelf.computeScroll(); } return false; } boolean IsScrollBeforeFling=false; public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if(mScroller.isFinished()) { Log.d("onScroll", "onScroll"); final int deltaY = (int) (distanceY); // mLastMotionY=y; if (deltaY < 0) { //下移 //Log.d("down"," "+getScrollY()); if (move > 0) { int move_this=Math.max(-move, deltaY); move=move+move_this; Log.d("move down", ""+move); scrollTo(0, move); } } else if (deltaY > 0) { //上移 if (MAXMOVE-move > 0) { int move_this=Math.min(MAXMOVE-move, deltaY); move=move+move_this; Log.d("move up", ""+move); scrollTo(0, move); } } } return false; } public void onShowPress(MotionEvent e) { } public boolean onSingleTapUp(MotionEvent e) { return false; } public void onLongPress(MotionEvent e) { } } 快速滑动ListView有一个属性可以设置 android:fastScrollEnabled="true" 类似于系统联系人一样 问题解决了。。。。onScroll 里的 scrollTo 竟然没写明对象。。。 mAllShelf.scrollTo 就OK了 分给 唯一关注的大哥 |
|||
android 软件盘弹出时的界面控制 | |||
一、软键盘显示的原理 软件盘的本质是什么?软键盘其实是一个Dialog! InputMethodService为我们的输入法创建了一个Dialog,并且将该Dialog的Window的某些参数(如Gravity)进行了设置,使之能够在底部或者全屏显示。当我们点击输入框时,系统对活动主窗口进行调整,从而为输入法腾出相应的空间,然后将该Dialog显示在底部,或者全屏显示。 二、活动主窗口调整 android定义了一个属性,名字为windowSoftInputMode, 用它可以让程序可以控制活动主窗口调整的方式。我们可以在AndroidManifet.xml中对Activity进行设置。如:android:windowSoftInputMode="stateUnchanged|adjustPan" 模式一,压缩模式 windowSoftInputMode的值如果设置为adjustResize,那么该Activity主窗口总是被调整大小以便留出软键盘的空间。 我们通过一段代码来测试一下,当我们设置了该属性后,弹出输入法时,系统做了什么。 重写Layout布局: 1. public class ResizeLayout extends LinearLayout{ 2. private static int count = 0; 3. public ResizeLayout(Context context, AttributeSet attrs) { 4. super(context, attrs); 5. } 6. @Override 7. protected void onSizeChanged(int w, int h, int oldw, int oldh) { 8. super.onSizeChanged(w, h, oldw, oldh); 9. Log.e("onSizeChanged " + count++, "=>onResize called! w="+w + ",h="+h+",oldw="+oldw+",oldh="+oldh); 10. } 11. @Override 12. protected void onLayout(boolean changed, int l, int t, int r, int b) { 13. super.onLayout(changed, l, t, r, b); 14. Log.e("onLayout " + count++, "=>OnLayout called! l=" + l + ", t=" + t + ",r=" + r + ",b="+b); 15. } 16. @Override 17. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 18. super.onMeasure(widthMeasureSpec, heightMeasureSpec); 19. Log.e("onMeasure " + count++, "=>onMeasure called! widthMeasureSpec=" + widthMeasureSpec + ", heightMeasureSpec=" + heightMeasureSpec); 20. } 我们的布局设置为: 1. <com.winuxxan.inputMethodTest.ResizeLayout 2. xmlns:android="http://schemas.android.com/apk/res/android" 3. android:id="@+id/root_layout" 4. android:layout_width="fill_parent" 5. android:layout_height="fill_parent" 6. android:orientation="vertical" 7. > 8. <EditText 9. android:layout_width="fill_parent" 10. android:layout_height="wrap_content" 11. /> 12. <LinearLayout 13. android:id="@+id/bottom_layout" 14. android:layout_width="fill_parent" 15. android:layout_height="fill_parent" 16. android:orientation="vertical" 17. android:gravity="bottom">s 18. <TextView 19. android:layout_width="fill_parent" 20. android:layout_height="wrap_content" 21. android:text="@string/hello" 22. android:background="#77777777" 23. /> 24. </LinearLayout> 25. </com.winuxxan.inputMethodTest.ResizeLayout> AndroidManifest.xml的Activity设置属性:android:windowSoftInputMode = "adjustResize" 运行程序,点击文本框,查看调试信息: E/onMeasure 6(7960): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec = 1073742024 E/onMeasure 7(7960): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec = 1073742025 E/onSizeChanged 8(7960): =>onSizeChanged called! w=320,h=201,oldw=320,oldh=377 E/onLayout 9(7960): =>OnLayout called! l=0, t=0,r=320,b=201 从调试结果我们可以看出,当我们点击文本框后,根布局调用了onMeasure,onSizeChanged和onLayout。 实际上,当设置为adjustResize后,软键盘弹出时,要对主窗口布局重新进行measure和layout,而在layout时,发现窗口的大小发生的变化,因此调用了onSizeChanged。 从下图的运行结果我们也可以看出,原本在下方的TextView被顶到了输入法的上方 模式二,平移模式 windowSoftInputMode的值如果设置为adjustPan,那么该Activity主窗口并不调整屏幕的大小以便留出软键盘的空间。相反,当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分。这个通常是不期望比调整大小,因为用户可能关闭软键盘以便获得与被覆盖内容的交互操作。 上面的例子中,我们将AndroidManifest.xml的属性进行更改:android: windowSoftInputMode = "adjustPan" 重新运行,并点击文本框,查看调试信息: E/onMeasure 6(8378): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec=1073742200 E/onMeasure 7(8378): =>onMeasure called! widthMeasureSpec=1073742144, heightMeasureSpec=1073742201 E/onLayout 8(8378): =>OnLayout called! l=0, t=0,r=320,b=377 我们看到:系统也重新进行了measrue和layout,但是我们发现,layout过程中onSizeChanged并没有调用,这说明输入法弹出前后并没有改变原有布局的大小。 从下图的运行结果我们可以看到,下方的TextView并没有被顶到输入法上方。 事实上,当输入框不会被遮挡时,该模式没有对布局进行调整,然而当输入框将要被遮挡时,窗口就会进行平移。也就是说,该模式始终是保持输入框为可见。如下图,整个 |
|||
Android如何获得系统(system)权限 | |||
Android中如何修改系统时间(应用程序获得系统权限) 在 android 的API中有提供 SystemClock.setCurrentTimeMillis()函数来修改系统时间,可惜无论你怎么调用这个函数都是没用的,无论模拟器还是真机,在logcat中总会得到"Unable to open alarm driver: Permission denied ".这个函数需要root权限或者运行与系统进程中才可以用。 本来以为就没有办法在应用程序这一层改系统时间了,后来在网上搜了好久,知道这个目的还是可以达到的。 第一个方法简单点,不过需要在Android系统源码的环境下用make来编译: 1. 在应用程序的AndroidManifest.xml中的manifest节点中加入 android:sharedUserId="android.uid.system"这个属性。 2. 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行 3. 使用mm命令来编译,生成的apk就有修改系统时间的权限了。 第二个办法麻烦点,不过不用开虚拟机跑到源码环境下用make来编译: 1. 同上,加入android:sharedUserId="android.uid.system"这个属性。 2. 使用eclipse编译出apk文件,但是这个apk文件是不能用的。 3. 用压缩软件打开apk文件,删掉META-INF目录下的CERT.SF和CERT.RSA两个文件。 4. 使用目标系统的platform密钥来重新给apk文件签名。这步比较麻烦, 首先找到密钥文件,在我的Android源码目录中的位置 是"build argetproductsecurity",下面的platform.pk8和platform.x509.pem 两个文件。 然后用Android提供的Signapk工具来签名,signapk的源代码是 在"build oolssignapk"下, 用法为"signapk platform.x509.pem platform.pk8 input.apk output.apk", 文件名最好使用绝对路径防止找不到,也可以修改源代码直接使用。 这样最后得到的apk和第一个方法是一样的。 最后解释一下原理,首先加入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,不过放到G3上安装直接提示"Package ... has no signatures that match those in shared user android.uid.system",这样也是保护了系统的安全。 最最后还说下,这个android:sharedUserId属性不只可以把apk放到系统进程中,也可以配置多个APK运行在一个进程中,这样可以共享数据,应该会很有用的。 |
|||
Android 使用 ToneGenerator 编写按键发声功能 | |||
private ToneGenerator mToneGenerator; private Object mToneGeneratorLock = new Object();//监视器对象锁 private boolean mDTMFToneEnabled; //按键操作音 private static final int TONE_LENGTH_MS = 150;//延迟时间 void playTone(int tone) { // TODO 播放按键声音 if (!mDTMFToneEnabled) { return; } AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int ringerMode = audioManager.getRingerMode(); if ((ringerMode == AudioManager.RINGER_MODE_SILENT) || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {//静音或震动时不发出按键声音 return; } synchronized(mToneGeneratorLock) { if (mToneGenerator == null) { Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone); return; } mToneGenerator.startTone(tone, TONE_LENGTH_MS);//发声 } } protected void onResume(){ super.onResume(); mDTMFToneEnabled = Settings.System.getInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;//获取系统参数“按键操作音”是否开启 synchronized(mToneGeneratorLock) { if (mToneGenerator == null) { try { mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 80); setVolumeControlStream(AudioManager.STREAM_MUSIC); } catch (RuntimeException e) { Log.w(TAG, "Exception caught while creating local tone generator: " + e); mToneGenerator = null; } } } } |
|||
android中自定义标题栏 | |||
http://www.itivy.com/android/archive/2011/8/20/android-activity-title-bar-operation.html |
|||
Android 2.2中的APK安装参数installLocation | |||
在Android 2.2中新的特性可以支持类似APP2SD卡上,我们的APK文件可以安装在SD卡上供用户使用,Android123今天就说下目前项目的升级和一些配置。 1. 首先让你的程序支持SD卡上安装必须具备设置API Level至少为8,即androidmanifest.xml的中android:minSdkVersion至少为8这样你的APK最终运行时兼容的固件只有2.2了,同时在androidmanifest.xml文件的根节点中必须加入android:installLocation这个属性,类似代码如下: < manifest xmlns:andro android:installLocation="preferExternal" ... > 复制代码 2. android:installLocation的值主要有preferExternal、auto 和internalOnly这三个选项,通常我们设置为preferExternal可以优先推荐应用安装到SD卡上,当然最终用户可以选择为内部的 ROM存储上,如果外部存储已满,Android内部也会安装到内部存储上,auto将会根据存储空间自适应,当然还有一些应用可能会有特殊的目的,他们一般必须安装在内部存储才能可靠运行,设置为internalOnly比较合适,主要体现在: Services 服务 Your running Service will be kill ed and will not be restarted when external storage is remounted. You can, however, register for the ACTION_EXTERNAL_APPLICATIONS_AVAILABLE broadcast Intent, which will notify your application when applications installed on external storage have become available to the system again. At which time, you can restart your Service. Android123提示大家一般实时后台监控类的应用都应该装到内部存储,比较可靠。 Alarm Services 闹铃提醒服务 Your alarms registered with AlarmManager will be cancelled. You must manually re-register any alarms when external storage is remounted. Input Method Engines 输入法引擎 Your IME will be replaced by the default IME. When external storage is remounted, the user can open system settings to enable your IME again. Live Wallpapers 活动壁纸 Your running Live Wallpaper will be replaced by the default Live Wallpaper. When external storage is remounted, the user can select your Live Wallpaper 〖黑软手机 资讯频道〗 again. Live Folders 活动文件夹 Your Live Folder will be removed from the home screen. When external storage is remounted, the user can add your Live Folder to the home screen again. App Widgets Widget Your App Widget will be removed from the home screen. When external storage is remounted, your App Widget will not be available for the user to select until the system resets the home application (usually not until a system reboot). Account Managers 账户管理 Your accounts created with AccountManager will disappear until external storage is remounted. Sync Adapters 同步适配器 Your AbstractThreadedSyncAdapter and all it s sync functionality will not work until external storage is remounted. Device Administrators 设备管理器 Your DeviceAdminReceiver and all it s admin capabilities will be disabled, which can have unforeseeable consequences for the device functionality, which may persist after external storage is remounted. 那么哪些应用适合安装在SD卡中呢? Android开发网建议一些占用资源比较大的游戏,比如大于3MB的单个文件,不需要长期驻留内存的应用,不具备提醒和实时监控的应用一般放到SD卡上比较合适,不过目前想让你的应用装到SD卡上,必须设置API Level至少为8以上,同时显示注明android:installLocation。 |
|||
Android TextView设置字体风格 | 控件属性 | ||
在开发应用过程中经常会遇到显示一些不同的字体风格的信息犹如默认的LockScreen上面的时间和充电信息。对于类似的情况,可能第一反应就是用不同的多个TextView来实现,对于每个TextView设置不同的字体风格以满足需求。 这里推荐的做法是使用Android.text.*;和android.text.style.*;下面的组件来实现RichText:也即在同一个TextView中设置不同的字体风格。对于某些应用,比如文本编辑,记事本,彩信,短信等地方,还必须使用这些组件才能达到想到的显示效果。 主要的基本工具类有Android.text.Spanned; android.text.SpannableString; android.text.SpannableStringBuilder;使用这些类来代替常规String。SpannableString和SpannableStringBuilder可以用来设置不同的Span,这些Span便是用于实现Rich Text,比如粗体,斜体,前景色,背景色,字体大小,字体风格等等,android.text.style.*中定义了很多的Span类型可供使用。 这是相关的API的Class General Hierarchy: 因为Spannable等最终都实现了CharSequence接口,所以可以直接把SpannableString和SpannableStringBuilder通过TextView.setText()设置给TextView。 使用方法 当要显示Rich Text信息的时候,可以使用创建一个SpannableString或SpannableStringBuilder,它们的区别在于SpannableString像一个String一样,构造对象的时候传入一个String,之后再无法更改String的内容,也无法拼接多个SpannableString;而SpannableStringBuilder则更像是StringBuilder,它可以通过其append()方法来拼接多个String: SpannableString word = new SpannableString("The quick fox jumps over the lazy dog"); SpannableStringBuilder multiWord = new SpannableStringBuilder(); multiWord.append("The Quick Fox"); multiWord.append("jumps over"); multiWord.append("the lazy dog"); 创建完Spannable对象后,就可以为它们设置Span来实现想要的Rich Text了,常见的Span有: AbsoluteSizeSpan(int size) ---- 设置字体大小,参数是绝对数值,相当于Word中的字体大小 RelativeSizeSpan(float proportion) ---- 设置字体大小,参数是相对于默认字体大小的倍数,比如默认字体大小是x, 那么设置后的字体大小就是x*proportion,这个用起来比较灵活,proportion>1就是放大(zoom in), proportion<1就是缩小(zoom out) ScaleXSpan(float proportion) ---- 缩放字体,与上面的类似,默认为1,设置后就是原来的乘以proportion,大于1时放大(zoon in),小于时缩小(zoom out) BackgroundColorSpan(int color) ----背景着色,参数是颜色数值,可以直接使用Android.graphics.Typeface里面定义的常量,如Typeface.BOLD,Typeface.ITALIC等等。 StrikethroughSpan----如果设置了此风格,会有一条线从中间穿过所有的字,就像被划掉一样 对于这些Sytle span在使用的时候通常只传上面所说明的构造参数即可,不需要设置其他的属性,如果需要的话,也可以对它们设置其他的属性。 SpannableString和SpannableStringBuilder都有一个设置上述Span的方法: /** * Set the style span to Spannable, such as SpannableString or SpannableStringBuilder * @param what --- the style span, such as StyleSpan * @param start --- the starting index of characters to which the style span to apply * @param end --- the ending index of characters to which the style span to apply * @param flags --- the flag specified to control */ setSpan(Object what, int start, int end, int flags); 其中参数what是要设置的Style span,start和end则是标识String中Span的起始位置,而 flags是用于控制行为的,通常设置为0或Spanned中定义的常量,常用的有: Spanned.SPAN_EXCLUSIVE_EXCLUSIVE --- 不包含两端start和end所在的端点 Spanned.SPAN_EXCLUSIVE_INCLUSIVE --- 不包含端start,但包含end所在的端点 Spanned.SPAN_INCLUSIVE_EXCLUSIVE --- 包含两端start,但不包含end所在的端点 Spanned.SPAN_INCLUSIVE_INCLUSIVE--- 包含两端start和end所在的端点 这里理解起来就好像数学中定义区间,开区间还是闭区间一样的。这里要重点说明下关于参数0,有很多时候,如果设置了上述的参数,那么Span会从start应用到Text结尾,而不是在start和end二者之间,这个时候就需要使用Flag 0。 |
|||
Android 使用 ToneGenerator 编写按键发声功能 | |||
private ToneGenerator mToneGenerator; private Object mToneGeneratorLock = new Object();//监视器对象锁 private boolean mDTMFToneEnabled; //按键操作音 private static final int TONE_LENGTH_MS = 150;//延迟时间 void playTone(int tone) { // TODO 播放按键声音 if (!mDTMFToneEnabled) { return; } AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int ringerMode = audioManager.getRingerMode(); if ((ringerMode == AudioManager.RINGER_MODE_SILENT) || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {//静音或震动时不发出按键声音 return; } synchronized(mToneGeneratorLock) { if (mToneGenerator == null) { Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone); return; } mToneGenerator.startTone(tone, TONE_LENGTH_MS);//发声 } } protected void onResume(){ super.onResume(); mDTMFToneEnabled = Settings.System.getInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;//获取系统参数“按键操作音”是否开启 synchronized(mToneGeneratorLock) { if (mToneGenerator == null) { try { mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 80); setVolumeControlStream(AudioManager.STREAM_MUSIC); } catch (RuntimeException e) { Log.w(TAG, "Exception caught while creating local tone generator: " + e); mToneGenerator = null; } } } } |
|||
ubuntu/linux下查看端口使用情况 | |||
想查看TCP或者UDP端口使用情况,使用 netstat -anp 如果有些进程看不见,如只显示”-”,可以尝试 sudo netstat -anp 如果想看某个端口的信息,使用lsof命令,如: sudo lsof -i :631 -bash-3.00# netstat -tln netstat -tln 命令是用来查看linux的端口使用情况 /etc/init.d/vsftp start 是用来启动ftp端口~! 看文件/etc/services netstat 查看已经连接的服务端口(ESTABLISHED) netstat -a 查看所有的服务端口(LISTEN,ESTABLISHED) sudo netstat -ap 查看所有 的服务端口并显示对应的服务程序名 nmap <扫描类型><扫描参数> 例如: nmap localhost nmap -p 1024-65535 localhost nmap -PT 192.168.1.127-245 当我们使用 netstat -apn 查看网络连接的时候,linux会发现很多类似下面的内容: Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 52 218.104.81.152:7710 211.100.39.250:29488 ESTABLISHED 6111/1 显示这台服务器开放了7710端口,那么 这个端口属于哪个程序呢?我们可以使用 lsof -i :7710 命令来查询: COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sshd 1990 root 3u IPv4 4836 TCP *:7710 (LISTEN) 这样,我们就知道了7710端口是属于sshd程序的。 |
|||
解开“OEM”与“ODM”之谜 | http://tech.sina.com.cn/c/2001-09-26/6123.html | ||
OEM一词在电脑硬件产品中简直实在是太普遍了,像OEM光驱、OEM显示器、OEM鼠标什么的。有些人甚至以为它是一个有质量保证的品牌。那么,你对OEM又有多少认识呢?说一件产品是OEM产品是否就代表信得过呢? 其实,OEM与现代工业社会有着密切的关系。一些著名的品牌商品制造商,常常因为自己的厂房不能达到大批量生产的要求,又或者需要某些特定的零件,因此向其他厂商求助。 这些伸出援手的厂商就被称为OEM(Original Equipment Manufacturer,原始设备生产商)。如将之引申到IT领域的话,则表示那些进行代工的生产商。例如CPU风扇,Intel或AMD公司本身并不生产,它们通常会找像日本三洋公司这样的专业电机制造企业作风扇OEM生产。 ODM又是怎么一回事呢?原来,某制造商设计出一种产品后,在某些情况下可能会被另外一些品牌的制造商看中,要求配上后者的品牌名称来进行生产,又或者稍微修改一些设计(如按键位置)来生产。这样做的最大好处是其他厂商减少了自己研制的时间。有些人也习惯性称这些产品是OEM,实际上应该称之为ODM(Original Design Manufacturer,原始设计制造商)。例如一些日本品牌的笔记本电脑实际上就是由台湾厂商代工生产的。事后,台湾笔记本电脑制造商只要修改某些设计细节或配件便可以以自己的品牌名称进行批量生产。原因在于它们为这些日本品牌作的是ODM而非OEM。当然,我们可以说它们都是从同一条生产线生产出来。 OEM和ODM两者最大的区别不单单是名称而已。OEM产品是为品牌厂商度身订造的,生产后也只能使用该品牌名称,绝对不能冠上生产者自己的名称再进行生产。而ODM则要看品牌企业有没有买断该产品的版权。如果没有的话,制造商有权自己组织生产,只要没有企业公司的设计识别即可。 在工业社会中,OEM和ODM可谓司空见惯。因为出于制造成本、运输方便性、节省开发时间等方面的考虑,知名品牌企业一般都愿意找其他厂商OEM或ODM。在找别的企业进行OEM或ODM时,知名品牌企业也要承担不少责任。毕竟产品冠的是自己的牌子,如果产品质量不佳的话,少则有顾客找上门投诉,重则可能要上法庭。所以,品牌企业在委托加工期间肯定会进行严格的质量控制。但代工结束后,质量不敢保证。故此,当有的商家告诉你某件产品的生产商是某大品牌的OEM或ODM产品时,绝不要相信其质量就等同于该品牌。你唯一能够相信的,是这家制造商有一定的生产能力。 另外,不知大家是否遇到过这样的情形:到电脑城购买产品(如LCD显示器)时,商家向你介绍某个台湾品牌的LCD显示器的液晶面板是某某日本大厂OEM的。其实这种说法是一个颇为错误的概念。首先,为保障品牌的质量和信誉,ODM或OEM公司的名称是保密的。如果说穿了A牌子笔记本电脑是B牌子生产的话,那人家还会买价格更贵的A牌子产品吗?因此上述LCD显示器的例子,实际上只是该台湾品牌购买了日本品牌的液晶面板配件来进行生产。质量好或不好,并不能完全相信。那我们该如何识别是否代工厂商采用了知名厂商的配件呢?以LCD显示器为例,目前其核心技术仍然控制在日本和韩国几家大厂商手中。消费者可以从技术指标来进行鉴别。如对比度为400:1的液晶显示器只有日本的日立(Hitachi)和富士通(Fujitsu)等公司生产,而对比度为350:1的产品则主要由韩国三星公司和日本NEC公司生产。 其实对于消费者来说,最怕听到的就是一些似是而非的简称,什么OEM、ODM之类,有时真的听得人一头雾水。不过,只要我们多看多学一些新知识,遇到有销售商家说OEM或ODM的问题,不要怕烦,先回家查清楚,那无论是OEM或ODM,问题都不大了。 |
|||
Android HandlerThread类,Bundle的使用 | 控件属性 | ||
1、其实上面的方法,直接使用handler.post(updateThread),然后线程updateThread直接调用run()方法,并没有调用start()方法, 所以并没有产生新的线程,都是在主线程里面运行的 2、要产生新的线程,可以用如下方法: //生成一个HandlerThread对象,实现了使用Looper来处理消息队列的功能,这个类由Android应用程序框架提供 HandlerThread handlerThread = new HandlerThread("handler_thread"); //必须先调用该类的start(); handlerThread.start(); 这样就产生了新的线程,然后可以继承Handler类,生成Handler对象,来控制线程 3、Bundle是键值对,键为string类型,值可以为多种类型,用于Message的setData()中存储数据的,加入数据如下 Bundle b = new Bundle(); b.putInt("age", 20); b.putString("name", "Jhon"); package mars.handler; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; public class HandlerTest2 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); HandlerThread handlerThread = new HandlerThread("handler_thread"); handlerThread.start(); MyHandler myHandler = new MyHandler(handlerThread.getLooper()); Message msg = myHandler.obtainMessage(); Bundle b = new Bundle(); b.putInt("age", 20); b.putString("name", "Jhon"); msg.setData(b); msg.sendToTarget(); } //继承Handler类, class MyHandler extends Handler{ public MyHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { Bundle b = msg.getData(); int age = b.getInt("age"); String name = b.getString("name"); System.out.println("age is " + age + ", name is" + name); } } } 此代码在实际运行的过程虽然没有问题,但是如果是想在void handleMessage()方法里刷新 View和控件会报如下错误: ...:only the original thread that created a view hierarchy can touch its views. 这主要是Android的相关View和控件不是线程安全的,我们必须做独立的处理。这里Android给 我们提供了很多方法,今天android开发网说一种简单的方法除了异步任务AsyncTask外使 用Handler可以很好的处理,和Win32的消息很像。 首先我们需要明白,主线程或者这里说的原始线程original thread 一般情况下是UI线程,当然UI线程并不一定是主线程,我们不能长时间的阻塞该应用,在Android平台上可能会产生类似Force close或Wait这样的对话框这里我们成为ANR,这里除了使用ProgressDialog方式给用一个动态的进度代表当前处理并没有中断可能需要 一些时间,所以告诉大家相关的网络处理可以使用工作者线程,但是worker 线程不能处理显示元素即UI相关的View或 Widget包中的高层的控件,所以通过一个Handler对象可以很好的传递Runnable或Message ,下面我们用一个简单的例子来描述 final Handler handler = new Handler(); new Thread() { public void run() { // list = getData(); //处理得到结果了,这里一些内容保存在主类的成员变量中 handler.post(new Runnable() { public void run() { //这里就可以获得主类中的组件刷新 gallery = (Gallery) findViewById(R.id.galley); gallery.setAdapter(new ImageAdapter(MainActivity.this, listData)); getLoading().dismiss(); } }); // 高速UI线程可以更新结果了 } }.start(); |
|||
Android中有用的Item选中和按下去的样式 | 控件属性 | ||
Android比较好的一点就是可以方便的定义自己常用的样式,而且可以重复使用。 下面是hmg25朋友能的一个选中item和按下item时候的样式,个人感觉跟系统的ListView比较相似, 所以记一下备用。。。 下面先看运行结果: 下面是focus选中时候的样子 下面是点击按下去的样子: 把下面的内容保存为xml文件,并放入到drawable文件夹中: dockbar_selector.xml Xhtml代码 1. <?xml version="1.0" encoding="UTF-8"?> 2. <selector 3. xmlns:android="http://schemas.android.com/apk/res/android"> 4. <item android:state_pressed="true" android:drawable="@drawable/selector_pressed_shape" /> 5. <item android:state_focused="true" android:state_window_focused="true" android:drawable="@drawable/selector_focused_shape" /> 6. <item android:state_focused="true" android:state_window_focused="false" android:drawable="@android:color/transparent" /> 7. </selector> <?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/selector_pressed_shape" /> <item android:state_focused="true" android:state_window_focused="true" android:drawable="@drawable/selector_focused_shape" /> <item android:state_focused="true" android:state_window_focused="false" android:drawable="@android:color/transparent" /> </selector> selector_focused_shape.xml Xhtml代码 1. <?xml version="1.0" encoding="UTF-8"?> 2. <shape 3. xmlns:android="http://schemas.android.com/apk/res/android"> 4. <solid android:color="#ffff9000" /> 5. <stroke android:width="0.0dip" android:color="#00000000" /> 6. <padding android:left="0.0dip" android:top="0.0dip" android:right="0.0dip" android:bottom="0.0dip" /> 7. <corners android:topLeftRadius="6.0dip" android:topRightRadius="6.0dip" android:bottomLeftRadius="6.0dip" android:bottomRightRadius="6.0dip" /> 8. </shape> <?xml version="1.0" encoding="UTF-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#ffff9000" /> <stroke android:width="0.0dip" android:color="#00000000" /> <padding android:left="0.0dip" android:top="0.0dip" android:right="0.0dip" android:bottom="0.0dip" /> <corners android:topLeftRadius="6.0dip" android:topRightRadius="6.0dip" android:bottomLeftRadius="6.0dip" android:bottomRightRadius="6.0dip" /> </shape> selector_pressed_shape.xml Xhtml代码 1. <?xml version="1.0" encoding="UTF-8"?> 2. <shape 3. xmlns:android="http://schemas.android.com/apk/res/android"> 4. <solid android:color="#ffffba00" /> 5. <stroke android:width="0.0dip" android:color="#00000000" /> 6. <padding android:left="0.0dip" android:top="0.0dip" android:right="0.0dip" android:bottom="0.0dip" /> 7. <corners android:topLeftRadius="6.0dip" android:topRightRadius="6.0dip" android:bottomLeftRadius="6.0dip" android:bottomRightRadius="6.0dip" /> 8. </shape> <?xml version="1.0" encoding="UTF-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#ffffba00" /> <stroke android:width="0.0dip" android:color="#00000000" /> <padding android:left="0.0dip" android:top="0.0dip" android:right="0.0dip" android:bottom="0.0dip" /> <corners android:topLeftRadius="6.0dip" android:topRightRadius="6.0dip" android:bottomLeftRadius="6.0dip" android:bottomRightRadius="6.0dip" /> </shape> 然后调用的时候,只需在layout的xml文件里面加上背景属性即可: Xhtml代码 1. android:background="@drawable/dockbar_selector" |
|||
Android中MediaButtonReceiver广播监听器的机制分析 | 控件属性 | ||
在Android中并没有定义MediaButtonReceive这个广播类,MediaButtonReceive只是作为一种通俗的命名方式来响应 插入耳机后,点击耳机上的按钮(名称:MEDIA_BUTTON)接受该广播事件的类。所有该MEDIA_BUTTON的按下我们就简称 为MEDIA_BUTTON广播吧。 顾名思义:它显然是一个广播接收器类(BroadbcastReceiver),那么它就具备了BroadbcastReceiver类的使用方式, 但是,因为它需要通过AudioManager对象注册,所以它有着自己的独特之处(否则我也不会单独拿出来分析,- -),后面我们 会慢慢的讲解。 点击MEDIA_BUTTON发送的Intent Action 为: ACTION_MEDIA_BUTTON ="android.intent.action.MEDIA_BUTTON" Intent 附加值为(Extra)点击MEDIA_BUTTON的按键码 : //获得KeyEvent对象 KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); //获得Action String intentAction = intent.getAction() ; AudioManager对象注册MEDIA_BUTTON广播的方法原型为: public voidregisterMediaButtonEventReceiver(ComponentNameeventReceiver) Register a component to be the sole receiverof MEDIA_BUTTON intents Parameters: eventReceiver : identifier of a BroadcastReceiver that will receive the media button intent. This broadcast receiver must be declared in the application manifest. 从注释可知以下两点: 1、 在AudioManager对象注册一个MediaoButtonRecevie,使它成为MEDIA_BUTTON的唯一接收器(这很重要, 我们会放在后面讲解) 也就是说只有我能收到,其他的都收不到这个广播了,否则的话大家都收到会照成一定的混乱; 2、 该广播必须在AndroidManifest.xml文件中进行声明,否则就监听不到该MEDIA_BUTTON广播了。 下面我们就简单的写一个MediaButtonReceiver类,并且在AndroidManifest.xml定义 1、 自定义的MediaButtonReceiver 广播类 view plainprint? 1. package com.qin.mediabutton; 2. 3. import android.content.BroadcastReceiver; 4. import android.content.Context; 5. import android.content.Intent; 6. import android.util.Log; 7. import android.view.KeyEvent; 8. 9. public class MediaButtonReceiver extends BroadcastReceiver { 10. private static String TAG = "MediaButtonReceiver"; 11. @Override 12. public void onReceive(Context context, Intent intent) { 13. // 获得Action 14. String intentAction = intent.getAction(); 15. // 获得KeyEvent对象 16. KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 17. 18. Log.i(TAG, "Action ---->" + intentAction + " KeyEvent----->"+ keyEvent.toString()); 19. 20. if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) { 21. // 获得按键字节码 22. int keyCode = keyEvent.getKeyCode(); 23. // 按下 / 松开 按钮 24. int keyAction = keyEvent.getAction(); 25. // 获得事件的时间 26. long downtime = keyEvent.getEventTime(); 27. 28. // 获取按键码 keyCode 29. StringBuilder sb = new StringBuilder(); 30. // 这些都是可能的按键码 , 打印出来用户按下的键 31. if (KeyEvent.KEYCODE_MEDIA_NEXT == keyCode) { 32. sb.append("KEYCODE_MEDIA_NEXT"); 33. } 34. // 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 35. // KEYCODE_MEDIA_PLAY_PAUSE 36. if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == keyCode) { 37. sb.append("KEYCODE_MEDIA_PLAY_PAUSE"); 38. } 39. if (KeyEvent.KEYCODE_HEADSETHOOK == keyCode) { 40. sb.append("KEYCODE_HEADSETHOOK"); 41. } 42. if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == keyCode) { 43. sb.append("KEYCODE_MEDIA_PREVIOUS"); 44. } 45. if (KeyEvent.KEYCODE_MEDIA_STOP == keyCode) { 46. sb.append("KEYCODE_MEDIA_STOP"); 47. } 48. // 输出点击的按键码 49. Log.i(TAG, sb.toString()); 50. } 51. } 52. } package com.qin.mediabutton; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; import android.view.KeyEvent; public class MediaButtonReceiver extends BroadcastReceiver { private static String TAG = "MediaButtonReceiver"; @Override public void onReceive(Context context, Intent intent) { // 获得Action String intentAction = intent.getAction(); // 获得KeyEvent对象 KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); Log.i(TAG, "Action ---->" + intentAction + " KeyEvent----->"+ keyEvent.toString()); if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) { // 获得按键字节码 int keyCode = keyEvent.getKeyCode(); // 按下 / 松开 按钮 int keyAction = keyEvent.getAction(); // 获得事件的时间 long downtime = keyEvent.getEventTime(); // 获取按键码 keyCode StringBuilder sb = new StringBuilder(); // 这些都是可能的按键码 , 打印出来用户按下的键 if (KeyEvent.KEYCODE_MEDIA_NEXT == keyCode) { sb.append("KEYCODE_MEDIA_NEXT"); } // 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 // KEYCODE_MEDIA_PLAY_PAUSE if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == keyCode) { sb.append("KEYCODE_MEDIA_PLAY_PAUSE"); } if (KeyEvent.KEYCODE_HEADSETHOOK == keyCode) { sb.append("KEYCODE_HEADSETHOOK"); } if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == keyCode) { sb.append("KEYCODE_MEDIA_PREVIOUS"); } if (KeyEvent.KEYCODE_MEDIA_STOP == keyCode) { sb.append("KEYCODE_MEDIA_STOP"); } // 输出点击的按键码 Log.i(TAG, sb.toString()); } } } 2、 在AndroidManifest.xml声明我们定义的广播类。 view plainprint? 1. <receiver android:name="MediaButtonReceiver"> 2. <intent-filter > 3. <action android:name="android.intent.action.MEDIA_BUTTON"></action> 4. </intent-filter> 5. </receiver> <receiver android:name="MediaButtonReceiver"> <intent-filter > <action android:name="android.intent.action.MEDIA_BUTTON"></action> </intent-filter> </receiver> 在模拟器上,我们可以手动构造MEDA_BUTTON的广播,并且将它发送出去(后面会介绍)。 如果有真机测试的话,按下MEDIA_BUTTON是可以接受到MEDIA_BUTTON广播的,如果没有接受到,请关闭所有应用 程序,在观察效果。 继续我们的下一步分析: 前面我们说明通过registerMediaButtonEventReceiver(eventReceiver)方法注册时,使它成为MEDIA_BUTTON的 唯一 接收器。这个唯一是怎么实现的呢? 我们在源码中,一步步追本溯源,相信一定可以找到答案,知道这“唯一“是 怎么来的。 第一步、 为AudioManager注册一个MediaButtonReceiver() ; view plainprint? 1. //获得AudioManager对象 2. AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE); 3. //构造一个ComponentName,指向MediaoButtonReceiver类 4. //下面为了叙述方便,我直接使用ComponentName类来替代MediaoButtonReceiver类 5. ComponentName mbCN = new ComponentName(getPackageName(),MediaButtonReceiver.class.getName()); 6. //注册一个MedioButtonReceiver广播监听 7. mAudioManager.registerMediaButtonEventReceiver(mbCN); 8. //取消注册的方法 9. mAudioManager.unregisterMediaButtonEventReceiver(mbCN); //获得AudioManager对象 AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE); //构造一个ComponentName,指向MediaoButtonReceiver类 //下面为了叙述方便,我直接使用ComponentName类来替代MediaoButtonReceiver类 ComponentName mbCN = new ComponentName(getPackageName(),MediaButtonReceiver.class.getName()); //注册一个MedioButtonReceiver广播监听 mAudioManager.registerMediaButtonEventReceiver(mbCN); //取消注册的方法 mAudioManager.unregisterMediaButtonEventReceiver(mbCN); MediaButtonReceiver就是我们用来接收MEDIA_BUTTON的广播类,下面为了叙述方便和直观上得体验,我直接使用 ComponentName类来替代真正的MediaoButtonReceiver广播类。 说明 接下来分析的文件路径全部在 frameworks/base/media/java/android/media/ 下 第二步、 进入AudioManager.java进行查看 ,发现如下方法: view plainprint? 1. //注册的方法为: 2. public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { 3. //TODO enforce the rule about the receiver being declared in the manifest 4. //我们继续查看getService()方法,看看IAudioService类到底是什么? 5. IAudioService service = getService(); 6. try { 7. //只是简单的调用了service的方法来完成注册,继续跟踪 8. service.registerMediaButtonEventReceiver(eventReceiver); 9. 10. } catch (RemoteException e) { 11. Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e); 12. } 13. } 14. //取消注册的方法为 15. public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { 16. IAudioService service = getService(); 17. try { 18. //只是简单的调用了service的方法来取消注册,,继续跟踪 19. service.unregisterMediaButtonEventReceiver(eventReceiver); 20. } catch (RemoteException e) { 21. Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e); 22. } 23. } //注册的方法为: public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { //TODO enforce the rule about the receiver being declared in the manifest //我们继续查看getService()方法,看看IAudioService类到底是什么? IAudioService service = getService(); try { //只是简单的调用了service的方法来完成注册,继续跟踪 service.registerMediaButtonEventReceiver(eventReceiver); } catch (RemoteException e) { Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e); } } //取消注册的方法为 public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { IAudioService service = getService(); try { //只是简单的调用了service的方法来取消注册,,继续跟踪 service.unregisterMediaButtonEventReceiver(eventReceiver); } catch (RemoteException e) { Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e); } } 找到getService()方法,其实现为: view plainprint? 1. //看看它到底是什么 2. private static IAudioService getService() 3. { 4. // 单例模式,大家懂得 5. if (sService != null) { 6. return sService; 7. } 8. //了解Binder机制 以及AIDL文件的使用,就明白了这不过是通过AIDL文件定义的Java层Binder机制 9. //b为IBinder基类接口 10. IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 11. //强制转换后,sService不过是一个客户端对象,IAudioService就是aidl文件定义的接口了 12. sService = IAudioService.Stub.asInterface(b); 13. return sService; 14. } 15. //sService对象的声明 16. private static IAudioService sService; //单例模式,不足为奇了 //看看它到底是什么 private static IAudioService getService() { // 单例模式,大家懂得 if (sService != null) { return sService; } //了解Binder机制 以及AIDL文件的使用,就明白了这不过是通过AIDL文件定义的Java层Binder机制 //b为IBinder基类接口 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); //强制转换后,sService不过是一个客户端对象,IAudioService就是aidl文件定义的接口了 sService = IAudioService.Stub.asInterface(b); return sService; } //sService对象的声明 private static IAudioService sService; //单例模式,不足为奇了 我们知道了AudiaoManager只不过是一个傀儡,所有的方法都是由IAudioService 对象去实现的,通过它的构造方式, 可以知道它应该是有AIDL文件形成的Binder机制, sService只是客户端对象,那么它的服务端对象在什么地方呢? 也就是继承了IAudioService.Stub桩的类。 第三步、接下来我们需要找到该IAudioService.aidl文件和真正的服务端对象 IAudioService.aidl定义如下: view plainprint? 1. package android.media; 2. 3. import android.content.ComponentName; 4. import android.media.IAudioFocusDispatcher; 5. /** 6. * {@hide} 7. */ 8. interface IAudioService { 9. 10. void adjustVolume(int direction, int flags); 11. void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); 12. void adjustStreamVolume(int streamType, int direction, int flags); 13. void setStreamVolume(int streamType, int index, int flags); 14. void setStreamSolo(int streamType, boolean state, IBinder cb); 15. void setStreamMute(int streamType, boolean state, IBinder cb); 16. int getStreamVolume(int streamType); 17. int getStreamMaxVolume(int streamType); 18. void setRingerMode(int ringerMode); 19. int getRingerMode(); 20. void setVibrateSetting(int vibrateType, int vibrateSetting); 21. int getVibrateSetting(int vibrateType); 22. boolean shouldVibrate(int vibrateType); 23. void setMode(int mode, IBinder cb); 24. int getMode(); 25. oneway void playSoundEffect(int effectType); 26. oneway void playSoundEffectVolume(int effectType, float volume); 27. boolean loadSoundEffects(); 28. oneway void unloadSoundEffects(); 29. oneway void reloadAudioSettings(); 30. void setSpeakerphoneOn(boolean on); 31. boolean isSpeakerphoneOn(); 32. void setBluetoothScoOn(boolean on); 33. boolean isBluetoothScoOn(); 34. int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId); 35. int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); 36. void unregisterAudioFocusClient(String clientId); 37. void registerMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法是我们需要弄懂的 38. void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法也是是我们需要弄懂的 39. void startBluetoothSco(IBinder cb); 40. void stopBluetoothSco(IBinder cb); 41. } package android.media; import android.content.ComponentName; import android.media.IAudioFocusDispatcher; /** * {@hide} */ interface IAudioService { void adjustVolume(int direction, int flags); void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); void adjustStreamVolume(int streamType, int direction, int flags); void setStreamVolume(int streamType, int index, int flags); void setStreamSolo(int streamType, boolean state, IBinder cb); void setStreamMute(int streamType, boolean state, IBinder cb); int getStreamVolume(int streamType); int getStreamMaxVolume(int streamType); void setRingerMode(int ringerMode); int getRingerMode(); void setVibrateSetting(int vibrateType, int vibrateSetting); int getVibrateSetting(int vibrateType); boolean shouldVibrate(int vibrateType); void setMode(int mode, IBinder cb); int getMode(); oneway void playSoundEffect(int effectType); oneway void playSoundEffectVolume(int effectType, float volume); boolean loadSoundEffects(); oneway void unloadSoundEffects(); oneway void reloadAudioSettings(); void setSpeakerphoneOn(boolean on); boolean isSpeakerphoneOn(); void setBluetoothScoOn(boolean on); boolean isBluetoothScoOn(); int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId); int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); void unregisterAudioFocusClient(String clientId); void registerMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法是我们需要弄懂的 void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法也是是我们需要弄懂的 void startBluetoothSco(IBinder cb); void stopBluetoothSco(IBinder cb); } 真正的服务端对象就是继承了 IAudioService.Stub 桩的类,AudioService就是该服务端对象,其实AudioManager的 所有操作都是由AudioService来实现的,它才是真正的老大。 第五步、 AudioService.java view plainprint? 1. //AudioService类 2. public class AudioService extends IAudioService.Stub { 3. //..... 4. //仅仅列出我们需要的方法 5. //这儿才是真正的注册MediaButtonReceiver的方法 6. public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { 7. Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver); 8. 9. synchronized(mRCStack) { 10. //调用它去实现注册ComponentName 11. pushMediaButtonReceiver(eventReceiver); 12. } 13. } 14. 15. //在查看pushMediaButtonReceiver()方法 先理解一下两个知识点,很重要的。 16. //RemoteControlStackEntry内部类不过是对ComponentName类的进一步封装(感觉没必要在加一层进行封装了) 17. private static class RemoteControlStackEntry { 18. public ComponentName mReceiverComponent;// 属性 19. //TODO implement registration expiration? 20. //public int mRegistrationTime; 21. 22. public RemoteControlStackEntry() { 23. } 24. 25. public RemoteControlStackEntry(ComponentName r) { 26. mReceiverComponent = r;// 构造函数赋值给mReceiverComponent对象 27. } 28. } 29. 30. //采用了栈存储结构(先进后出)来保存所有RemoteControlStackEntry对象,也就是保存了ComponentName对象 31. private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); 32. 33. //回到pushMediaButtonReceiver()查看,这下该拨开云雾了吧,继续学习 34. private void pushMediaButtonReceiver(ComponentName newReceiver) { 35. // already at top of stack? 36. //采用了一个栈(前面我们介绍的知识点)来保存所有注册的ComponentName对象 37. //如果当前栈不为空并且栈顶的对象与新注册的ComponentName对象一样,不做任何事,直接返回 38. if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) { 39. return; 40. } 41. //获得mRCStack栈的迭代器 42. Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); 43. //循环 44. while(stackIterator.hasNext()) { 45. RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); 46. //如果当前栈内保存该新注册的ComponentName对象,将它移除,跳出循环 47. if(rcse.mReceiverComponent.equals(newReceiver)) { 48. mRCStack.remove(rcse); 49. break; 50. } 51. } 52. //将新注册的ComponentName对象放入栈顶 53. mRCStack.push(new RemoteControlStackEntry(newReceiver)); 54. } 55. } //AudioService类 public class AudioService extends IAudioService.Stub { //..... //仅仅列出我们需要的方法 //这儿才是真正的注册MediaButtonReceiver的方法 public void registerMediaButtonEventReceiver(ComponentName eventReceiver) { Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver); synchronized(mRCStack) { //调用它去实现注册ComponentName pushMediaButtonReceiver(eventReceiver); } } //在查看pushMediaButtonReceiver()方法 先理解一下两个知识点,很重要的。 //RemoteControlStackEntry内部类不过是对ComponentName类的进一步封装(感觉没必要在加一层进行封装了) private static class RemoteControlStackEntry { public ComponentName mReceiverComponent;// 属性 //TODO implement registration expiration? //public int mRegistrationTime; public RemoteControlStackEntry() { } public RemoteControlStackEntry(ComponentName r) { mReceiverComponent = r;// 构造函数赋值给mReceiverComponent对象 } } //采用了栈存储结构(先进后出)来保存所有RemoteControlStackEntry对象,也就是保存了ComponentName对象 private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); //回到pushMediaButtonReceiver()查看,这下该拨开云雾了吧,继续学习 private void pushMediaButtonReceiver(ComponentName newReceiver) { // already at top of stack? //采用了一个栈(前面我们介绍的知识点)来保存所有注册的ComponentName对象 //如果当前栈不为空并且栈顶的对象与新注册的ComponentName对象一样,不做任何事,直接返回 if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) { return; } //获得mRCStack栈的迭代器 Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); //循环 while(stackIterator.hasNext()) { RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); //如果当前栈内保存该新注册的ComponentName对象,将它移除,跳出循环 if(rcse.mReceiverComponent.equals(newReceiver)) { mRCStack.remove(rcse); break; } } //将新注册的ComponentName对象放入栈顶 mRCStack.push(new RemoteControlStackEntry(newReceiver)); } } 小结一下: 栈(mRCStack)维护了所有CompoentName对象,对每个CompoentName对象,保证它有且仅有一个, 新注册的CompoentName对象永远处于栈顶 我们看下取消注册的方法: view plainprint? 1. //我们看下取消注册的方法 2. /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */ 3. public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { 4. Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver); 5. 6. synchronized(mRCStack) { 7. //调用removeMediaButtonReceiver方法去实现 8. removeMediaButtonReceiver(eventReceiver); 9. } 10. } 11. 12. private void removeMediaButtonReceiver(ComponentName newReceiver) { 13. Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); 14. while(stackIterator.hasNext()) { 15. //获得mRCStack栈的迭代器 16. RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); 17. //如果存在该对象,则移除,跳出循环 18. if(rcse.mReceiverComponent.equals(newReceiver)) { 19. mRCStack.remove(rcse); 20. break; 21. } 22. } 23. } //我们看下取消注册的方法 /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */ public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) { Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver); synchronized(mRCStack) { //调用removeMediaButtonReceiver方法去实现 removeMediaButtonReceiver(eventReceiver); } } private void removeMediaButtonReceiver(ComponentName newReceiver) { Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); while(stackIterator.hasNext()) { //获得mRCStack栈的迭代器 RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); //如果存在该对象,则移除,跳出循环 if(rcse.mReceiverComponent.equals(newReceiver)) { mRCStack.remove(rcse); break; } } } 通过对前面的学习,我们知道了AudioManager内部利用一个栈来管理包括加入和移除ComponentName对象, 新的疑问来了?这个MEDIA_BUTTON广播是如何分发的呢 ? 其实,AudioService.java文件中也存在这么一个MediaoButtonReceiver的广播类,它为系统广播接收器,即用来接收 系统的MEDIA_BUTTON广播,当它接收到了这个MEDIA_BUTTON广播 ,它会对这个广播进行进一步处理,这个处理过程 就是我们需要的弄清楚。 MediaButtonBroadcastReceiver 内部类如下: view plainprint? 1. private class MediaButtonBroadcastReceiver extends BroadcastReceiver { 2. @Override 3. public void onReceive(Context context, Intent intent) { 4. //获得action ,系统MEDIA_BUTTON广播来了 5. String action = intent.getAction(); 6. //action不正确 直接返回 7. if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) { 8. return; 9. } 10. //获得KeyEvent对象 11. KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 12. if (event != null) { 13. // if in a call or ringing, do not break the current phone app behavior 14. // TODO modify this to let the phone app specifically get the RC focus 15. // add modify the phone app to take advantage of the new API 16. //来电或通话中,不做处理直接返回 17. if ((getMode() == AudioSystem.MODE_IN_CALL) ||(getMode() == AudioSystem.MODE_RINGTONE)) { 18. return; 19. } 20. synchronized(mRCStack) { 21. //栈不为空 22. if (!mRCStack.empty()) { 23. // create a new intent specifically aimed at the current registered listener 24. //构造一个Intent对象 ,并且赋予Action和KeyEvent 25. Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 26. targetedIntent.putExtras(intent.getExtras()); 27. //指定该处理Intent的对象为栈顶ComponentName对象的广播类 28. targetedIntent.setComponent(mRCStack.peek().mReceiverComponent); 29. // trap the current broadcast 30. // 终止系统广播 31. abortBroadcast(); 32. //Log.v(TAG, " Sending intent" + targetedIntent); 33. //手动发送该广播至目标对象去处理,该广播不再是系统发送的了 34. context.sendBroadcast(targetedIntent, null); 35. } 36. //假设栈为空,那么所有定义在AndroidManifest.xml的监听MEDIA_BUTTON的广播都会处理, 37. //在此过程中如果有任何应用程注册了registerMediaButton 该广播也会立即终止 38. } 39. } 40. } 41. } private class MediaButtonBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //获得action ,系统MEDIA_BUTTON广播来了 String action = intent.getAction(); //action不正确 直接返回 if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) { return; } //获得KeyEvent对象 KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (event != null) { // if in a call or ringing, do not break the current phone app behavior // TODO modify this to let the phone app specifically get the RC focus // add modify the phone app to take advantage of the new API //来电或通话中,不做处理直接返回 if ((getMode() == AudioSystem.MODE_IN_CALL) ||(getMode() == AudioSystem.MODE_RINGTONE)) { return; } synchronized(mRCStack) { //栈不为空 if (!mRCStack.empty()) { // create a new intent specifically aimed at the current registered listener //构造一个Intent对象 ,并且赋予Action和KeyEvent Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); targetedIntent.putExtras(intent.getExtras()); //指定该处理Intent的对象为栈顶ComponentName对象的广播类 targetedIntent.setComponent(mRCStack.peek().mReceiverComponent); // trap the current broadcast // 终止系统广播 abortBroadcast(); //Log.v(TAG, " Sending intent" + targetedIntent); //手动发送该广播至目标对象去处理,该广播不再是系统发送的了 context.sendBroadcast(targetedIntent, null); } //假设栈为空,那么所有定义在AndroidManifest.xml的监听MEDIA_BUTTON的广播都会处理, //在此过程中如果有任何应用程注册了registerMediaButton 该广播也会立即终止 } } } } 总结一下MEDIA_BUTTON广播: AudioManager也就是AudioService服务端对象内部会利用一个栈来管理所有ComponentName对象,所有对象有且仅有一个, 新注册的ComponentName总是会位于栈顶。 当系统发送MEDIA_BUTTON,系统MediaButtonBroadcastReceiver 监听到系统广播,它会做如下处理: 1、 如果栈为空,则所有注册了该Action的广播都会接受到,因为它是由系统发送的。 2、 如果栈不为空,那么只有栈顶的那个广播能接受到MEDIA_BUTTON的广播,手动发送了MEDIA_BUTTON 广播,并且指定了目标对象(栈顶对象)去处理该MEDIA_BUTTON 。 下面分析一下KeyEvent对象里的KeyCode按键,可能的按键码有: 1、KeyEvent.KEYCODE_MEDIA_NEXT 2、KeyEvent.KEYCODE_HEADSETHOOK 3、KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE(已废除,等同于KEYCODE_HEADSETHOOK) 4、KeyEvent.KEYCODE_MEDIA_PREVIOUS 5、KeyEvent.KEYCODE_MEDIA_STOP PS : 在我的真机测试中,按下MEDIA_BUTTON只有KEYCODE_HEADSETHOOK可以打印出来了。 下面给出一个小DEMO检验一下我们之前所做的一切,看看MEDIA_BUTTON是如何处理分发广播的。 编写两个MediaButtonReceiver类用来监听MEDIA_BUTTON广播: 1 、China_MBReceiver.java view plainprint? 1. package com.qin.mediabutton; 2. 3. import android.content.BroadcastReceiver; 4. import android.content.Context; 5. import android.content.Intent; 6. import android.util.Log; 7. import android.view.KeyEvent; 8. 9. public class China_MBReceiver extends BroadcastReceiver { 10. 11. private static String TAG = "China_MBReceiver" ; 12. @Override 13. public void onReceive(Context context, Intent intent) { 14. //获得Action 15. String intentAction = intent.getAction() ; 16. //获得KeyEvent对象 17. KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 18. 19. Log.i(TAG, "Action ---->"+intentAction + " KeyEvent----->"+keyEvent.toString()); 20. 21. if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){ 22. //获得按键字节码 23. int keyCode = keyEvent.getKeyCode() ; 24. //按下 / 松开 按钮 25. int keyAction = keyEvent.getAction() ; 26. //获得事件的时间 27. long downtime = keyEvent.getEventTime(); 28. 29. //获取按键码 keyCode 30. StringBuilder sb = new StringBuilder(); 31. //这些都是可能的按键码 , 打印出来用户按下的键 32. if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){ 33. sb.append("KEYCODE_MEDIA_NEXT"); 34. } 35. //说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 KEYCODE_MEDIA_PLAY_PAUSE 36. if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ==keyCode){ 37. sb.append("KEYCODE_MEDIA_PLAY_PAUSE"); 38. } 39. if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){ 40. sb.append("KEYCODE_HEADSETHOOK"); 41. } 42. if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){ 43. sb.append("KEYCODE_MEDIA_PREVIOUS"); 44. } 45. if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){ 46. sb.append("KEYCODE_MEDIA_STOP"); 47. } 48. //输出点击的按键码 49. Log.i(TAG, sb.toString()); 50. 51. } 52. 53. } 54. 55. } package com.qin.mediabutton; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; import android.view.KeyEvent; public class China_MBReceiver extends BroadcastReceiver { private static String TAG = "China_MBReceiver" ; @Override public void onReceive(Context context, Intent intent) { //获得Action String intentAction = intent.getAction() ; //获得KeyEvent对象 KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); Log.i(TAG, "Action ---->"+intentAction + " KeyEvent----->"+keyEvent.toString()); if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){ //获得按键字节码 int keyCode = keyEvent.getKeyCode() ; //按下 / 松开 按钮 int keyAction = keyEvent.getAction() ; //获得事件的时间 long downtime = keyEvent.getEventTime(); //获取按键码 keyCode StringBuilder sb = new StringBuilder(); //这些都是可能的按键码 , 打印出来用户按下的键 if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){ sb.append("KEYCODE_MEDIA_NEXT"); } //说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 KEYCODE_MEDIA_PLAY_PAUSE if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ==keyCode){ sb.append("KEYCODE_MEDIA_PLAY_PAUSE"); } if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){ sb.append("KEYCODE_HEADSETHOOK"); } if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){ sb.append("KEYCODE_MEDIA_PREVIOUS"); } if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){ sb.append("KEYCODE_MEDIA_STOP"); } //输出点击的按键码 Log.i(TAG, sb.toString()); } } } 2 、England_MBReceiver.java同于China_MBRreceiver ,打印Log TAG= "England_MBReceiver" 3、在AndroidManifest.xml文件定义: view plainprint? 1. <strong> <receiver android:name=".China_MBReceiver"> 2. <intent-filter > 3. <action android:name="android.intent.action.MEDIA_BUTTON"></action> 4. </intent-filter> 5. </receiver> 6. 7. <receiver android:name=".Enaland_MBReceiver"> 8. <intent-filter > 9. <action android:name="android.intent.action.MEDIA_BUTTON"></action> 10. </intent-filter> 11. </receiver></strong> <strong> <receiver android:name=".China_MBReceiver"> <intent-filter > <action android:name="android.intent.action.MEDIA_BUTTON"></action> </intent-filter> </receiver> <receiver android:name=".Enaland_MBReceiver"> <intent-filter > <action android:name="android.intent.action.MEDIA_BUTTON"></action> </intent-filter> </receiver></strong> 4、MainActivity .java 我们通过手动构造一个MEDIA_BUTTON广播去查看我们的MediaButtonReceiver类的打印信息。 view plainprint? 1. package com.qin.mediabutton; 2. 3. import android.app.Activity; 4. import android.content.ComponentName; 5. import android.content.Context; 6. import android.content.Intent; 7. import android.media.AudioManager; 8. import android.os.Bundle; 9. import android.view.KeyEvent; 10. 11. public class MainActivity extends Activity { 12. /** Called when the activity is first created. */ 13. @Override 14. public void onCreate(Bundle savedInstanceState) { 15. super.onCreate(savedInstanceState); 16. setContentView(R.layout.main); 17. 18. //由于在模拟器上测试,我们手动发送一个MEDIA_BUTTON的广播,有真机更好处理了 19. Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 20. //构造一个KeyEvent对象 21. KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ; 22. //作为附加值添加至mbIntent对象中 23. mbIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 24. 25. //此时China_MBReceiver和England_MBReceiver都会接收到该广播 26. sendBroadcast(mbIntent); 27. 28. 29. AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); 30. //AudioManager注册一个MediaButton对象 31. ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName()); 32. //只有China_MBReceiver能够接收到了,它是出于栈顶的。 33. //不过,在模拟上检测不到这个效果,因为这个广播是我们发送的,流程不是我们在上面介绍的。 34. mAudioManager.registerMediaButtonEventReceiver(chinaCN); 35. //sendBroadcast(mbIntent,null); 36. } 37. //当一个Activity/Service死去时,我们需要取消这个MediaoButtonReceiver的注册,如下 38. protected void onDestroy(){ 39. super.onDestroy() ; 40. AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); 41. ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName()); 42. //取消注册 43. mAudioManager.unregisterMediaButtonEventReceiver(chinaCN); 44. } 45. } package com.qin.mediabutton; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.os.Bundle; import android.view.KeyEvent; public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //由于在模拟器上测试,我们手动发送一个MEDIA_BUTTON的广播,有真机更好处理了 Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); //构造一个KeyEvent对象 KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ; //作为附加值添加至mbIntent对象中 mbIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); //此时China_MBReceiver和England_MBReceiver都会接收到该广播 sendBroadcast(mbIntent); AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); //AudioManager注册一个MediaButton对象 ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName()); //只有China_MBReceiver能够接收到了,它是出于栈顶的。 //不过,在模拟上检测不到这个效果,因为这个广播是我们发送的,流程不是我们在上面介绍的。 mAudioManager.registerMediaButtonEventReceiver(chinaCN); //sendBroadcast(mbIntent,null); } //当一个Activity/Service死去时,我们需要取消这个MediaoButtonReceiver的注册,如下 protected void onDestroy(){ super.onDestroy() ; AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName()); //取消注册 mAudioManager.unregisterMediaButtonEventReceiver(chinaCN); } } 值得注意的一点时,当我们为一个应用程序注册了MediaoButtonReceiver时,在程序离开时,我们需要取消该 MediaoButtonReceiver的注册,在onDestroy()调用unregisterMediaButtonEventReceiver()方法就OK,这样应用程序之间 的交互就更具逻辑性了。 |
|||
Linux下rpm 安装包方式安装 | |||
为了方便linux 用户添加和删除软件,Red Hat 公司提出了软件包管理器RPM,由于它的出现使得在linux 中安装、卸载应用程序变得相对简单,默认情况下(即不出现文件依赖问题)用户只需双击rpm 软件包,系统会自动进行安装。 一个rpm包文件是能够让应用软件运行的全部文件的一个集合,它记录了二进制软件的内容、安装的位置、软件包的描述信息、软件包之间的依赖关系等信息。RPM 工具对系统中全部rpm 软件包进行全面管理,因此它能够记住用户添加了什么以及这些软件每个文件的具体安装路径,以便用户完全地、彻底地删除。一般来说,rpm 软件包发布的软件比需要手工编译的软件容易安装和维护,但是有些rpm软件包需要大量的依赖包,这时如果没有联网也是比较头痛的一件事情。 下面介绍命令行方式安装rpm,在终端中我们可以使用rpm -i [选项] [rpm 包文件名]来进行安装,常用选项如下: -h 使用符号#显示安装进度 -v 报告每一步操作的情况 --replacepkge 无论软件包是否已被安装,都强行安装软件包 --test 安装测试,并不实际安装 --nodeps 忽略软件包的依赖关系强行安装(一般不能正常运行,因为缺少依赖文件) --force 忽略软件包及文件的冲突 假设在root 用户桌面上有一个notepad.rpm 软件包,那么我们可以在终端中输入: rpm -ivh /root/Desktop/notepad.rpm进行安装,如果出现了错误提示“error: Faild dependencies”则表明出现了软件包依赖问题,下面会有提示需要哪个文件,安装完 哪个文件后(google 一个)再次安装即可。也可以在上述命 令的后面加上--nodeps 强制安装。 如果需要删除rpm 软件包可以使用下面的命令 rpm -e notepad 如果同样出现依赖问题同样加上--nodeps参数。如果大家的系统能够连接互联网,则在图形界面下双击rpm 软件包即使出现了依赖问题,软件包会自动到网络下载相应依赖文件后继续安装。 |
|||
Android通话状态(PhoneState)的获取 | |||
1. 利用繼承PhoneStateListener來實作當通話狀態為閒置、接起或響起時我們所要做的動作。 2. 由於我們需取得目前手機的通話狀態,因此必需在AndroidManifest.xml內新增一個讀取通話狀態的權限。 3. MainActivity.java package org.me.android_callstate; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.widget.Toast; public class MainActivity extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); //電話狀態的Listener MyPhoneStateListener myPhoneStateListener = new MyPhoneStateListener(); //取得TelephonyManager TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); //將電話狀態的Listener加到取得TelephonyManager telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } public class MyPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String phoneNumber) { switch (state) { //電話狀態是閒置的 case TelephonyManager.CALL_STATE_IDLE: break; //電話狀態是接起的 case TelephonyManager.CALL_STATE_OFFHOOK: Toast.makeText(MainActivity.this, "正接起電話…", Toast.LENGTH_LONG).show(); break; //電話狀態是響起的 case TelephonyManager.CALL_STATE_RINGING: Toast.makeText(MainActivity.this, phoneNumber + "正打電話來…", Toast.LENGTH_LONG).show(); break; default: break; } } } } 4.AndroidManifest.xml <?xml version="1.0" encoding="UTF-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.me.android_callstate"> <application> <activity android:name=".MainActivity" android:label="MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission> </manifest> |
|||
setVolumeControlStream(int streamType) | |||
当开发多媒体应用或者游戏应用的时候,需要使用音量控制键来设置程序的音量大小。在Android系统中有多中音频流,通过Activity中的函数 setVolumeControlStream(int streamType)可以设置该Activity中音量控制键控制的音频流,一般在onCreate函数中设置。 Android中有如下几种音频流: * AudioManager.STREAM_MUSIC /** The audio stream for music playback */ * AudioManager.STREAM_RING /** The audio stream for the phone ring */ * AudioManager.STREAM_ALARM /** The audio stream for alarms */ * AudioManager.STREAM_NOTIFICATION /** The audio stream for notifications */ * AudioManager.STREAM_SYSTEM /** The audio stream for system sounds */ * AudioManager.STREAM_VOICECALL /** The audio stream for phone calls */ setVolumeControlStream函数描述: void android.app.Activity .setVolumeControlStream(int streamType) Suggests an audio stream whose volume should be changed by the hardware volume controls. The suggested audio stream will be tied to the window of this Activity. If the Activity is switched, the stream set here is no longer the suggested stream. The client does not need to save and restore the old suggested stream value in onPause and onResume. Parameters: streamType The type of the audio stream whose volume should be changed by the hardware volume controls. It is not guaranteed that the hardware volume controls will always change this stream's volume (for example, if a call is in progress, its stream's volume may be changed instead). To reset back to the default, use AudioManager.USE_DEFAULT_STREAM_TYPE . |
|||
深入剖析Android消息机制 | 控件属性 | ||
在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、 更好地架构系统,避免一些低级的错误。在学习Android中消息机制之前,我们先了解与消息有关的几个类: 1.Message 消息对象,顾名思义就是记录消息信息的类。这个类有几个比较重要的字段: a.arg1和arg2:我们可以使用两个字段用来存放我们需要传递的整型值,在Service中,我们可以用来存放Service的ID。 b.obj:该字段是Object类型,我们可以让该字段传递某个多项到消息的接受者中。 c.what:这个字段可以说是消息的标志,在消息处理中,我们可以根据这个字段的不同的值进行不同的处理,类似于我们在处理Button 事件时,通过switch(v.getId())判断是点击了哪个按钮。 在使用Message时,我们可以通过new Message()创建一个Message实例,但是Android更推荐我们通过Message.obtain()或者 Handler.obtainMessage()获取Message对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实 例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象。通过分 析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。 代码下载地址: 在Linux公社的1号FTP服务器里,下载地址: FTP地址:ftp://www.linuxidc.com 用户名:www.linuxidc.com 密码:www.muu.cc 在 2011年LinuxIDC.com\4月\深入剖析Android消息机制 下载方法见 http://www.linuxidc.net/thread-1187-1-1.html 2.MessageQueue 消息队列,用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。存放并非实际意义的保存,而是将Message对象以链表 的方式串联起来的。MessageQueue对象不需要我们自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个 MessageQueue。我们可以通过Looper.myQueue()获取当前线程中的MessageQueue。 3.Looper MessageQueue的管理者,在一个线程中,如果存在Looper对象,则必定存在MessageQueue对象,并且只存在一个Looper对象和一个 MessageQueue对象。在Android系统中,除了主线程有默认的Looper对象,其它线程默认是没有Looper对象。如果想让我们新创建的 线程拥有Looper对象时,我们首先应调用Looper.prepare()方法,然后再调用Looper.loop()方法。典型的用法如下: 1. class LooperThread extends Thread 2. { 3. public Handler mHandler; 4. public void run() 5. { 6. Looper.prepare(); 7. //其它需要处理的操作 8. Looper.loop(); 9. } 10. } 倘若我们的线程中存在Looper对象,则我们可以通过Looper.myLooper()获取,此外我们还可以通过Looper.getMainLooper()获 取当前应用系统中主线程的Looper对象。在这个地方有一点需要注意,假如Looper对象位于应用程序主线程中,则 Looper.myLooper()和Looper.getMainLooper()获取的是同一个对象。 4.Handler 消息的处理者。通过Handler对象我们可以封装Message对象,然后通过sendMessage(msg)把Message对象添加到 MessageQueue 中;当MessageQueue循环到该Message时,就会调用该Message对象对应的handler对象的 handleMessage()方法对其进行处理。 由于是在handleMessage()方法中处理消息,因此我们应该编写一个类继承自 Handler,然后在handleMessage()处理我们需要的操 作。 1. /** 2. * 3. * @author coolszy 4. * @blog http://blog.csdn.net/coolszy 5. * 6. */ 7. 8. public class MessageService extends Service 9. 10. { 11. private static final String TAG = "MessageService"; 12. private static final int KUKA = 0; 13. private Looper looper; 14. private ServiceHandler handler; 15. /** 16. * 由于处理消息是在Handler的handleMessage()方法中,因此我们需要自己编写类 17. * 继承自Handler类,然后在handleMessage()中编写我们所需要的功能代码 18. * @author coolszy 19. * 20. */ 21. private final class ServiceHandler extends Handler 22. { 23. public ServiceHandler(Looper looper) 24. { 25. super(looper); 26. } 27. 28. @Override 29. public void handleMessage(Message msg) 30. { 31. // 根据what字段判断是哪个消息 32. switch (msg.what) 33. { 34. case KUKA: 35. //获取msg的obj字段。我们可在此编写我们所需要的功能代码 36. Log.i(TAG, "The obj field of msg:" + msg.obj); 37. break; 38. // other cases 39. default: 40. break; 41. } 42. // 如果我们Service已完成任务,则停止Service 43. stopSelf(msg.arg1); 44. } 45. } 46. 47. @Override 48. public void onCreate() 49. { 50. Log.i(TAG, "MessageService-->onCreate()"); 51. // 默认情况下Service是运行在主线程中,而服务一般又十分耗费时间,如果 52. // 放在主线程中,将会影响程序与用户的交互,因此把Service 53. // 放在一个单独的线程中执行 54. HandlerThread thread = new HandlerThread("MessageDemoThread", Process.THREAD_PRIORITY_BACKGROUND); 55. thread.start(); 56. // 获取当前线程中的looper对象 57. looper = thread.getLooper(); 58. //创建Handler对象,把looper传递过来使得handler、 59. //looper和messageQueue三者建立联系 60. handler = new ServiceHandler(looper); 61. } 62. 63. @Override 64. public int onStartCommand(Intent intent, int flags, int startId) 65. { 66. Log.i(TAG, "MessageService-->onStartCommand()"); 67. 68. //从消息池中获取一个Message实例 69. Message msg = handler.obtainMessage(); 70. // arg1保存线程的ID,在handleMessage()方法中 71. // 我们可以通过stopSelf(startId)方法,停止服务 72. msg.arg1 = startId; 73. // msg的标志 74. msg.what = KUKA; 75. // 在这里我创建一个date对象,赋值给obj字段 76. // 在实际中我们可以通过obj传递我们需要处理的对象 77. Date date = new Date(); 78. msg.obj = date; 79. // 把msg添加到MessageQueue中 80. handler.sendMessage(msg); 81. return START_STICKY; 82. } 83. 84. @Override 85. public void onDestroy() 86. { 87. Log.i(TAG, "MessageService-->onDestroy()"); 88. } 89. 90. @Override 91. public IBinder onBind(Intent intent) 92. { 93. return null; 94. } 95. } 下面我们通过跟踪代码分析在Android中是如何处理消息。首先贴上测试代码: 运行结果: onCreate(); onStartCommand(); onDestrory(); 注:在测试代码中我们使用了HandlerThread类,该类是Thread的子类,该类运行时将会创建looper对象,使用该类省去了我们自己 编写Thread子类并且创建Looper的麻烦。 下面我们分析下程序的运行过程: 1.onCreate() 首先启动服务时将会调用onCreate()方法,在该方法中我们new了一个HandlerThread对象,提供了线程的名字和优先级。 紧接着我们调用了start()方法,执行该方法将会调用HandlerThread对象的run()方法: 1. public void run() { 2. mTid = Process.myTid(); 3. Looper.prepare(); 4. synchronized (this) { 5. mLooper = Looper.myLooper(); 6. notifyAll(); 7. } 8. Process.setThreadPriority(mPriority); 9. onLooperPrepared(); 10. Looper.loop(); 11. mTid = -1; 12. } 在run()方法中,系统给线程添加的Looper,同时调用了Looper的loop()方法: 1. public static final void loop() { 2. 3. Looper me = myLooper(); 4. MessageQueue queue = me.mQueue; 5. while (true) { 6. Message msg = queue.next(); // might block 7. //if (!me.mRun) { 8. // break; 9. //} 10. if (msg != null) { 11. if (msg.target == null) { 12. // No target is a magic identifier for the quit message. 13. return; 14. } 15. if (me.mLogging!= null) me.mLogging.println( 16. ">>>>> Dispatching to " + msg.target + " " 17. + msg.callback + ": " + msg.what 18. ); 19. msg.target.dispatchMessage(msg); 20. if (me.mLogging!= null) me.mLogging.println( 21. "<<<<< Finished to " + msg.target + " " 22. + msg.callback); 23. msg.recycle(); 24. } 25. } 26. } 通过源码我们可以看到loop()方法是个死循环,将会不停的从MessageQueue对象中获取Message对象,如果 MessageQueue 对象中 不存在Message对象,则结束本次循环,然后继续循环;如果存在Message对象,则执行 msg.target.dispatchMessage(msg),但 是这个msg的.target字段的值是什么呢?我们先暂时停止跟踪源码,返回到 onCreate()方法中。线程执行完start()方法后,我们可 以获取线程的Looper对象,然后new一个ServiceHandler对象,我们把Looper对象传到ServiceHandler构造函数中将使 handler、looper和messageQueue三者建立联系. 2.onStartCommand() 执行完onStart()方法后,将执行onStartCommand()方法。首先我们从消息池中获取一个Message实例,然后给 Message对象的 arg1、what、obj三个字段赋值。紧接着调用sendMessage(msg)方法,我们跟踪源代码,该方法将会调用 sendMessageDelayed(msg, 0)方法,而sendMessageDelayed()方法又会调用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)方法,在该方法中我们要注意该句代码msg.target = this,msg的target指 向了this,而this就是ServiceHandler对象,因此msg的target字段指向了 ServiceHandler对象,同时该方法又调用 MessageQueue 的enqueueMessage(msg, uptimeMillis)方法: 1. final boolean enqueueMessage(Message msg, long when) { 2. if (msg.when != 0) { 3. throw new AndroidRuntimeException(msg 4. + " This message is already in use."); 5. } 6. if (msg.target == null && !mQuitAllowed) { 7. throw new RuntimeException("Main thread not allowed to quit"); 8. } 9. synchronized (this) { 10. if (mQuiting) { 11. RuntimeException e = new RuntimeException( 12. msg.target + " sending message to a Handler on a dead thread"); 13. Log.w("MessageQueue", e.getMessage(), e); 14. return false; 15. } else if (msg.target == null) { 16. mQuiting = true; 17. } 18. msg.when = when; 19. //Log.d("MessageQueue", "Enqueing: " + msg); 20. Message p = mMessages; 21. if (p == null || when == 0 || when < p.when) { 22. msg.next = p; 23. mMessages = msg; 24. this.notify(); 25. } else { 26. Message prev = null; 27. while (p != null && p.when <= when) { 28. prev = p; 29. p = p.next; 30. } 31. msg.next = prev.next; 32. prev.next = msg; 33. this.notify(); 34. } 35. } 36. return true; 37. } 该方法主要的任务就是把Message对象的添加到MessageQueue中(数据结构最基础的东西,自己画图理解下)。 handler.sendMessage()-->handler.sendMessageDelayed()-->handler.sendMessageAtTime()-->msg.target = this;queue.enqueueMessage==>把msg添加到消息队列中 3.handleMessage(msg) onStartCommand()执行完毕后我们的Service中的方法就执行完毕了,那么handleMessage()是怎么调用的呢?在前面分析的 loop()方法中,我们当时不知道msg的target字段代码什么,通过上面分析现在我们知道它代表ServiceHandler对 象,msg.target.dispatchMessage(msg);则表示执行ServiceHandler对象中的 dispatchMessage()方法: 1. public void dispatchMessage(Message msg) { 2. if (msg.callback != null) { 3. handleCallback(msg); 4. } else { 5. if (mCallback != null) { 6. if (mCallback.handleMessage(msg)) { 7. return; 8. } 9. } 10. handleMessage(msg); 11. } 12. } 该方法首先判断callback是否为空,我们跟踪的过程中未见给其赋值,因此callback字段为空,所以最终将会执行handleMessage ()方法,也就是我们ServiceHandler类中复写的方法。在该方法将根据what字段的值判断执行哪段代码。 至此,我们看到,一个Message经由Handler的发送,MessageQueue的入队,Looper的抽取,又再一次地回到Handler的怀抱中 。而绕的这一圈,也正好帮助我们将同步操作变成了异步操作。 |
|||
android ViewFilpper属性 | 控件属性 | ||
1、ViewFilpper的 android:persistentDrawingCache(指定缓存策略) 定义绘图的高速缓存的持久性。 绘图缓存可能由一个 ViewGroup 在特定情况下为其所有的子类启用,例如在一个滚动的过程中。 此属性可以保留在内存中的缓存后其初始的使用。 坚持缓存会消耗更多的内存,但可能会阻止频繁的垃圾回收是反复创建缓存。 默认情况下持续存在设置为滚动 2、ViewFilpper的android:flipInterval( 定每个View动画之间的时间间隔) 设置每个view显示的时间长短,单位毫秒 3、ViewFilpper的inAnimation和outAnimation(分别指定View进出使用的动画效果) |
|||
android文档中allowTaskReparenting疑问 | |||
If an activity has its allowTaskReparenting attribute set to "true", it can move from the task it starts in to the task it has an affinity for when that task comes to the fore. For example, suppose that an activity that reports weather conditions in selected cities is defined as part of a travel application. It has the same affinity as other activities in the same application (the default affinity) and it allows reparenting. One of your activities starts the weather reporter, so it initially belongs to the same task as your activity. However, when the travel application next comes forward, the weather reporter will be reassigned to and displayed with that task. 一个activity1原来属于task1,但是如果task2启动起来的话,activity1可能不再属于task1了 ,转而投奔task2去了。 当然前提条件是allowTaskReparenting,还有affinity设置 有点像,你捡到一条狗,在家里喂养几天觉得不错,当自己家的了;但是突然有一天他的主人找上门来了 ,小狗还是乖乖和主人走了。 用法<application android:allowTaskReparenting="true/false"></application> 是否允许activity更换从属的任务,比如从短信息任务 切换到浏览器任务。 用来标记Activity能否从启动的Task移动到有着affinity的Task(当这个Task进入到前台时)—— “true”,表示能移动,“false”,表示它必须呆在启动时呆在的那个Task里。 如果这个特性没有被设定,设定到<application>元素上的allowTaskReparenting特性 的值会应用到Activity上。默认值为“false”。 一般来说,当Activity启动后,它就与启动它的Task关联,并且在那里耗尽它的整个生命周期 。当当前的Task不再显示时,你可以使用这个特性来强制Activity移动到有着affinity的Task中。 典型用法是:把一个应用程序的Activity移到另一个应用程序的主Task中。 例如,如果e-mail中包含一个web页的链接,点击它就会启动一个Activity来显示这个页面 。这个Activity是由Browser应用程序定义的,但是,现在它作为e-mail Task的一部分。如果它重 新宿主到Browser Task里,当Browser下一次进入到前台时,它就能被看见,并且,当e-mail Task 再次进入前台时,就看不到它了。 Actvity的affinity是由taskAffinity特性定义的。Task的affinity是通过读取根 Activity的affinity 决定。因此,根据定义,根Activity总是位于相同affinity的Task里。 由于启动模式为“singleTask”和 “singleInstance”的Activity只能位于Task的底部,因此, 重新宿主只能限于“standard”和“singleTop”模式。 |
|||
Android 修改Menu背景 | |||
public void setMenuBackground(Context context, Menu menu) { ((Activity) context).getLayoutInflater().setFactory(new Factory() { @Override public View onCreateView(String name, Context context, AttributeSet attrs) { if (name.equalsIgnoreCase("com.android.internal.view.menu.IconMenuItemView")) { try { LayoutInflater f = ((Activity) context) .getLayoutInflater(); final View view = f.createView(name, null, attrs);// 尝试创建我们自己布局 new Handler().post(new Runnable() { public void run() { view.setBackgroundResource(R.drawable.menu_bg);// 设置背景为我们自定义的图片,替换cwj_bg文件即可 } }); return view; } catch (Exception e) { } } return null; } }); } |
|||
手机常用查询 | |||
1 输入*#06#(手机型号,请更换):显示IMEI码 2 输入*#0000#:显示软件版本 (部分型号如果不起作用,可按*#型号代码#,如*#6110#) 第一行--软件版本; 第二行--软件发布日期; 第三行--手机型号 3 输入*#92702689#查询更多的手机信息。有五个选项(可用上下方向键选择): ①Serial No.:手机的IMEI码。 ②Made:手机的制造日期. (本人用的是诺基亚6108, 上面写的是092003, 即为2003年09月生产的 ) ③Purchasing Date:购买日期,此日期一经设定不可更改,新机子应该是mmyyyy ④Repaired:维修次数的记录。新机子应该是mmyyyy ⑥Life timer:新机子是6553501。 B 高级指令 (专门为各位大侠准备的, ^_^ 建议一般初学者不要随便输入,以免输入错误导致手机无法复原!) 1 速率编码 *3370#或*efr#:开启(EFR)全速率编码 #3370#或#efr#:关闭全速率编码(开启全速增强型编码模式,可改善话音质量但会耗电) *4720#或*hra0#:开启(HR)半速率编码 #4720#或#hra0#:关闭半速率编码 (话音质量降低,但可延长电池大概30%左右使用时间,需网络支持) 键入这些代码后,会关机重开,然后才能生效。 2 SIM卡锁信息 *#746025625# (= *#sim0clock#):如果SIM卡能被锁,进行检测时键入该指令后,手机显 示 "SIM CLOCK STOP ALLOWED " or "SIM CLOCK STOP NOT ALLOWED ",这取决于你的SIM卡。 SIM卡锁信息:包括四种不同的锁 1).国家锁--锁指定的国家 2).网络锁--锁指定的网络 3).供应商锁--锁服务提供商 4).SIM卡锁--锁指定的SIM卡 3 查询手机是否锁频 首先必须找出设定手机时必须使用的几个键。其中,连续按*键二次即出现 "+ ";连续按*键三次 即出现 "p ";连续按*键四次即出现 "w "。然后,你就可以依次顺序输入相应组合键。 #pw+1234567890+1#:查询是否锁国家码 #pw+1234567890+2#:查询是否锁网络码 #pw+1234567890+3#:查询是否锁网络提供者锁定的码 #pw+1234567890+4#:查询是否锁SIM卡 4 电源按键 轻触电源键,屏幕将显示情景模式的选单,你可以通过上下键快速地在各个模式中切换。在键盘 锁模式下打开屏幕灯,你曾感受过在夜晚摸黑想打开手机的键盘锁的烦恼吗?其实你可以轻触电 源键,这时屏幕和键盘的夜灯将会打开,这样你就可以从容地分辨按键打开键盘锁了。 5 查看手机状态 操作指令:#pw+(mastercode)+X#(mastercode)是一10位数(没有括号) X是一个1到4的数, 它显示以上的锁,还不确定何数对应 何锁。 SIM卡不限制信息,意味着你的电话还没锁。 1).VIN CHARGER INPUT VOLTAGE 8.4V 0.8A 2).CHRG CTRL CHARGER CONTROL PWM 32Khz 3).XMIC MIC INPUT 60mV - 1V 4).SGND SIGNAL GROUND 5).XEAR EAR OUTPUT 80mV - 1V 6).MBUS 9600 B/S 7).FBUS_RX 9.6 - 230.4 KB/S 8).FBUS_TX 9.6 - 230.4 KB/S 9).L_GND CHARGER / LOGIC GND C 诺基亚其他手机指令内容 1 NOKIA 9000/9000i 1).显示IMEI号:*#06# 2).显示软件版本号:*#682371158412125# 最新版本在电话信息下面 3).显示制造星期和年: *#3283# 4).电话类型: GE8 2 5110锁码机解码 操作步骤:按C,按下 -> 按C不放 -> 按*不放 -> 按*不放 -> 键入04*PIN*PIN*PIN#,就完成了。 3 更换当前号码 按住#键不放约一秒钟,屏幕会出现“交换号码?” ,按确认后屏幕上方会出现一个2字,这时 手机不能拨出但能接听来电。取消重操作一次,利用它可达到锁机的效果。在非来电时进入来电 菜单,持续按住功能键两秒,将进入来电菜单,而这个菜单平时只是会在有电话打入时才会出现 的。 4 6110在待机画面显示名字 选择一个最不常使用的操作模式,通常是寻呼机 Menu3-5,将个人化选择Menu3-5-2设定完后, 重新命名Menu3-5-3为自己的名字,启动后即可。备注:若要恢复为寻呼机,重新命名Menu3-5 -3为空白即可。 D 查验“手机串号”:www.chinamobile.gov.cn; 辨别“进网许可证”真伪:www.tenaa.com.cn! 如果买的是原装水货,一般来说质量还是行的;如果是改版就难说了,不法商家私自将英文机改装成中文 机,将英文机软件置换,使之可以输入中文。此种手机在使用中可能会出现字符乱码、死机等现象。 E 还有一个鲜为人知的: 恢复GMS蜂窝网运营商的工程代码:*#67705646# 如果是“全球通”或“神州行”的SIM卡就将待机画面恢复为“中国电信”或“中国移动”;如果是中国联通用户,就显示为“中国联通”! |
|||
Android获取手机和系统版本等信息的代码 | |||
String phoneInfo = "Product: " + android.os.Build.PRODUCT; phoneInfo += ", CPU_ABI: " + android.os.Build.CPU_ABI; phoneInfo += ", TAGS: " + android.os.Build.TAGS; phoneInfo += ", VERSION_CODES.BASE: " + android.os.Build.VERSION_CODES.BASE; phoneInfo += ", MODEL: " + android.os.Build.MODEL; phoneInfo += ", SDK: " + android.os.Build.VERSION.SDK; phoneInfo += ", VERSION.RELEASE: " + android.os.Build.VERSION.RELEASE; phoneInfo += ", DEVICE: " + android.os.Build.DEVICE; phoneInfo += ", DISPLAY: " + android.os.Build.DISPLAY; phoneInfo += ", BRAND: " + android.os.Build.BRAND; phoneInfo += ", BOARD: " + android.os.Build.BOARD; phoneInfo += ", FINGERPRINT: " + android.os.Build.FINGERPRINT; phoneInfo += ", ID: " + android.os.Build.ID; phoneInfo += ", MANUFACTURER: " + android.os.Build.MANUFACTURER; phoneInfo += ", USER: " + android.os.Build.USER; // Toast.makeText(this, phoneInfo, Toast.LENGTH_LONG).show(); TextView t = (TextView) findViewById(R.id.main_phoneinfo); t.setText(phoneInfo); |
|||
改变android程序head样式 | |||
我们怎么把自己的程序头部弄好看一点呢? 一步一步来 第一编写头部布局xml文件 <LinearLayout android:id="@+id/activity_title" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" xmlns:android="http://schemas.android.com/apk/res/android" android:gravity="center_vertical" > <ImageView android:id="@+id/icon" android:layout_width="25px" android:layout_height="25px" android:background="@drawable/icon" > </ImageView> <TextView android:id="@+id/app_title" android:layout_width="wrap_content" android:layout_height="25px" android:text="@string/app_name" android:paddingLeft="8px" android:paddingTop="2px" > </TextView> </LinearLayout> 第二在每个activity中 super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.main); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.title); 注意这四句代码的顺序 这样的话头部就变得有图标有文字,而且有背景了,但是有一个问题,就头部两边总有空白的地方,很是难看,现在在做地三步 在样式文件中添加<style name="CustomWindowTitleBackground"> <item name="android:background">@drawable/title_bg</item> <item name="android:textSize">17dp</item> <item name="android:textColor">#ffffff</item> <item name="android:paddingLeft">14dp</item> </style> <style name="test" parent="android:Theme"> <item name="android:windowTitleSize">40dp</item> <item name="android:windowTitleBackgroundStyle">@style/CustomWindowTitleBackground</item> </style> 第四在manifest中所需要改变的activity标签中添加android:theme ="@style/test" 这样就解决了第二步所存在的问题了 |
|||
Android开发中IntentService的使用 | 控件属性 | ||
IntentService是Service类的子类,用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService,IntentService通过worker thread处理每个Intent对象,执行完所有的工作之后自动停止Service。 说明:worker thread处理所有通过传递过来的请求,创建一个worker queue,一次只传递一个intent到onHandleIntent中,从而不必担心多线程带来的问题。处理完毕之后自动调用stopSelf()方法;默认实现了Onbind()方法,返回值为null; 模式实现了StartCommand()方法,这个方法会放到worker queue中,然后在onHandleIntent()中执行0。 使用IntentService需要两个步骤: 1、写构造函数 2、复写onHandleIntent()方法 好处:处理异步请求的时候可以减少写代码的工作量,比较轻松地实现项目的需求 —————————————— 关于继承IntentService,构造函数需要注意一个地方,Eclipse默认生成的构造函数是 public MyIntentService(String name) { super(name); } 而官方文档的示例中是没有参数的构造方法,使用Eclipse这个默认构造函数的话会报一个运行时错误: java.lang.InstantiationException,在Google Groups里找到了解决办法,继承IntentService的类必须有一个public的无参的构造函数,将上面Eclipse自动生成的构造函数改为下面这样的就可以了: public MyIntentService() { super("someName"); } 为什么要这样改,看IntentService构造函数的源码: public IntentService(String name) { super(); mName = name; } SDK文档里说构造函数里面的服务名字只在调试时有用,可以随便写一个名字。 另一个需要特别指出的时,在onHandleIntent里不需要自己处理线程,或者新启线程,IntentService默认会为队列中的任务启动后台线程,源码中的实现是这样的: private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } 看源码可以得知onHanldeIntent()是在新的后台线程HandlerThread里执行的,所以不需要要我们自己新开线程。 ------------------------- 不管是何种Service,它默认都是在应用程序的主线程(亦即UI线程)中运行的。所以,如果你的Service将要运行非常耗时或者可能被阻塞的操作时,你的应用程序将会被挂起,甚至会出现ANR错误。为了避免这一问题,你应该在Service中重新启动一个新的线程来进行这些操作。现有两种方法共大家参考: ① 直接在Service的onStartCommand()方法中重启一个线程来执行,如: @Override public int onStartCommand(Intent intent, int flags, int startId) { MyServiceActivity.updateLog(TAG + " ----> onStartCommand()"); new Thread(new Runnable() { @Override public void run() { // 此处进行耗时的操作,这里只是简单地让线程睡眠了1s try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }).start(); return START_STICKY; } ② Android SDK 中为我们提供了一个现成的Service类来实现这个功能,它就是IntentService,它主要负责以下几个方面: # Creates a default worker thread that executes all intents delivered to onStartCommand() separate from your application's main thread. 生成一个默认的且与主线程互相独立的工作者线程来执行所有传送至 onStartCommand() 方法的Intetnt # Creates a work queue that passes one intent at a time to your onHandleIntent() implementation, so you never have to worry about multi-threading. 生成一个工作队列来传送Intent对象给你的onHandleIntent()方法,同一时刻只传送一个Intent对象,这样一来,你就不必担心多线程的问题。 # Stops the service after all start requests have been handled, so you never have to call stopSelf(). 在所有的请求(Intent)都被执行完以后会自动停止服务,所以,你不需要自己去调用stopSelf()方法来停止该服务 # Provides default implementation of onBind() that returns null. 提供了一个onBind()方法的默认实现,它返回null # Provides a default implementation of onStartCommand() that sends the intent to the work queue and then to your onHandleIntent() implementation 提供了一个onStartCommand()方法的默认实现,它将Intent先传送至工作队列,然后从工作队列中每次取出一个传送至onHandleIntent()方法,在该方法中对Intent对相应的处理 以上,英文来自官方SDK,中文为我所译。 从以上看来,你所需要做的就是实现 onHandleIntent() 方法,在该方法内实现你想进行的操作。另外,继承IntentService时,你必须提供一个无参构造函数,且在该构造函数内,你需要调用父类的构造函数,如下: public HelloIntentService() { super("HelloIntentService"); } 下面给出一例,来解释一下: // activity 的onCreate() @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startSer1 = (Button) findViewById(R.id.startSer1); stopSer1 = (Button) findViewById(R.id.stopSer1); startSer2 = (Button) findViewById(R.id.startSer2); stopSer2 = (Button) findViewById(R.id.stopSer2); log = (TextView) findViewById(R.id.log); logView = (ScrollView) findViewById(R.id.logView); startSer1.setOnClickListener(btnListener); stopSer1.setOnClickListener(btnListener); startSer2.setOnClickListener(btnListener); stopSer2.setOnClickListener(btnListener); intent = new Intent(MyServiceActivity.this, IntentServiceDemo.class); // 打印出主线程的ID long id = Thread.currentThread().getId(); updateLog(TAG + " ----> onCreate() in thread id: " + id); } // service 代码 package com.archer.rainbow; import java.text.SimpleDateFormat; import java.util.Date; import android.app.IntentService; import android.content.Intent; public class IntentServiceDemo extends IntentService { private static final String TAG = "IntentServiceDemo"; private static final SimpleDateFormat SDF_DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss.SSS"); public IntentServiceDemo() { super(TAG); MyServiceActivity.updateLog(TAG + " ----> constructor"); } @Override public void onCreate() { super.onCreate(); // 打印出该Service所在线程的ID long id = Thread.currentThread().getId(); MyServiceActivity.updateLog(TAG + " ----> onCreate() in thread id: " + id); } @Override public void onDestroy() { super.onDestroy(); MyServiceActivity.updateLog(TAG + " ----> onDestroy()"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { MyServiceActivity.updateLog(TAG + " ----> onStartCommand()"); // 记录发送此请求的时间 intent.putExtra("time", System.currentTimeMillis()); return super.onStartCommand(intent, flags, startId); } @Override public void setIntentRedelivery(boolean enabled) { MyServiceActivity.updateLog(TAG + " ----> setIntentRedelivery()"); super.setIntentRedelivery(enabled); } @Override protected void onHandleIntent(Intent intent) { // 打印出处理intent所用的线程的ID long id = Thread.currentThread().getId(); MyServiceActivity.updateLog(TAG + " ----> onHandleIntent() in thread id: " + id); long time = intent.getLongExtra("time", 0); Date date = new Date(time); try { // 打印出每个请求对应的触发时间 MyServiceActivity.updateLog(TAG + " ----> onHandleIntent(): 下载文件中..." + SDF_DATE_FORMAT.format(date)); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } 应用启动时,界面如下: 从此图可以看出,主线程(UI线程)的ID是1。接,连续点击三次Start Service 1 按钮,得如下画面: 从此图中可以看出,IntentServiceDemo的onCreate()所处的线程ID仍为1,说明它是在主线程中被执行的,且只被执行一次。然后,我每点击一次按钮,它都会触发一下onStartCommand()方法。仔细看第二次与第三次的onCommand()方法以及 onHandleIntent()打印出来的语句,你会发现,第二、三两次点击按钮与第一次点击按钮的时间是没有超过3秒钟的,它们是连续被执行的,这说明了什么呢?说明,在第一个intent被处理时(即onHandleIntent()处于运行中),该Service仍然可以接受新的请求,但接受到新的请求后并没有立即执行,而是将它们放入了工作队列中,等待被执行。 这就是 IntentService 的简单用法。但你若是想在Service中让多个线程并发的话,就得另想法子喽。比如,使用第一种方法,在Service内部起多个线程,但是这样的话,你可要处理好线程的同步哦~~~ |
|||
关于android应用程序使用ActivityManager退出的问题! | |||
1.5-2.1之前都是 ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.restartPackage("包名"); 就搞定了。 但2.2就不行,只是把后台数据清空了,没有关闭页面,导致null异常。 解决方案: 步骤1: /** * activityList:所有activity对象,用于退出时全部finish; Activity走onCreate时,添加到该集合 */ public static List<Activity> activityList = new ArrayList<Activity>(); 步骤2: /** * 页面初始化 * * @param savedInstanceState */ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityList.add(this); 步骤3: /** * 退出客户端。 * * @param context 上下文 */ public static void exitClient(Context context) { Log.d(TAG, "----- exitClient -----"); // 关闭所有Activity for (int i = 0; i < activityList.size(); i++) { if (null != activityList.get(i)) { activityList.get(i).finish(); } } ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.restartPackage("com.huawei.softclient.mtvclient"); System.exit(0); } 这里要加System.exit(0); <uses-permission andriod:name="android.permission.RESTART_PAKCEAGES"> 该方法经过专业测试已经通过。 当时搞这东西害得我头大了几天,主要是资料太少了,只能自己研究。真是研究生啊。。。 ---------------------------------------------------------------------------------------------------------------------- 很多网友不知道,Android应用如何完全退出,此前Android123讲过了三种比较常见的方法,参考 Android程序完全退出的三种方法 一文,但是有的网友可能发现Android 2.2中即使使用了Google最新给出的Android 2.2新增API killBackgroundProcesses 方法也无法实现。在J2SE中我们可能使用System.exit(0); 退出,那么我们提到的5种常规方法都无法完全退出怎么办呢? 今天Android开发网给出大家第六种非常规方法,制造异常退出。 1. 首先我们可以制造一个空指针的异常,比如TextView.setText方法中执行一个int型的内容,由于setText方法重载了 R.string.xxx这样的资源int型内容,但是我们没有声明这个资源,仅仅把String写成了int的值,就会产生一个异常, |
|||
android总结 | |||
1. 当发生ANR时,记录会保存在data/anr/traces.txt里 2. 关于LBS(Location Based Service),基于位置的服务。关键是要获得坐标信息,然后就可以根据坐标来实现各种业务。 android提供了两种基本的方式来获取坐标,一种是基于GPS,一种是基于NETWORK,包括基站定位和WIFI定位。 GPS的优点是只要有GPS传感器,就可以得到坐标,得到坐标的原理有很多,包括时间差等等。GPS定位的优势是坐标比较精确, 而且即使没有网络也是可以的(当然得到坐标以后的后续处理,没网络还是不行的)。缺点是当在隧道,或者室内的时候, 接收不到卫星信息,就无法得到坐标。android中对应GPS定位的provider是GPS_PROVIDER 基站定位的优点是,只要在基站的覆盖范围内(换言之,手机有信号),就可以得到cell id和LAC(Location Area Code)。 然后通过将cell id和LAC发送到某个系统,这个系统就可以返回位置信息。这有个条件,就是某个系统拥有位置信息。 可能是运营商提供的系统(通常要收费),也可能是免费系统(比如常用的google)。这个方式的缺点是得到的坐标可能不会很精确, 在一个基站覆盖范围内的任何一点,得到的都是同样的坐标,坐标的精确度取决于基站小区的覆盖密度。 另外同样的cell id和LAC,发送给不同的系统,得到的坐标也有可能是不同的。 android中对应基站定位的provider是NETWORK_PROVIDER。另外,还没看过源代码,但我猜测如果用android api来做基站定 位,那应该默认是把cell id和LAC发送到google服务器上,那么就必须要求能上网了。其实如果位置信息系统提供了短信网关接口 ,也是可以不上网的,这样就不能用android api了,而是要获取到cell id和LAC(可能还要求别的参数,根据位置信息系统提供 商提供的接口决定),然后以短信方式发送到位置信息系统,也是可以的。我们这次就打算这么做 3. 关于adapter,现在理解得多了一点。有一类View叫做AdapterView,比如ListView就是AdapterView的一种。这类的 View有一个setAdapter(Adapter adapter)方法,通过这个方法,来update View。 Java代码 ListView listView = (ListView)findViewById(R.layout.list_view); listView.setAdapter(new MyAdapter(this)); 上面的listView在初始化的时候是没有数据的,也就是没有ListItem,通过setAdapter()方法,才填充了数据。 Adapter类的作用,就是根据数据源,来返回View,每个View最终会成为AdapterView的其中一个Child View。如同其名字一样, Adapter是一个数据到视图的适配器。可以将其理解为是一个容器的处理器,它将容器内的每一项,映射成一个View并返回。 数据源可以是资源文件指定的数据集合,也可以来自SQLite,也可以是内存中的自定义对象……总之是一种数据对象的集合。而将 要返回的View,是由Adapter中的getView()方法创建并返回的,可以来自layout文件的设置,也可以是自定义的View对象。 搞清楚原理,就明白AdapterView和Adapter是怎样协同工作,生成页面的了。Adapter根据数据源,循环调用getView()方 法,生成View对象的集合。然后AdapterView.setAdapter()方法,用前面生成的View对象集合,来给视图生成数据项。 ListView只是AdapterView的一个子类,还有很多其他的AdapterView,但是原理都是相同的。有空的话可以看看 ListAdapter类的getView()方法的源码,应该会对这个过程理解得更清晰一些。 4. 搞清楚了上一点,再来看ListActivity,其实ListActivity只是一个普通的Activity,只是它内置了一个ListView, 以及提供了getListView()方法来获取内置的ListView;还提供了setListAdapter()方法,来给这个内置的ListView设置 Adapter;以及诸如onListItemClick()等方法而已。在要使用ListActivity的地方,用普通的Activity也是完全可以的, 只是要多写一些方法而已。 5. 在前面的博客里提到过,关于UI Thread有2个原则。1:不要阻塞UI Thread,2:不要在UI Thread之外操作视图组件。 今天补充一点,除了进一步封装的AsyncTask类之外,android主要是通过提供Handler类来支持这一点的。 Java代码 private final Handler handler = new Handler(){ public void handleMessage(Message msg){ doSomeThingWithView();// 在UI Thread中对视图组件做操作 } }; public void someMethod(){ new Thread(){ public void run(){ someLongTimeJob();// 在worker Thread中做一些耗时操作,以免阻塞UI thread handler.sendMessage();// 发送消息到UI Thread } }.start(); } 通过以上的代码结构,来遵循上述的2个原则。当然,用AsyncTask来处理,代码会更加简洁一些。不过看到比较多的源代码 ,还是采用这种结构来做的。其他方式都不太好,或者有阻塞UI Thread,造成ANR的问题。或者是在worker Thread中操作 了View组件,可能隐藏了一些多线程情况下的BUG Handler就是android提供的,不同线程之间通信的一种方式。用无参数构造器的方式创建Handler实例,则将其绑定到当前 线程(UI Thread)的Looper中。Looper是和Handler紧密相关的一个类,开发者一般不会直接操作Looper类。这个Looper 内部维护一个Message Queue,会对每条Message进行处理。没有看过源码,不过我估计这个过程应该是异步的。 6. 关于Layout布局,现在我还搞得不是太清楚,也是目前最薄弱的一块,只能先简单理解一下书上说的内容。 首先每种ViewGroup都有一个内部类LayoutParams,这个LayoutParams有一个继承体系,比如ViewGroup. LayoutParams->ViewGroup.MarginLayoutParams->LinearLayout.LayoutParams->TableLayout .LayoutParams 然后貌似每个组件的最终Layout是由其Parent决定的,而其自身的Layout又决定了它的Child的Layout布局,这是一个递 归的过程。 A child View requests a size, and the parent makes a decision on how to position the child view on the screen. The child makes a request and the parent makes the decision. Child elements do keep track of what size they're initially asked to be, in case layout is recalculated when things are added or removed, but they can't force a particular size. Because of this, View elements have two sets of dimensions: the size and width they want to take up [getMeasuredWidth ()] and the actual size they end up after a parent's decision [getWidth()]. 暂时就知道这么多,以后再补充一点。我个人感觉这是android中比较难的一部分,可能是以前我没做过swing的开发,所以不太 好类比。android布局和我熟悉的html+css布局差别还是挺大的 7. View组件还有一个focus的概念。即虽然页面上有很多组件,但同时只能有一个组件获取到焦点,来响应用户输入。用户的操 作,可能导致焦点的改变。这点和html一样,很好理解。通常“下一焦点”是由android系统决定的,但是开发者也可以通过在XML 中设置nextFocusDown等属性,来改变默认行为。另外View也有requestFocus()方法,来请求获取焦点。类似jquery中的$ (element).focus() 8. android中的事件响应机制类似设计模式中的“观察者模式”,一个事件分2个阶段:the component raising the event and the component(or components) that respond to the event. 前者类似Button.setOnClickListener (OnClickListener listener),后者类似listener.onClick(View v)。这里要再次重 申一下,由于View接口是单线程的,所以无论何时,要记得只能在UI Thread中调用View上的方法(包括更新View) 9. 程序中用到的资源,统一放在res文件夹下,res文件夹下的子目录是有命名规范的,乱命名就认不出来(也就是,无法编译, 然后通过R.java来引用)。比如res/drawable下放图片,res/layout下放布局XML,res/values下放strings、colors 、styles、arrays等,具体的可以看google的API文档。然后这些资源文件会由aapt工具编译成binary,之后程序中就可以通过 R Class来获取这些编译后的资源 10. Resources类是所有资源的统一入口,提供了如getStringArray(int),openRawResource(int),getXml(int)等 方法 |
|||
Android垃圾回收实质内容解析 | |||
Android手机操作系统中的代码编写方式对于有基础的编程人员来说是比较容易的。因为它是基于Linux平台的操作系统。我们在这里为大家介绍的是Android垃圾回收这一机制,以加深大家对这一系统的了解。 个人觉得sp和wp实际上就是Android 为其c++实现的自动垃圾 回收机制 ,具体到内部实现,sp和wp实际上只是一个实现垃圾回收功能的接口而已,比如说对*,->的重载,是为了其看起来跟真正的指针一样,而真正实现垃圾回收的是refbase这个基类。这部分代码的目录在:/frameworks/base/include/utils/RefBase.h 首先所有的类都会虚继承refbase类,因为它实现了达到Android垃圾回收所需要的所有function,因此实际上所有的对象声明出来以后都具备了自动释放自己的能力,也就是说实际上智能指针就是我们的对象本身,它会维持一个对本身强引用和弱引用的计数,一旦强引用计数为0它就会释放掉自己。 首先我们看sp,sp实际上不是smart pointer的缩写,而是strong pointer,它实际上内部就包含了一个指向对象的指针而已。我们可以简单看看sp的一个构造函数: 1.template< typename T> 2.sp< T>::sp(T* other) 3.: m_ptr(other) 4.{ 5.if (other) other->incStrong(this); 6.} 比如说我们声明一个对象: 1.sp< CameraHardwareInterface> hardware(new CameraHal()); 实际上sp指针对本身没有进行什么操作,就是一个指针的赋值,包含了一个指向对象的指针,但是对象会对对象本身增加一个强引用计数,这个 incStrong的实现就在refbase类里面。新new出来一个CameraHal对象,将它的值给 sp< CameraHardwareInterface>的时候,它的强引用计数就会从0变为1。因此每次将对象赋值给一个sp指针的时候,对象的强引用计数都会加1,下面我们再看看sp的析构函数: 1.template< typename T> 2.sp< T>::~sp() 3.{ 4.if (m_ptr) m_ptr->decStrong(this); 5.} 实际上每次delete一个sp对象的时候,sp指针指向的对象的强引用计数就会减一,当对象的强引用技术 为0的时候这个对象就会被自动释放掉。 我们再看wp,wp就是weak pointer的缩写,弱引用指针的原理 ,就是为了应用Android垃圾回收来减少对那些胖子对象对内存的占用,我们首先来看wp的一个构造函数: 1.wp< T>::wp(T* other) 2.: m_ptr(other) 3.{ 4.if (other) m_refs = other->createWeak(this); 5.} 它和sp一样实际上也就是仅仅对指针进行了赋值而已,对象本身会增加一个对自身的弱引用计数,同时wp还包含一个m_ref指针,这个指针主要是用来将wp升级为sp时候使用的: 1.template< typename T> 2.sp< T> wp< T>::promote() const 3.{ 4.return sp< T>(m_ptr, m_refs); 5.} 6.template< typename T> 7.sp< T>::sp(T* p, weakref_type* refs) 8.: m_ptr((p && refs->attemptIncStrong(this)) ? p : 0) 9.{ 10.} 实际上我们对wp指针唯一能做的就是将wp指针升级为一个sp指针,然后判断是否升级成功,如果成功说明对象依旧存在,如果失败说明对象已经被释放掉了。wp指针我现在看到的是在单例中使用很多,确保mhardware对象只有一个,比如: 1.wp< CameraHardwareInterface> CameraHardwareStub::singleton; 2.sp< CameraHardwareInterface> CameraHal::createInstance() 3.{ 4.LOG_FUNCTION_NAME 5.if (singleton != 0) { 6.sp< CameraHardwareInterface> hardware = singleton.promote(); 7.if (hardware != 0) { 8.return hardware; 9.} 10.} 11.sp< CameraHardwareInterface> hardware(new CameraHal()); //强引用加1 12.singleton = hardware;//弱引用加1 13.return hardware;//赋值构造函数,强引用加1 14.} 15.//hardware被删除,强引用减1 |
|||
Android的智能指针 | |||
在Android的源代码中,经常会看到形如:sp<xxx>、wp<xxx>这样的类型定义,这其实是Android中的智能 指针。智能指针是C++中的一个概念,通过基于引用计数的方法,解决对象的自动释放的问题。在C++编程中,有两个很让人头痛的问题:一是忘记释放动态申 请的对象从而造成内存泄露;二是对象在一个地方释放后,又在别的地方被使用,从而引起内存访问错误。程序员往往需要花费很大精力进行精心设计,以避免这些 问题的出现。在使用智能指针后,动态申请的内存将会被自动释放(有点类似Java的垃圾回收),不需要再使用delete来释放对象,也不需要考虑一个对 象是否已经在其它地方被释放了,从而使程序编写工作减轻不少,而程序的稳定性大大提高。 Android的智能指针相关的源代码在下面两个文件中: frameworks\base\include\utils\RefBase.h frameworks\base\libs\utils\RefBase.cpp 涉及的类以及类之间的关系如下图所示: Android中定义了两种智能指针类型,一种是强指针sp(strong pointer),一种是弱指针(weak pointer)。其实称为强引用和弱引用更合适一些。强指针与一般意义的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使 用者都放弃了对该对象的引用,则该对象将被自动销毁。 弱指针也指向一个对象,但是弱指针仅仅记录该对象的地址,不能通过弱指针来访问该对象,也就是说不能通过弱智真来调用对象的成员函数或访问对象的成员变 量。要想访问弱指针所指向的对象,需首先将弱指针升级为强指针(通过wp类所提供的promote()方法)。弱指针所指向的对象是有可能在其它地方被销 毁的,如果对象已经被销毁,wp的promote()方法将返回空指针,这样就能避免出现地址访问错的情况。 是不是很神奇?弱指针是怎么做到这一点的呢?其实说穿了一点也不复杂,原因就在于每一个可以被智能指针引用的对象都同时被附加了另外一个 weakref_impl类型的对象,这个对象中负责记录对象的强指针引用计数和弱指针引用计数。这个对象是智能指针的实现内部使用的,智能指针的使用者 看不到这个对象。弱指针操作的就是这个对象,只有当强引用计数和弱引用计数都为0时,这个对象才会被销毁。 说了这么多原理,下面该看看到底智能指针该怎么使用了。假设现在有一个类MyClass,如果要使用智能指针来引用这个类的对象,那么这个类需满足下列两个前提条件: (1) 这个类是基类RefBase的子类或间接子类; (2) 这个类必须定义虚构造函数,即它的构造函数需要这样定义: virtual ~MyClass(); 满足了上述条件的类就可以定义智能指针了,定义方法和普通指针类似。比如普通指针是这样定义: MyClass* p_obj; 智能指针是这样定义: sp<MyClass> p_obj; 注意不要定义成 sp<MyClass>* p_obj。初学者容易犯这种错误,这样实际上相当于定义了一个指针的指针。尽管在语法上没有问题,但是最好永远不要使用这样的定义。 定义了一个智能指针的变量,就可以象普通指针那样使用它,包括赋值、访问对象成员、作为函数的返回值、作为函数的参数等。比如: p_obj = new MyClass(); // 注意不要写成 p_obj = new sp<MyClass> sp<MyClass> p_obj2 = p_obj; p_obj->func(); p_obj = create_obj(); some_func(p_obj); 注意不要试图delete一个智能指针,即 delete p_obj。不要担心对象的销毁问题,智能指针的最大作用就是自动销毁不再使用的对象。不需要再使用一个对象后,直接将指针赋值为NULL即可: p_obj = NULL; 上面说的都是强指针,弱指针的定义方法和强指针类似,但是不能通过弱指针来访问对象的成员。下面是弱指针的示例: wp<MyClass> wp_obj = new MyClass(); p_obj = wp_obj.promote(); // 升级为强指针。不过这里要用.而不是->,真是有负其指针之名啊 wp_obj = NULL; 智能指针用起来是很方便,在一般情况下最好使用智能指针来代替普通指针。但是需要知道一个智能指针其实是一个对象,而不是一个真正的指针,因此其运行效率是远远比不上普通指针的。所以在对运行效率敏感的地方,最好还是不要使用智能指针为好。 |
|||
Android Service如何关闭Activity | |||
今天在编写Android程序的时候,遇到这个问题。通过百度和google的搜索结果,总结出以下方法: 方法一: public class mService extends Service { //保存在service中的Activity对象 private static mActivity m; //启动服务 static void startservice(Context c){ m=(mActivity)c; Intent iService=new Intent(c,mService.class); iService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); c.startService(iService); } //关闭服务 static void stopservice(Context c){ Intent iService=new Intent(c,mService.class); iService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); c.stopService(iService); } …… //在mService中关闭mActivity m.finish(); } public class mActivity extends Activity { // private MediaPlayer mMPlayer; /* * (non-Javadoc) * @see android.app.Activity#onCreate(android.os.Bundle) */ @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); …… //启动mService mService.startservice(mActivity.this); …… //停止mService mService.stopservice(mActivity.this); } } 方法二: 主要包括3部分 1. 定义application类,这个类可以保存获取activity实例,记得manifest中加入android:name=".MyApp" public class MyApp extends Application{ private MyServiceActivity myActivity; public void setInstance(MyServiceActivity instance){ myActivity = instance; } public MyServiceActivity getInstance(){ return myActivity; } } 2. 在activity中保存实例 public class MyServiceActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((MyApp)getApplication()).setInstance(this); …… } } 3. 在service中取回实例 public class MyService extends Service { MyServiceActivity myActivity; @Override public void onCreate() { super.onCreate(); setForeground(true); android.os.Debug.waitForDebugger(); myActivity = ((MyApp)getApplication()).getInstance(); …… } } -------------------------------------------------------------------------------- 我试过第一个方法,可行的。 但是当遇到AIDL的时候,恐怕就不能用这两种方法关闭Service启动的Activity了。 我试过很多办法都是失败的,比如我把Activity赋值给一个静态的变量,不管怎么传值,在AIDL调用的方法中该变量就是Null。 查看相关资料后,我的理解是AIDL不支持静态参数,也就说在AIDL中调用任何静态参数都是NULL。 这也仅仅是我的理解,可能有错误!希望高手及时指正! 当时我的需求是这样的,一个客户端要通过我的AIDL不断地发请求打开页面,因此我需要在Service中开启一个线程,当有新的请求发过来的时候我打开页面。之前写的程序要么就是打开一个页面之后死活都不动了,要么就是不停地开启一个新的Activity,然后按back键要退掉很多的Activity,而且关闭所有的Activity之后,Service打开的新Activity又是死活不变换了。 因此我需要Service打开新的页面的时候,把上一个Activity finish掉。尝试很多方法无果之后,只能用广播了,我用Service发一个广播,然后Activity接受到广播之后关闭。 但是这样又出来另外个问题,广播都是一个类继承BroadcastReceive,这个类怎么去关闭Activity呢? 因此只能把广播写在Acivity中了。代码如下: public class AnHTTPTool extends Activity { private WebView mWebView; private Context context; private String mTrafficID; private String mUrl; private String mAPN; private long mRas2Ftp; private long mIntervalTime; private int mTimes; private AnHTTPTool aht; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { //启动mService HTTPManagerService.startservice(AnHTTPTool.this); super.onCreate(savedInstanceState); setContentView(R.layout.browser); aht = this; mTrafficID = getIntent().getStringExtra("mTrafficID"); mUrl = getIntent().getStringExtra("mUrl"); mAPN = getIntent().getStringExtra("mAPN"); mRas2Ftp = getIntent().getLongExtra("mRas2Ftp", 0); mIntervalTime = getIntent().getLongExtra("mIntervalTime", 0); mTimes = getIntent().getIntExtra("mTimes", 1); mWebView = (WebView) findViewById(R.id.WebView01); mWebView.loadUrl(mUrl); WebSettings mWebSettings = mWebView.getSettings(); MyWebViewClient mWebViewClient = new MyWebViewClient(); mWebView.setWebViewClient(mWebViewClient); // 增加一个Intent过滤,添加Receiver监听service发送的广播 IntentFilter filter = new IntentFilter(); filter.addAction("Broadcast one!"); registerReceiver(mReceiver, filter); } protected BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub String action = intent.getAction(); if(action.equals("Broadcast one!")){ aht.finish(); //your code here } } }; } class MyWebViewClient extends WebViewClient { // 网页开始加载 @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // TODO Auto-generated method stub Log.d("H3c", "PageStarted"); super.onPageStarted(view, url, favicon); } // 网页结束加载 @Override public void onPageFinished(WebView view, String url) { // TODO Auto-generated method stub Log.d("H3c", "PageFinished"); super.onPageFinished(view, url); } // 在WebView中打开新的网页 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // TODO Auto-generated method stub view.loadUrl(url); return true; } } 这样在Service中用: Intent i = new Intent("Broadcast one!"); //发送广播 context.sendBroadcast(i); 就可以结束掉打开的Activity了! 在程序结束的时候记得注销广播: unregisterReceiver(mReceiver); |
|||
Android 关闭所有Activity完全退出程序方法 | |||
Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(startMain); System.exit(0); Android程序有很多Activity,比如说主窗口A,调用了子窗口B,在B中如何关闭整个Android应用程序呢? 这里给大家三种比较简单的方法实现。 首先要说明在B中直接使用finish(),接下来手机显示的还是主窗口A,所以一起来看看是如何实现的吧。 1. Dalvik VM的本地方法 android.os.Process.killProcess(android.os.Process.myPid()); 获取PID,目前获取自己的也只有该API,否则从/proc中自己的枚举其他进程吧,不过要说明的是,结束其他进程不一定有权限,不然就乱套了。 System.exit(0); 常规java、c#的标准退出法,返回值为0代表正常退出 2. 任务管理器方法 首先要说明该方法运行在Android 1.5 API Level为3以上才可以,同时需要权限android.permission.RESTART_PACKAGES,我们直接结束自己的package即可,直接使用ActivityManager类的restartPackage方法即可,参数为package name,该类通过getSystemService(Context.ACTIVITY_SERVICE)来实例化ActivityManager对象,这种方法系统提供的,但需要显示声明权限,所以使用中需要综合考虑。 3. 根据Activity的声明周期 我们知道Android的窗口类提供了历史栈,我们可以通过stack的原理来巧妙的实现,这里我们在A窗口打开B窗口时在Intent中直接加入标志Intent.FLAG_ACTIVITY_CLEAR_TOP,这样开启B时将会清除该进程空间的所有Activity。 在A窗口中使用下面的代码调用B窗口 Intent intent = new Intent(); intent.setClass(Android123.this, CWJ.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //注意本行的FLAG设置 startActivity(intent); 接下来在B窗口中需要退出时直接使用finish方法即可全部退出。 |