天天看點

記錄開發經曆-----AIDL實作APP關機功能

一、AIDL的文法

首先,介紹一下AIDL的文法。

AIDL的文法與Java的接口寫法十分類似,甚至可以說一模一樣,但也有細微的差別,總結如下:

  1. 不能使用private、public、protect修飾方法;
  2. 支援傳遞資料類型有:java基本資料類型(byte、short、int、long、float、double、char、boolean)、String、CharSequence、List(接收方必須是ArrayList)、Map(接收方必須是HashMap)、其他AIDL定義的AIDL接口、實作Parcelable序列化的類;
  3. .其他AIDL定義的AIDL接口和實作Parcelable序列化的類必須import,即使在相同包結構下,其餘的類型不需要import;
  4. 對于非基本資料類型,也不是String和CharSequence類型的,需要有方向訓示,包括in、out和inout,in表示由用戶端設定,out表示由服務端設定,inout是兩者均可設定。

二、AIDL使用

AIDL的使用分為服務端和用戶端,由服務端建立service。首先我們來實作服務端的建立和AIDL建立。

注意

基本開發流程:先開發Service端,後開發Client端

使用AndroidStudio建立AidlDemo工程後,再在裡面建立Service Module和Client Module,不用管預設的app Module

如下圖,右鍵New建立AIDL檔案,會直接生成一個AIDL模闆類IService.aidl。

記錄開發經曆-----AIDL實作APP關機功能

Service端

包名: com.studyhelper.aidl

1、建立aidl檔案,如:IMyAidlInterface.aidl,新增接口,void PowerOff();

2、檢查build/generated/source/aidl/debug下是否存在對應的java檔案,若無則Rebuild Project

3、建立繼承于android.app.Service的Service類,如:MyService.java,并實作必須要實作的onBind方法

package com.studyhelper.aidl;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;


import static android.content.Intent.ACTION_REQUEST_SHUTDOWN;
import static android.content.Intent.EXTRA_KEY_CONFIRM;


public class MyService extends Service {
        public MyService() {
        }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return iMyAidlInterface;
    }
    private IMyAidlInterface.Stub iMyAidlInterface=new IMyAidlInterface.Stub() {
        @Override
        public void PowerOff() throws RemoteException {
            Intent i=new Intent();
        i.setAction(ACTION_REQUEST_SHUTDOWN);
        i.putExtra(EXTRA_KEY_CONFIRM, false);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(i);
        }
    };
}

           

3.1、在AndroidManifest.xml檔案中靜态注冊Service,詳細注冊代碼如下:

<!-- exported表示是否支援其它應用調用目前元件 -->
<!-- process表示将組建運作到單獨的程序中 -->
<!-- action用于用于過濾識别其他的Intent -->
        <service
            android:name=".MyService"
            android:enabled="true"
             android:process=":Remote"
            android:exported="true">
            <intent-filter >
                <action android:name="com.studyhelper.aidl.MyService"/>
            </intent-filter>
        </service>
           

Client端

包名:com.studyhelper.client

1、将Service端的aidl檔案,拷貝到main檔案夾下,需要注意的是aidl檔案的包名還有Service端的包名相同

2、檢查build/generated/source/aidl/debug下是否存在對應的java檔案,若無則Rebuild Project

3、建立Intent對象并執行個體化,接着配置在Service端配置的action,實作Service的綁定,具體代碼如下是以:

package com.studyhelper.client;


import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


import com.studyhelper.aidl.IMyAidlInterface;

public class MainActivity extends AppCompatActivity  {
IMyAidlInterface mBinder;
Boolean mIsBind =false;
private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mBinder=IMyAidlInterface.Stub.asInterface(service);
        mIsBind=true;
        Log.i("MainActivity","onServiceConnected");
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Intent intent =new Intent();
intent.setAction("com.studyhelper.aidl.MyService");
intent.setPackage("com.studyhelper.aidl");
bindService(intent,conn,BIND_AUTO_CREATE);//開啟service

        Button button = findViewById(R.id.off);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    mBinder.PowerOff();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

           

需要注意的是調用的關機接口是不對上層開放的

在AndroidManifest.xml中加入語句

android:sharedUserId="android.uid.system"

與系統一樣的uid,将apk提升為系統權限

該語句加在版本号後。

然後再寫入一條系統權限

<uses-permission android:name="android.permission.SHUTDOWN"/>

該權限隻有系統級apk才能使用

但是,人的需求是無限的。如果我們非要使用不可用的 API 怎麼辦呢?最簡單的就是 Java 的反射,反射 @hiden 的方法或類,修改通路修飾符,然後就可以搞事情了~~還有一種方法是從裝置中提取,簡單說就是把裝置上的 /system/framework/framework.jar 提取出來,經過一系列轉換,最終得到完整的 android.jar,具體的步驟可以參考這篇文章:android怎樣調用@hide和internal API。另外一種方式非常簡單,GitHub 上有一個項目:android-hidden-api,裡面提供了衆多版本完整的 android.jar 包,下圖所示。

記錄開發經曆-----AIDL實作APP關機功能

我們把工程 clone 下來,找到對應平台的 android.jar 包,替換掉 Android SDK 下面的 jar,最好先備份一下原始的 jar,重新編譯工程或者重新開機 Studio 就行了。

這種方式對于個人開發來說沒有問題,你把 android.jar 替換掉就好了,但是如果面對團隊開發,就非常痛苦了 %>_<%,每個人都要替換 SDK 的 android.jar,代價和風險可想而知。那麼有沒有好的解決辦法,既可以讓工程編譯通過,又能夠免去多人替換 jar 的成本呢?答案是有的。

Studio 預設引用的是 SDK 下面的 android.jar,那我們把它的引用改成完整的 jar 的路徑不就行了麼?

我們把完整的 android.jar 放在工程 libs 目錄下,也就是平時依賴 jar 的地方,然後在工程 build.gradle 配置的 dependencies 裡,以 provided 的方式引用 android.jar。因為每個工程子產品依賴 android.jar 的類型就是 provided,這樣不會把 android.jar 打包到應用中,運作環境中存在 framework.jar,應用直接就可以使用。

dependencies {
     // compile fileTree(include: ['*.jar'], dir: 'libs')  這行一定要去掉,當然為 android.jar 換個目錄也行
    testCompile 'junit:junit:4.12'
    provided files('libs/hidden_api_28.jar')
}
           

最後還要在工程根目錄的 build.gradle 裡面配置目前 project,加上下面的代碼就行了。

project('app') { // app是你工程的名字,配置隻對目前工程有效
    gradle.projectsEvaluated {
        tasks.withType(JavaCompile) {
            // 注意修改 jar 包的路徑,替換 app/libs/hidden_api_23.jar,其他部分不要改
            // Xbootclasspath/p:是 Java 編譯的尋址優先設定,先找預設路徑還是全路徑
            options.compilerArgs.add('-Xbootclasspath/p:app/libs/hidden_api_28.jar')
        }
    }
}
           

現在重新編譯工程,雖然會在代碼中出現錯誤提示,但是編譯打包運作都是正常的。_

在開發中使用隐藏 API 和内部 API 是不推薦的做法,但是為了實作一些「黑科技」,這些又是必須的~