安卓反编译及无限循环拨打电话

        这次是一个循环拨打电话的需求,直到打通为止,否则就一直循环播打,这个问题的难点在于如何获取到对方接通电话的状态结束循环(此处说的非offhook状态,因为offhook只是摘机状态,无法判断对方是否接起电话),经过上网查阅资料,去电接通状态是获取不到的,可是怎么结束循环呢,这个问题困扰了我好长时间,后来有一次看到一个电话订票抢票软件,可以输入一个号码,一直循环拨打,直到打通为止,这不是跟我的需求一样吗!所以抱着学习的态度,同时也抱着试一试的态度,进行了反编译,查看了一下它的实现方式。所以,我们先来看一下如何反编译查看源码吧。

我在上篇文章中写了反编译apk获取资源文件,大家可以点击查看,本文主要介绍使用dex2jar反编译查看源码,反编译工具包也可以从中下载,下面是以8684这个电话订票抢票软件为例的。

使用dex2jar反编译获取源码

1. 首先,我们先将dex2jar及jd-gui下载并解压,dex2jar是用来反编译的,我们要用到d2j-dex2jar.bat这个文件,如果你是linux或mac系统的话就要用d2j-dex2jar.sh这个文件,jd-gui是用来查看反编译后的jar包的,这个在下面会说到,解压后如下图:

2.获取要反编译的apk中的dex文件,我们可以将demo.apk修改后缀名为.zip文件,并使用解压工具将其中的.dex文件取出,放到dex2jar解压后的目录,如下图所示:

3.在此目录下打开命令行,并执行 d2j-dex2jar classes.dex 指令,执行结束后,没有任何报错,则会在文件夹中得到classes-dex2jar.jar文件,如图:

4.classes-dex2jar.jar文件,我们是无法查看的,需要jd-gui工具来查看,打开jd-gui,并将此jar文件拖入打开的对话框中,即可查看源码:

自此,我们就获得了demo.apk中的源代码,但是一些资源id并不是以R.id.button的形式体现的,而是以真正的数字id体现,如上图中:this.comm.halt(getText(2131165188)),还需要注意的是,现在向市场上传apk时有时需要强制给apk加壳,这样我们这种反编译的方式是获取不到源码的,需要先进行脱壳,而且如果代码经过混淆以后,类名等都是以a,b,c等没有意义的字母直接表示的,造成阅读的难度,至于如何脱壳,再次不做赘述,以后再做研究。下面我们介绍下反编译出来的源码中停止循环的逻辑。

从反编译的源码中获取停止循环的逻辑


       首先将应用安装到手机上,并定位到具有该功能的界面,使用hierarchyviewer.bat可以查看当前打开的是哪个activity,通过点击按钮的文字获取对应的id,并在activity中得到按钮调用方法的位置,一般简单的应用之间看onclick方法就能定位到,而且有的可以直接通过包名或者类名得到,比如本例中,直接通过包名qiangpiao就可以得到(是的,就是你没跑了):

        可以看到,这些类名都是有意义的,所以大大降低了阅读源码的难度,通过分析,我最终将判断结束循环拨打电话的源码定位到了PhoneCallService这个类,经过分析发现,原来这里结束循环的做法是每次挂断电话以后,判断一下通话的数据库中的通话时长是否大于0(额,就这么简单吗。。。),因为打通之后,而对方没有接听电话的话,运营商会自动挂断电话,或者我们可以手动挂断电话,此时,通话时长为0,如果对方接听了电话,则通话时长大于0,核心代码及:

private boolean LastCallSucceed() {

    if (!this.isCall)

    return false;

    String[] arrayOfString = { "number", "duration" };

    Cursor localCursor = getContentResolver().query(CallLog.Calls.CONTENT_URI, arrayOfString, null, null, "date DESC");      //获取number跟duration列的cursor游标

    return (localCursor.moveToFirst()) && (localCursor.getInt(1) > 0);   //定位到最后一条通话,并返回duration的时间是否大于0

}

看到这里,算是解决了困扰了我好久的问题,结合这个我写了一个可以循环拨打电话的demo,逻辑也很简单,贴一下主要逻辑代码,写一个service,在onCreate()中,初始化要循环的电话,并监听电话的状态:

public void onCreate() {

    mPhoneNumList.add("xxxxxxxxxxx");//TODO本demo需要自己添加测试号码

    mPhoneNumList.add("xxxxxxxxxxx");

    tm= (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);

    tm.listen(newMyPhoneStateListener(),PhoneStateListener.LISTEN_CALL_STATE);  //监听电话状态

    super.onCreate();

}

PhoneStateListener类:

classMyPhoneStateListenerextendsPhoneStateListener {

    @Override

    public voidonCallStateChanged(intstate,String incomingNumber) {

        switch(state) {

        caseTelephonyManager.CALL_STATE_IDLE:

        mHandler.sendEmptyMessageDelayed(0,3000);   //当挂断电话时,发送一个延时消息,调用拨打电话的逻辑,实现循环拨打电话

       break;

      caseTelephonyManager.CALL_STATE_OFFHOOK:

      break;

      caseTelephonyManager.CALL_STATE_RINGING:

      break;

       }

   }

}

Handler的调用逻辑,接受到延时消息后,判断之后调用逻辑:

privateHandlermHandler=newHandler() {

    @Override

    public voidhandleMessage(Message msg) {

        if(!isLastCallSucceed() &&mIsLoopCall) {   //通过mIsLoopCall标记来过滤普通电话的挂断状态

            startPhoneCall();

        }else{

            mIsLoopCall=false;

        }

    }

};

在MainActivity中只是简单进行Service的绑定及调用循环拨打电话的操作,这里就不贴了,具体的代码已经上传github,有需要的朋友可以down下来瞅一下,如果对您有所帮助,还望star哈,3Q!

这里是传送门:github地址

推荐阅读更多精彩内容