Contacts从系统应用移植为普通应用
1. 遇到的第一个问题就是隐藏api问题。
Google之所以要将一些API隐藏(指加上@hide标记的public类、方法或常量)是有原因的。其中很大的原因就是Android系统本身还在不断的进化发展中。从1.0、1.1到现在即将问世的Android 2.3.4。 这些隐藏的API本身可能是不稳定的,所以,使用隐藏API,意味着程序更差的兼容性。所以能不使用隐藏API最好还是不要使用隐藏的API。不过有时为了实现Android应用某些特殊的功能或者效果,隐藏的API往往能发挥意想不到的作用。
从网上查到使用Android系统隐藏api的方法(放弃了使用java的反射机制,工作量太大,太繁琐了):
使用Android系统隐藏api的前提,我们需要得到Android系统源码编译输出的一个文件:out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\classes.jar
这里我使用的是Android2.3的源码编译生成的文件,这个包里面包含所有的系统api, 隐藏的, 公开的。
添加jar:工程上右键功能菜单->Build Path->Configure Build Path...->Libraries选项卡。
a.这时应该有一个列表, 如果你没有添加过, 应该只有一项, 就是系统自带的Android SDK, 选中并删除系统添加的sdk;
b.点Add Library -> User Library 选择User Library 按钮, 新建一个User Library 将刚才那个文件 classes.jar 和系统本身的文件都导入进来;
c.调整下顺序, 将 classes.jar 调到前面,这样添加了之后, 就可以使用系统隐藏的api了。
使用隐藏api, 有个前提:
许多api涉及到系统权限问题, 比如后台安装文件要求有安装程序的权限, 而这个安装程序权限不是随便有的, 只有经ROM签名认证的才可以使用这个权限. 虽然说可以在配置文件里面添加这个权限, 但是悲剧的是你仍然不能拥有这个权限, 在这点上, Google做的真绝..
我的理解是经ROM签名认证就会和特定定制的系统有关了,所以没有尝试。
移植过程会有很多问题,在此一一记录。
2.通话记录。
a.当进入通话记录时,会取消状态栏上的未接来电的通知。在2.2上是没有问题的,但是在2.3上就会由于没有权限而抛出异常,就是说在2.3上普通应用没有取消未接来电通知的权限了,xml中做了配置也没用。2.3加强权限控制,真没办法。连云助手在2.3上也会抛出异常,而来电通无耻的在这里捕获所有异常。。操作失败,但是不会抛异常。
既然系统的“通话记录”有取消未接来电通知的权限,可以调用系统的“通话记录”。以下把系统“通话记录”记为B
尝试一:启动一个不可见的activity B,没想到办法,不知道有没有;
尝试二:直接启动B,保证其完成取消未接来电通知并且在用户可见之前通过ActivityManager关闭。效果不太好,也算一直实现了。使用一个线程,启动B之后通过ActivityManager来关闭B。代码如下:public class WatcherThread extends Thread{ private Context context; public WatcherThread(Context context){ this.context=context; } String yourclassNmae=null; public void run(){ Intent mIntent=new Intent(context,MyCallActivity.class); startActivity(mIntent); try { Thread.sleep(200); } catch (Exception e){ } ActivityManager activityManager = (ActivityManager)context.getSystemService(ACTIVITY_SERVICE); List<RunningTaskInfo> RunningTasks=activityManager.getRunningTasks(5); for(RunningTaskInfo mRunningTaskInfo:RunningTasks){ String className=mRunningTaskInfo.topActivity.getClassName(); if(className.equals(yourclassNmae)){ //1.restartPackage已经被弃用 //activityManager.restartPackage(mRunningTaskInfo.topActivity.getPackageName()); //2.killBackgroundProcesses可用,在2.3下使用restartPackage其实还是要调用killBackgroundProcesses来实现 activityManager.killBackgroundProcesses(mRunningTaskInfo.topActivity.getPackageName()); //使用以上2个方法还需要配置android.Manifest.permission#KILL_BACKGROUND_PROCESSES权限 //由于以上已经引入了classes.jar,还可以使用隐藏方法forceStopPackage //需要配置android.Manifest.permission#FORCE_STOP_PACKAGES权限 activityManager.forceStopPackage(mRunningTaskInfo.topActivity.getPackageName()); } } } }但是尝试之后发现并没有关闭B。通过关键词killbackgroundprocesses查询。找到一个Blog:http://www.cnblogs.com/ayiah/archive/2010/11/05/1870224.html,根据文章说使用killbackgroundprocesses ,不会kill用户可见的activity,这就是为什么我的测试没有关闭B,根据测试forceStopPackage也不能kill用户可见的activity,此尝试失败。
尝试三:取得一个实例,然后通过实例来结束此activity。
Activity mActivity=实例; if(mActivity.isFinishing()){ mActivity.finish(); }尝试三的问题是如何取得这个实例,ActivityManager能得到的信息有限,无法取得实例。不知道如何取得B的实例。。。
所做尝试失败,目前只好龌龊的吃掉所有异常,不作处理。
b.系统拨打电话可以调用action如下:
ACTION_CALL(普通),
ACTION_CALL_EMERGENCY (紧急电话),
ACTION_CALL_PRIVILEGED(系统专属),
而普通应用要拨打电话只能调用ACTION_CALL。所以整个应用中拨打电话这个动作都要改写成调用ACTION_CALL。
系统API如下:
/** * Activity Action: Perform a call to someone specified by the data. * <p>Input: If nothing, an empty dialer is started; else {@link #getData} * is URI of a phone number to be dialed or a tel: URI of an explicit phone * number. * <p>Output: nothing. * * <p>Note: there will be restrictions on which applications can initiate a * call; most applications should use the {@link #ACTION_DIAL}. * <p>Note: this Intent <strong>cannot</strong> be used to call emergency * numbers. Applications can <strong>dial</strong> emergency numbers using * {@link #ACTION_DIAL}, however. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CALL = "android.intent.action.CALL"; /** * Activity Action: Perform a call to an emergency number specified by the * data. * <p>Input: {@link #getData} is URI of a phone number to be dialed or a * tel: URI of an explicit phone number. * <p>Output: nothing. * @hide */ public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY"; /** * Activity action: Perform a call to any number (emergency or not) * specified by the data. * <p>Input: {@link #getData} is URI of a phone number to be dialed or a * tel: URI of an explicit phone number. * <p>Output: nothing. * @hide */ public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
3.联系人列表
通过这部分发现我在做的工作,其实很多同学已经做了,不过没办法,就当锻炼自己了。代码不报错后,运行出现以下异常:
I/ContactsListActivity( 1182): Called with action: com.android.contacts.action.LIST_DEFAULT E/DatabaseUtils( 183): Writing exception to parcel E/DatabaseUtils( 183): java.lang.UnsupportedOperationException: Only CrossProcessCursor cursors are supported across process for now E/DatabaseUtils( 183): at android.database.CursorToBulkCursorAdaptor.<init>(CursorToBulkCursorAdaptor.java:97) E/DatabaseUtils( 183): at android.content.ContentProvider$Transport.bulkQuery(ContentProvider.java:179) E/DatabaseUtils( 183): at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:111) E/DatabaseUtils( 183): at android.os.Binder.execTransact(Binder.java:320) E/DatabaseUtils( 183): at dalvik.system.NativeStart.run(Native Method) E/DatabaseUtils( 183): Caused by: java.lang.ClassCastException: com.android.providers.contacts.ContactsProvider2$3 E/DatabaseUtils( 183): at android.database.CursorToBulkCursorAdaptor.<init>(CursorToBulkCursorAdaptor.java:81) E/DatabaseUtils( 183): ... 4 more W/AsyncQuery( 1182): java.lang.UnsupportedOperationException: Only CrossProcessCursor cursors are supported across process for now D/AndroidRuntime( 1162): Shutting down VM关于Only CrossProcessCursor cursors are supported across process for now找到以下相关链接:1.http://www.cnblogs.com/chenxian/archive/2011/03/15/1984501.html,原文说:
这是java的多态造成的,返回的都是一个Cursor对象,这就是为什么向下转型是不安全的。provider将cursor跨进程传递时将会强制向下转换为CrossProcessCursor类型。。。。,使用SQLiteQueryBuilder貌似就可以了。
由于是调用系统的provider,所以不可能去修改了。只能找出问题,开始觉得去查源码太复杂了,同事提醒,简单的写了一个Demo,可以列出联系人,发现根本不存在什么跨进程的问题。但是还是没发现问题所在。后来对比了下Demo和系统代码都是列出所有联系人,没什么区别,唯一的不同就是uri:
Demo的:
Uri uri=Contacts.CONTENT_URI;
系统的:
uri=buildSectionIndexerUri(Contacts.CONTENT_URI); buildSectionIndexerUri()方法里返回的是uri.buildUpon().appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
继续在网上查找发现以下2个链接:http://www.eoeandroid.com/thread-70806-1-1.html
http://topic.csdn.net/u/20110406/17/cba2957e-c726-4a38-917d-ea34d761c75e.html
根据以上查询证实问题确实是在uri上。其实,2.2以前确实使用的是uri=Contacts.CONTENT_URI。原文:默认的查询全部联系人调用的是 mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, getContactSelection(),null,getSortOrder(projection));
其中uri通过getUriToQuery()方法得到,推出uri=buildSectionIndexerUri(Contacts.CONTENT_URI);
这里把uri改成uri=Contacts.CONTENT_URI;就可以查询出联系人
但是获得联系人列表没有“A,B,C,D”表头
然后去看源码发现这个函数buildSectionIndexerUri(Contacts.CONTENT_URI);获得是一个分级ui(AbstractHierarchicalUri)*******
“buildSectionIndexerUri”已经很明显了--索引表头,只是太浮躁了,联系人的字母表头就是这么来的。
整理以下得到uri的过程:
1.Uri uri = getUriToQuery(); 2.private Uri getUriToQuery() { switch(mMode) { case MODE_DEFAULT: return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS; } } 3.private static final Uri CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS =buildSectionIndexerUri(Contacts.CONTENT_URI); 4.//此处就是说得到一个带字母表头的uri private static Uri buildSectionIndexerUri(Uri uri) { return uri.buildUpon().appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build(); } 5.//4中都是Uri的子类中的方法,此处知道可以得到一个带字母表头的list的uri即可,感兴趣的可以自己看下Uri的源代码中的Builder和HierarchicalUri(继承自以上提到的AbstractHierarchicalUri)子类。
既然这样,跟进ContactsProvider2里查看query源码:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder)在query里,到查询完成得到cursor以后的源码是这样的:
//1 Cursor cursor = query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy, limit); if(readBooleanQueryParameter(uri, ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, false)) { //2 cursor = bundleLetterCountExtras(cursor, db, qb, selection, selectionArgs, sortOrder); }
已经得到cursor 了,判断一下readBooleanQueryParameter()是否需要字母表头!问题就在这儿了。
也就说不要字母表头,完全没有问题;要带字母表头,就要使用处理过的cursor,就会引发最开始的问题:Only CrossProcessCursor cursors are supported across process for now。终于找到了根源。只是还没有好好的看具体实现,回头看下。
看了下具体实现,做了下测试,代码如下(添加在以上代码之后,return之前):
if(cursor instanceof CrossProcessCursor){ Log.d("spare_H","1"); } else if(cursor instanceof CursorWrapper){ Log.d("spare_H","2"); }测试证明,cursor1是CrossProcessCursor类型的,cursor2是CursorWrapper的,所以查询带表头的cursor时就会出现无法完成类型转换,转换不成 CrossProcessCursor,而确实又是跨进程查询,最终导致:Only CrossProcessCursor cursors are supported across process for now!