概述:本章主要针对对APP无基础的同学,对java不了解的同学,主要帮助大家理解APP的工作方式,给大家阅读APP源码提供一定的思路。大神可以直接跳过。APP的工作流程我们以较简单的EMOD来引导大家入门,理解起来相对直观,且EMOD包含Android常用的编写方法。通过本文的学习,以便于工作在Android底层而没有从事app开发工作的同学能够具备一定的思路和能力去阅读app代码。
首先我们来介绍一下我们这次介绍的APP的基本功能和使用方法。
基本功能:EMOD为工厂模式engineering mode的缩写,主要作用是工厂生产的过程中,使用EMOD对手机的基本功能进行验证。
使用方法:我们在拨号盘输入规定的号码即可打开APP,点击里面的对应选项做相应测试。
首先和大家陈述一点,拨号键盘打开EMOD使用的方法为广播,至于为什么能打开,以及他的实现,本章节不做描述。本章只描述打开EMOD第一个界面之后到获取sensor服务的地方。
首先我们输入自己设定的指令这里保密,例如“123456”进入工厂模式的如下界面,如图:图一。
图一:
点击图一中的“手动”按钮,来到图二所示
图二:
本文主要描述sensor的流程,我们选择一个较简单的sensor 光感距感二合一传感器,我们来看看他在工厂模式里的测试项“接近和光感测试”项,如图三所示
图三:
如图三所示:在图三的界面中,我们可以看到sensor数据的变化,以及数据变化后,界面的变化。
至此,我们借助如下的三个问题来学习EMOD APP是如何工作的
问题一:图一到图三,者三个界面上的控件是如何实现的
问题二:这三个界面是如何实现界面跳转的
问题三:第三个界面中,APP是如何获取到sensor的数据并在界面上实现更新的
注意:希望大家在理解完所有的知识点后再看代码,写得不够详细的知识点大家自己找度娘了!
首先补充两个基本知识点:
知识点一:
android 的四大组件
activity界面(我们每看到的一个界面,在AndroidAPP中即为一个activity)这里我们只关心activity
BroadcastReceiver广播接收器
Service 后台服务
ContentProvider 内容提供者
android apk 源码的目录功能
这里我们以eclipse软件打开的Android apk源码来介绍一下源码的目录结构,在Android源码中此目录也是大同小异的。
这里只介绍我们看代码经常要看的地方
知识点二:
activity的生命周期
void onCreate();第一次创建Activity时被调用
void onStart(); 启动Activity时被调用
void onRestart(); 重新启动Activity时被调用
void onResume(); 可以进行用户交互时调用
void onPause(); 暂停Activity被调用,可以看到页面,获取不到焦点
void onStop(); 停止Activity时被调用,看不到页面,获取不到焦点
void onDestroy(); 销毁Activity时被调用
知识点三:
在activity中我们所看到的按钮,文字,图片,视频等均为控件,在Android app中这些控件所对应的类名为:
按钮 button
文字 textview
图片 imageview
等等...
每个类中都封装好了他们私有的一些方法,如:
textview.setText 设置文字的显示内容
button.setOnClickListener(listener); 设置按钮按下后的处理方法为listener(这里为了便于底层的同学理解,换个说法,这里就像设置中断处理函数一样,把按键按下理解为中断就可以了),listener为OnClickListener类型,我们只要重写里面的onClick方法即可,onClick方法里面就是我们的处理函数的实体了。
知识点四:
我们在如何activity中确定这个activity所使用的xml布局文件是哪一个
设置布局的统一方法为:this.setContentView(R.layout.start);
知识点五:
定义控件在activity对应xml文件中定义,在xml文件中我们可以设置当前activity的布局格式,控件所处的位置,大小,颜色及控件中的各种内容。
这里注意一点,我们要在xml文件中设置各个控件对应的id,设置好id后,会自动在R.id下生成控件对应的id号(不需要我们手动设置)。
例:我们看图一对应的xml文件start.xml
<Button android:id="@+id/manual" android:layout_width="120dip" android:layout_height="wrap_content" /> <Button android:id="@+id/auto" android:layout_width="120dip" android:layout_height="wrap_content" />
start.xml设置了button的id,长和宽。
知识点六:
我们在xml文件中定义控件,在java文件中使用控件,获取控件普遍使用的方法为findViewById(R.id.控件id名);
例:findViewById(R.id.auto);
知识点七:
字符串通常定义在string.xml文件中,可以使用R.string.字符串名来使用字符串
例:string.xml的一段定义
<string name="select_mode">"选择测试模式"</string> <string name="btn_manual">"手动"</string> <string name="btn_auto">"自动"</string> <string name="btn_factory_reset">"恢复出厂设置"</string>
使用时:manual.setText(R.string.btn_manual);
R.string.btn_manual得到的字符串就是“手动”,这里有点类似c语言的宏定义,希望能帮助大家理解。
来看问题一(问题二的答案贯穿其中):我们先从图一的界面的代码开始分析
代码块一:
package com.wind.emode; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class startActivity extends Activity {//一个实现界面的类必须是Activity的子类 Button manual = null; Button auto = null; Button result = null; Button factory_reset = null; TextView title = null; int menuIndex = -1; int autodetect = 0; Intent intent = null; protected void onCreate(Bundle savedInstanceState) {//startActivity的入口方法onCreate super.onCreate(savedInstanceState); this.setContentView(R.layout.start);//设置布局文件为start.xml manual = (Button) findViewById(R.id.manual); auto = (Button) findViewById(R.id.auto); result = (Button) findViewById(R.id.result); factory_reset = (Button) findViewById(R.id.factory_reset); //if(!getResources().getBoolean(R.bool.config_project_t621w)){ factory_reset.setVisibility(View.GONE); //} title = (TextView) findViewById(R.id.start_title);//获取标题控件 title.setText(R.string.select_mode);//修改标题控件内容<string name="select_mode">"选择测试模式"</string> intent = new Intent(); manual.setText(R.string.btn_manual);//设置按键manual内容为 <string name="btn_manual">"手动"</string> result.setText(R.string.btn_result);//设置按键result内容为 <string name="btn_result">"查看测试结果"</string> OnClickListener listener = new OnClickListener() {//初始化监听按键事件的对象listener public void onClick(View arg0) {//按键事件服务函数onClick menuIndex = 4; intent.setComponent(new ComponentName("com.wind.emode", "com.wind.emode.emodeSubmenu"));//设置要跳转的组件包名"emodeSubmenu" intent.putExtra("menu", menuIndex); //填充intent内容 startActivity(intent);//开始跳转 } }; manual.setOnClickListener(listener);//设置按键"手动"事件处理内容 result.setOnClickListener(listener);//设置按键"查看测试结果"事件处理内容 auto.setText(R.string.btn_auto);//设置按键auto内容为 <string name="btn_auto">"自动"</string> auto.setOnClickListener(new OnClickListener() {//设置监听"自动"按键 public void onClick(View arg0) { autodetect = 1; // auto test flag 15s close intent.setComponent(new ComponentName("com.wind.emode",//设置要跳转的组件包名"AutoDetect" "com.wind.emode.AutoDetect")); intent.putExtra("auto", autodetect);//填充intent内容 startActivity(intent);//开始跳转 } }); factory_reset.setText(R.string.btn_factory_reset);//设置按键factory_reset内容 <string name="btn_factory_reset">"恢复出厂设置"</string> factory_reset.setOnClickListener(new OnClickListener() {//设置监听"自动"按键 public void onClick(View arg0) { intent.setComponent(new ComponentName("com.android.settings","com.android.settings.Settings$PrivacySettingsActivity"));//设置要跳转的组件包名"PrivacySettingsActivity" intent.putExtra("emode_dialer", "emode_dialer");//填充intent内容 startActivity(intent);//开始跳转 } }); } }总结:
根据知识点二activity的生命周期,我们知道,当加载界面的时候会首先调用到onCreate();方法,在onCreate()方法中,做了如下几件事情。
1.设置当前activity所对应的布局文件
2.从xml文件中获取到了各个button,及textView控件,并实例化了他们对应的对象。
3.设置了这些控件所显示的内容
4.设置了几个button被按下后所处理的内容
到此,我们最好奇的地方应该是button按下后,做了什么?是怎么实现界面跳转的。
我们就按下“手动”按键后,怎么跳转到图二所示界面进行一下分析
补充知识点:
知识点八:
Android中提供了Intent机制来协助应用间的交互与通讯,Intent不仅可用于应用程序之间,也可用于应用程序内部的activity, service和broadcast receiver之间的交互。
Intent本意为意图,Intent其实是比较大的一个知识点,这里不好说得很全面,大家可以初略的理解为,我们可以用Intent来实现两个activity或者service或者broadcast receiver之间通讯(知识点一有相关介绍)。
例:代码块一中“手动”button所对应的监听处理方法中
OnClickListener listener = new OnClickListener() {//初始化监听按键事件的对象listener public void onClick(View arg0) {//按键事件服务函数onClick menuIndex = 4;//注意:发送的内容为4 intent.setComponent(new ComponentName("com.wind.emode",//设置要跳转的组件包名"com.wind.emode" "com.wind.emode.emodeSubmenu"));//设置接收的类名 intent.putExtra("menu", menuIndex);//填充intent内容 startActivity(intent);//开始跳转 } };这里填充了需要跳转的包名,以及需要向这个包传递的内容,并开始了跳转。
注意:填充Intent的时候使用key:name的方式填充,接收端可以通过key(这里是“menu”)获取到name(这里是“menuIndex”也就是4)。
总结:监听处理方法代码中我们得知,当我们按下“手动”button的时候,页面进行了跳转。
到此,图一所示的界面源码我们分析完了,很直观,只是调用了几个button控件,和一个textView控件,代码里只是设置了这几个控件里显示的内容,并设置了几个按键的监听内容,以及界面跳转方向。
下面我们来分析,图二所示的界面做了哪些工作。
明确阅读思路:我们既然指定了要跳转的包名和接收的类名,我们就可以找到这个类,看这个类做了什么工作。
提醒:第一个界面向第二个界面发送的内容只是一个数字4!
看代码块二前必须知道以下几个知识点:
知识点八:
MOV列表设计ListView:(这里也是一个比较复杂的知识点,不能说得面面俱到)
我们注意到图二是一个列表设计,要实现列表设计就要使用到ListView,我们在这里可以这么理解ListView:
在介绍ListView前,我们可以看看我们手机的通讯录,通讯录就是一个列表设计,他每一行(每一个item)包括联系人的头像(图片),联系人的姓名(字符串),电话号码(字符串)或者还有更多信息,但是我们可以发现,每一行都有他们各自的内容,他们排列的方式也就是布局都是按一定规律排列的,没错,这都是需要我们来填充的,这就需要使用到adapter了。
下面是三种主要的adapter
ArrayAdapter 只能显示文本,每个"表格"里只能显示一行文字
SimpleAdapter 可以在每个"表格"里面随意安排布局,内容不受限制,使用规定的适配器方法
BaseAdapter 自定义适配器(重要),可以在每个"表格"里面随意安排布局,内容不受限制,可以自定义适配器方法
这里介绍一下EMOD用到的ArrayAdapter的填充方法
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects);
context:传入当前所处上下文,即当前所处类(一般为:所处类.this)
resource:传入表格里的要设置的布局文件索引,(一般为:R.layout.布局文件名)
textViewResourceId:传入表格里的要设置的文字格式索引,(一般为:R.id.文字布局id名)
objects:传入要显示的数据集合(继承List所以是集合),<T(自定义泛型)>
我们只要实例化我们需要的adapter,再填充到listView中,一个列表就创建成功了。具体的实现大家可以看代码块二。
知识点九:
BroadcastReceiver 广播的使用:
广播就像我们听的收音机一样,一个发送一个接收,他们的发送频道和接收频道要一致,这里的“频道”是为了帮助大家理解,书面语叫action。
发送端:
1.填充发送内容:intent.putExtra("key", "乾隆出来接客了");
2.设置广播频率:intent.setAction("com.example.boradrecv.MainActivity");(与接收端一致)
或:new intent("com.example.boradrecv.MainActivity");(intent有重载构造器)
3.发送intent:sendBroadcast(intent);
接收:
一.静态注册
1.创建一个类继承BroadcastReceiver,其中onReceive方法,会在接收广播的时候调用
2.在xml文件中注册一个广播接收器,新建一个于activity同级标签receiver,属性为刚刚创建的.java文件
在里面再创建一个intent-filter标签,并创建action,属性为广播的频道(可任意写)。
<receiver android:name="com.example.boradrecv.newborad">
<intent-filter >
<action android:name="com.example.boradrecv.MainActivity"/>
</intent-filter>
</receiver>
3.接收intent.getAction();
二.动态注册
1.创建一个类继承BroadcastReceiver的对象(MyReceicer),其中onReceive方法,会在接收广播的时候调用
2.使用public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
注册一个广播接收服务对象receiver的广播频道为filter
MyReceicer receiver= new MyReceicer();
IntentFilter filter= new IntentFilter("频道字符串");
registerReceiver(receiver, filter);
用到的xml文件
<string-array name="config_submenu_board_name" translatable="false"> <item>"电池信息"</item> <item>"显示屏测试"</item> <item>"背光测试"</item> <item>"震动测试"</item> <item>"铃音与SD卡测试"</item> <item>"内存与内部SD卡测试"</item> <item>"按键测试"</item> <item>"蓝牙测试"</item> <item>"GPS测试"</item> <item>"触摸屏测试"</item> <item>"音频回路测试"</item> <item>"FM测试"</item> <item>"传感器测试"</item> <item>"WLAN测试"</item> <item>"后摄像头测试"</item> <item>"前摄像头测试"</item> <item>"闪光灯测试"</item> <item>"指纹测试"</item> <!--<item>"霍尔传感器测试"</item>--> </string-array>大家可以看到这里设置了列表中每一个item的显示内容。
<string-array name="config_submenu_board_index"> <item>A983A25B</item> <item>A983A31B</item> <item>A983A26B</item> <item>A983A86B</item> <item>A983A234B</item> <item>A983A636B</item> <item>A983A07A02B</item> <item>AB09AAAB</item> <item>A983A477B</item> <item>AB19B</item> <item>A983A21B</item> <item>A983A36B</item> <item>A1988B</item> <item>A983A9434622B</item> <item>A983A473B</item> <item>A983A679B</item> <item>A983A224B</item> <item>A983A666B</item> <!--<item>A983A6479B</item>--> </string-array>
以上是每一个item对应的编码代码块二中有使用这个编码去发送广播,上面两个array其实是对应的,比如我们按下“电池信息”发送的编码就是“A983A25B”。
代码块二:
package com.wind.emode; //包名为com.wind.emode与代码块一中设置的跳转包名一致 import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.widget.TextView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.provider.Settings; import android.content.ContentResolver; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.AdapterView; import android.util.Config; import android.util.Log; public class emodeSubmenu extends Activity {//emodeSubmenu这个类继承了Activity,所以他也是一个界面 Intent intent; int back = 0; MyAdapter adapter; //private class MyAdapter extends BaseAdapter(BaseAdapter MOV列表设计) private ListView mlistview; private TextView CTitle; // *983*1# // phone test private static String[] array_all; // *983*0# // board test private static String[] array_board; // *983*7# private static String[] array_other; private static String[] Index_all; //*983*0# //board test private static String[] Index_board; private static String[] Index_other; private int[] completed = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; private String strCompleted; private Bitmap failBm; private LayoutInflater inflater; private Bitmap okBm; private String text_result; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); inflater = LayoutInflater.from(this); Resources res = getResources(); text_result = res.getString(R.string.submenu_text_result); okBm = BitmapFactory.decodeResource(res, R.drawable.ic_right); failBm = BitmapFactory.decodeResource(res, R.drawable.ic_wrong); strCompleted = res.getString(R.string.tip_completed); Index_board = res.getStringArray(R.array.config_submenu_board_index);//获取相应的编码 Index_all = res.getStringArray(R.array.config_submenu_all_index); Index_other = res.getStringArray(R.array.config_other_index); array_board = res.getStringArray(R.array.config_submenu_board_name);//获取列表内容数组 array_all = res.getStringArray(R.array.config_submenu_all_name); array_other = res.getStringArray(R.array.config_other_name); setContentView(R.layout.em_menu); mlistview = (ListView) findViewById(R.id.ListView);//获取ListView的布局,这里只设置了在每一行里的布局格式,并没有填充实际的内容 CTitle = (TextView) findViewById(R.id.ctitile); intent = new Intent(); int menuId = getIntent().getIntExtra("menu", -1);//读取startActivity传来的值(失败return -1) 这里获取到4 switch (menuId) { case 0: //CTitle.setText(strCompleted + "0/" + Index_all.length); adapter = new MyAdapter(this, R.layout.em_menu_item, R.id.txt, array_all, Index_all); mlistview.setAdapter(adapter); mlistview.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { Intent intent = new Intent("android.intent.action.emode_menu"); intent.putExtra("com.zte.smartdialer.serial", Index_all[arg2]); sendBroadcast(intent); // completed[arg2] = 1; // final int n = sum(completed, Index_all.length); // CTitle.setText(strCompleted + n + "/" + Index_all.length); } }); break; case 1: CTitle.setVisibility(View.GONE); adapter = new MyAdapter(this, R.layout.em_menu_item, R.id.txt, array_other, Index_other); mlistview.setAdapter(adapter); mlistview.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { Intent intent = new Intent("android.intent.action.emode_menu"); intent.putExtra("com.zte.smartdialer.serial", Index_other[arg2]); sendBroadcast(intent); } }); break; case 4: //CTitle.setText(strCompleted + "0/" + Index_board.length); adapter = new MyAdapter(this, R.layout.em_menu_item, R.id.txt, array_board, Index_board); //这里实现一个继承了ArrayAdapter的子类MyAdapter对象将上面获取到的array_board(列表要显示的内容),Index_board(内容对应的拨号号码) mlistview.setAdapter(adapter);//填充列表,这样在界就可以按我们设置的格式和内容显示一个列表了 mlistview.setOnItemClickListener(new AdapterView.OnItemClickListener() {//设置列表监听 public void onItemClick(AdapterView<?> arg0, View arg1,//注意:在我们点击某一个item时,回调onItemClick时自动传入arg2,这个参数为被点击的item的标号 int arg2, long arg3) { Intent intent = new Intent("android.intent.action.emode_menu");//设置频道action(接收端与此一致) intent.putExtra("com.zte.smartdialer.serial", Index_board[arg2]);//填充key为"com.zte.smartdialer.serial",填充广播内容为被点击位置对应的编码 sendBroadcast(intent);//发送广播 // completed[arg2] = 1; // final int n = sum(completed, Index_board.length); // CTitle.setText(strCompleted + n + "/" + Index_board.length); } }); break; default: finish(); break; } } protected void onResume() { adapter.onResume(); super.onResume(); } public class MyAdapter extends ArrayAdapter<String> {//定义一个继承ArrayAdapter的子类MyAdapter int[] indexs = null; String[] labels = null; int[] results = null; public MyAdapter(Context context, int resId, int txtResId, String[] arr, String[] ixs) {//MyAdapter的构造器 super(context, resId, txtResId, arr);//在MyAdapter的构造器中调用ArrayAdapter的构造器,并传参,讲前面初始化好的每行显示内容数组arr传入 labels = arr; int len = labels.length; results = new int[len]; indexs = new int[len]; for (int i = 0; i < len; i++) { results[i] = 0; indexs[i] = EMODE_ARRAY.valueOf(ixs[i]).ordinal(); } } private void sum() { int notests = 0; int succs = 0; int fails = 0; int len = results.length; int n = 0; for (int i = 0; i < len; i ++) { if (results[i] == 1){ succs ++; }else if (results[i] == 2){ fails ++; }else{ notests ++; } } CTitle.setText(String.format(text_result, succs, fails, notests)); } public View getView(int pos, View v, ViewGroup parent) { if (v == null){ v = inflater.inflate(R.layout.em_menu_item, null); } ((TextView) v.findViewById(R.id.txt)).setText(labels[pos]); ImageView img = (ImageView) v.findViewById(R.id.img); if (results[pos] == 1){ img.setImageBitmap(okBm);//显示pass图片 }else if (results[pos] == 2){ img.setImageBitmap(failBm);//显示失败图片 }else{ img.setImageBitmap(null);//默认不显示 } return v; } public void onResume() { ContentResolver cr = getContentResolver(); String org_results = Settings.System.getString(cr, MatchingReceiver.Is_Telephone ? "emode_pcba_results" : "emode_results"); if(org_results != null){ String[] pairs = org_results.split(","); String[] tp; //SharedPreferences sp = getSharedPreferences("results", Context.MODE_PRIVATE); int len = results.length; int s = pairs.length; for (int i = 0; i < len; i++) { for(int j = 0; j < s; j ++){ tp = pairs[j].split("="); if(indexs[i] == Integer.parseInt(tp[0])){ results[i] = Integer.parseInt(tp[1]); } } } notifyDataSetChanged(); } sum(); } } private int sum(int[] a, int l) { int s = 0; for (int i = 0; i < l; i++) { s += a[i]; } return s; } }总结:
1.通过界面一里设置的跳转包名,我们可以找到代码块二,切确定代码块二就是界面一的跳转位置。
2.在83行,第二个界面的源码中通过获取界面一传来的值4。
3.通过一个switch case语句判断获取到的值,来到case 4的代码段。
4.case 4中实现了一个ArrayAdapter的子类MyarrayAdapter,并填充了里面的格式和内容,并显示在界面上。
5.case 4中还设置了listview监听,在监听方法里使用了广播,频道action为"android.intent.action.emode_menu",发送内容为被按下的item对应的编码
至此,界面二的源码我们也阅读完了,我们现在关心的是界面二是怎么跳转到第三个界面的。
明确阅读思路:我们知道一个信息,广播是有接收端和发送端的,代码块二的广播实现了发送端,我们现在要通过接收端的实现方式和频道号action来找到接收端。
通过action "android.intent.action.emode_menu"我们找到了接收端,见代码块三。
代码块三(代码太长,有省略):
package com.wind.emode; import java.lang.reflect.Array; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.util.Log; import android.provider.Settings; import android.widget.Toast; import android.os.FileUtils; import android.os.SystemProperties; import android.os.Bundle; import android.os.PowerManager; import android.util.Config; import android.net.Uri; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.File; import java.io.FileOutputStream; import android.content.pm.PackageManager; import android.content.res.Resources; import java.io.InputStream; // A=* B=# enum EMODE_ARRAY { A983A0B, A983A3640A01B, A983A25B, A983A31B, A983A26B, A983A86B, A983A07A02B, A983A246B, A983A27274B, AB09AAAB, A983A1275B, A983A7B, . . . //省略 UNKNOWN } public class MatchingReceiver extends BroadcastReceiver{ int menuIndex = -1; int autodetect = 0; Intent intent = new Intent(); static boolean Is_Telephone = false; //modify sim lock private boolean isSimlock=false; @Override public void onReceive(Context context, Intent intent) {//广播接收端入口 Log.e("Emode_Receiver", "*********OnReceive action=" + intent.getAction()); String Emodenumber; if(com.android.internal.telephony.TelephonyIntents.SECRET_CODE_ACTION.equals(intent.getAction())){ Uri uri = intent.getData(); Intent inte = new Intent(); inte.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Uri.parse("android_secret_code://9463*1").equals(uri)) { inte.setComponent(new ComponentName("com.wind.emode", "com.wind.emode.TSActivity")); Is_Telephone = false; }else if(Uri.parse("android_secret_code://9463*7").equals(uri)){ inte.setComponent(new ComponentName("com.wind.emode", "com.wind.emode.DevInfoVersion"));// old value => DevInfo } context.startActivity(inte); return; } if (intent.getAction().equals("android.intent.action.emdoe"))//判断action是否一致 { Emodenumber = intent.getStringExtra("com.zte.smartdialer.input"); EMODE_ARRAY EmodeCode = EMODE_ARRAY.UNKNOWN; try{ EmodeCode = EMODE_ARRAY.valueOf(Emodenumber.replace('*', 'A').replace('#', 'B')); }catch(IllegalArgumentException e){ } //modify sim lock Bundle bundle=intent.getExtras(); if(bundle!=null){ isSimlock=bundle.getBoolean("isSimlock",false); } System.out.println("pzp onReceive isSimlock="+isSimlock); Log.e("Emode_Receiver", "*******emode code=" + EmodeCode); if (EmodeCode != EMODE_ARRAY.UNKNOWN) { func(context, EmodeCode); } } if (intent.getAction().equals("android.intent.action.emode_menu")) { String serial_number = intent.getStringExtra( "com.zte.smartdialer.serial");//获取广播内容 Log.e("Emode_Receiver", "*********menu number=" + serial_number); func(context, EMODE_ARRAY.valueOf(serial_number));//对应拨号码的处理函数 这里我们关心 "接近和光感测试" 对应拨号码 A983A239B,往下看func方法 } if (intent.getAction().equals("android.intent.action.emode_autodetect")) { autodetect = intent.getIntExtra("com.zte.smartdialer.autoflag", -1); Log.e("Emode_Receiver", "******auto detect flag=" + autodetect); String serial_number = intent.getStringExtra( "com.zte.smartdialer.serialauto"); Log.e("Emode_Receiver", "*********autodetect number=" + serial_number); func(context, EMODE_ARRAY.valueOf(serial_number)); } if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { Log.v("Emode_Receiver", "ACTION_BOOT_COMPLETED"); //setRootFlag(checkSystemRootOrNot()); if(context.getResources().getBoolean(R.bool.needCali)){ SensorCaliUtil.writeCaliValue(); } } } private boolean checkSystemRootOrNot() { File su = new File("/system/xbin/su"); boolean suExists = false; boolean superUserExists = false; if (su != null && su.exists()) { suExists = true; } File superUserApk = new File("/system/app/Superuser.apk"); if (superUserApk != null && superUserApk.exists()) { superUserExists = true; } boolean testKeys = SystemProperties.get("ro.build.tags").equals("test-keys"); boolean notSecure = SystemProperties.get("ro.secure").equals("0"); Log.v("Emode_Receiver", "checkSystemRootOrNot: " + suExists + ":" + superUserExists + ":" + testKeys + ":" + notSecure); return suExists || superUserExists || testKeys || notSecure; } private void setRootFlag(boolean rootOrNot) { final int startIndex = 205; final int valueIndex = startIndex + 5; final String flagStr = "ROOT: END"; byte[] buff = new byte[256]; try { FileInputStream is = new FileInputStream("/dev/pro_info"); FileOutputStream os = new FileOutputStream("/dev/pro_info"); int count = is.read(buff); if(count > startIndex){ System.arraycopy(flagStr.getBytes(), 0, buff, startIndex, flagStr.length()); if (rootOrNot) { buff[valueIndex] = 'Y'; } else { buff[valueIndex] = 'N'; } os.write(buff); } is.close(); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void func(Context context, EMODE_ARRAY code) {//处理广播发送来的编码 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("index_", code.ordinal()); Log.d("*************", "=====>" + code); switch (code) { case A983A0B: // *983*0#------------------------------------------------menu intent.setComponent(new ComponentName("com.wind.emode", "com.wind.emode.startActivity")); Is_Telephone = true; context.startActivity(intent); break; . . . //省略 case A983A239B://*983*239#//我们关心的psensor的case /*if(true) { intent.setComponent(new ComponentName("com.mediatek.engineermode", "com.mediatek.engineermode.sensor.PSensorCalibration")); //intent.putExtra("auto", 1); context.startActivity(intent); } else {*/ intent.setComponent(new ComponentName("com.wind.emode",//设置要跳转的组件包名"com.wind.emode" "com.wind.emode.LightAndProximitySensorTest"));//设置接收类名"LightAndProximitySensorTest" intent.putExtra("auto", 1);//设置发送内容 context.startActivity(intent);//开始跳转 //} break; . . . //省略 default: break; } } //by qinjian add for Z280 auto test tp 20160613 start private String getTPString(){ String tp_path = "/sys/devices/platform/HardwareInfo/01_ctp"; String tpType = null; try { byte[] bytes = new byte[256]; InputStream is = new FileInputStream(tp_path); int count = is.read(bytes); is.close(); tpType = new String(bytes, 0, count); tpType = tpType.substring(tpType.indexOf(':') + 1); } catch (Exception e) { e.printStackTrace(); } return tpType.toLowerCase(); } //by qinjian add for Z280 auto test tp 20160613 end }
总结:
1.通过广播的实现方法,我们可以确定代码块三就是界面二的中广播的接收端。
2.通过获取广播传来的内容,通过switch case,我们来到case A983A239B。
3.在这个switch case中实现了页面的跳转,这里与界面一是一样的,我们依旧关心他跳转的类名"LightAndProximitySensorTest"
至此:代码块三分析完毕,在有分析代码块一的基础上,这显得很轻松了,问题一和问题二我们现在应该在心里都有了答案。现在我们来看问题三!见代码块四。
明确阅读思路:这里很明显了,肯定是去看类"LightAndProximitySensorTest"。
知识点十:
service是什么?
服务:服务是一个运行在后台的应用程序代表其他应用程序执行一个长时间操作的行为,虽然不与用户交互但供应功能供其它应用程序使用。
如何获取sensor服务:
1.创建一个类继承SensorEventListener
2.通过getSystemService获取到与SENSOR_SERVICE同名的服务对象。
3.通过我们刚刚获取到的服务,得到对应的对象,因为sensor有很多,你到底要用哪一个呢!
例:getDefaultSensor(Sensor.TYPE_LIGHT); //获取光感sensor对象
4.注册对sensor的监听。
例:registerListener(this, mSensor2, SensorManager.SENSOR_DELAY_NORMAL);
5.当sensor数据变化的时候调用SensorEventListener中的虚拟方法onSensorChanged,我们只需要在onSensorChanged方法中做我们想做的事情就可以了。
代码块四:
package com.wind.emode; import android.graphics.Color; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.widget.TextView; import android.widget.Button; import android.util.Log; import android.view.View; import android.view.Window; //mark off common SensorTest,because phone hasn't light and proxy sensor. public class LightAndProximitySensorTest extends EmActivity implements SensorEventListener {//注意:LightAndProximitySensorTest实现了SensorEventListener这个类(来自于framework层API),当sensor数据变化的时候,onSensorChanged方法被调用 TextView mtext7; TextView mtext8; TextView mGsensorResult; TextView mCompassResult; TextView mLightResult; TextView mProximityResult; TextView mLightCaliResult; TextView mProximityCaliResult; Button mbutton; Button mLightCali; Button mProximityCali; SensorCaliUtil mCaliUtil; //[email protected] 20141015 begin int mTempLightValue; int mTempProximityValue; //[email protected] 20141015 end SensorManager sm; Sensor mSensor2; Sensor mSensor3; private boolean mFlag; private int mLightUploadTime; private int mProximityUploadTime; protected static final String LOG_TAG = "EMODE_SensorTestNoClib"; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.em_sensor_light_prox); btnSucc.setEnabled(false); mCaliUtil = new SensorCaliUtil(); if(getResources().getBoolean(R.bool.needCali)){ findViewById(R.id.container_light_cali).setVisibility(View.VISIBLE); findViewById(R.id.container_proximity_cali).setVisibility(View.VISIBLE); } sm = (SensorManager) getSystemService(SENSOR_SERVICE);//获取sensor服务SensorManager mSensor2 = sm.getDefaultSensor(Sensor.TYPE_LIGHT);//获取光感sensor对象 mSensor3 = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);//获取距感sensor对象 mtext7 = (TextView) findViewById(R.id.m0_t7); mtext8 = (TextView) findViewById(R.id.m0_t8); mtext8.setText(getString(R.string.proximity) + " X : ");//显示控件内容为 <string name="light">"接近传感器"</string> mtext7.setText(getString(R.string.light) + " X: "); //显示控件内容为 <string name="light">"光传感器"</string> mLightResult = (TextView) findViewById(R.id.light_result); mProximityResult = (TextView) findViewById(R.id.proximity_result); mProximityResult.setText(getString(R.string.proximity) + getString(R.string.test_fail));//显示控件内容为"接近传感器测试失败" mProximityResult.setTextColor(Color.RED);//设置距感默认字体颜色为红色 mLightResult.setText(getString(R.string.light) + getString(R.string.test_fail));//显示控件内容为"光传感器器测试失败" mLightResult.setTextColor(Color.RED);//设置光感默认字体颜色为红色 mLightCaliResult = (TextView) findViewById(R.id.light_cali_result); mProximityCaliResult = (TextView) findViewById(R.id.proximity_cali_result); mLightCali = (Button) findViewById(R.id.btn_light_cali); mProximityCali = (Button) findViewById(R.id.btn_proximity_cali); Button.OnClickListener listen = new Button.OnClickListener() { public void onClick(View v) { mLightCali.setEnabled(false); mProximityCali.setEnabled(false); switch (v.getId()) { case R.id.btn_light_cali: { mCaliUtil.lightCali(mLightCaliResult, mLightCali, mProximityCali); return; } case R.id.btn_proximity_cali: { mCaliUtil.proximityCali(mProximityCaliResult, mLightCali, mProximityCali); return; } default: break; } } }; mLightCali.setOnClickListener(listen); mProximityCali.setOnClickListener(listen); mbutton = (Button) findViewById(R.id.m0_b); mbutton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { switch (v.getId()) { case R.id.m0_b: { finish(); return; } default: break; } } }); mbutton.setText("OK"); // if (EMflag == AutoDetect) { // EMtimer.schedule(EMtask, 15000); // } } public void onSensorChanged(SensorEvent event) {//当sensor数据变化的时候,onSensorChanged方法被调用,实现数据的显示和判断计数 float[] values = event.values; Sensor sensor = event.sensor; synchronized (this) { if (sensor.getType() == Sensor.TYPE_LIGHT) { mtext7.setText(getString(R.string.light) + " X: " + values[0]); //[email protected] 20141015 begin if(mTempLightValue != (int)values[0]){ mTempLightValue = (int)values[0]; mLightUploadTime ++; } //[email protected] 20141015 end if(mLightUploadTime >= 6){ //光感数据变化6次后修改界面 mLightResult.setText(getString(R.string.light) + getString(R.string.test_pass)); mLightResult.setTextColor(Color.GREEN); } } if (sensor.getType() == Sensor.TYPE_PROXIMITY) { mtext8.setText(getString(R.string.proximity) + " X: " + values[0]); //[email protected] 20141015 begin boolean validate = ((int)values[0] == 0 || (int)values[0] == 1); if(validate && mTempProximityValue != (int)values[0]){ mTempProximityValue = (int)values[0]; mProximityUploadTime ++; } //[email protected] 20141015 end if(mProximityUploadTime >= 6){//距感数据变化6次后修改界面 mProximityResult.setText(getString(R.string.proximity) + getString(R.string.test_pass)); mProximityResult.setTextColor(Color.GREEN); } } if (mLightResult.getCurrentTextColor() == Color.GREEN && mProximityResult.getCurrentTextColor() == Color.GREEN) { btnSucc.setEnabled(true); } } } @Override protected void onResume() { super.onResume(); sm.registerListener(this, mSensor2, SensorManager.SENSOR_DELAY_NORMAL);//设置监听光感和距感,在有事件的时候调用onSensorChanged方法(监听精度200ms) sm.registerListener(this, mSensor3, SensorManager.SENSOR_DELAY_NORMAL); } @Override protected void onStop() { sm.unregisterListener(this);//注销sensormanager super.onStop(); } // please do not del!!! // @Override public void onAccuracyChanged(Sensor arg0, int arg1) { } }总结 :
1.通过代码块四的包名和类名,我们可以确定代码块四是代码块三的跳转位置。
2.代码块四获取了一些button控件和textView,设置了他们的默认显示内容,颜色,呈现在我们眼前的就是图三所示的界面。
3.获取了sensor服务
4.从sensor服务中获取要使用的sensor对象
5.设置了对sensor监听
6.填充了onSensorChanged方法,并在onSensorChanged中对获取到的sensor数据做相应处理,并显示在界面上
7.在生命周期onStop里,注销sensor(注意:在apk不使用sensor的时候要关闭掉sensor,以免产生不必要的功耗)!
至此:代码块四阅读完毕,我们得到了问题三的答案。
通过本文的学习,我们大致的理解了EMOD中光感距感传感器这样一个小模块的实现流程,我们发现获取sensor的数据是通过服务来实现的,注意了,这里到了我们第一个交界处,即apk与服务的交界处,下一章节《Android 6.0 sensor 框架详解 (framework层) (一)》,我们根据知识点十解决如下几个问题。
1.getSystemService(SENSOR_SERVICE)为什么可以获取到sensor服务
2.getDefaultSensor(Sensor.TYPE_LIGHT)为什能得到我们想要的sensor对象
3.注册对sensor的监听的作用是什么
4.问什么在sensor数据变化时会回调onSensorChanged
感谢大家的阅读,希望大家能有所收获,请指出不足与错误!