Contacts应用移植

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


 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值