1. 程式人生 > >獲取未安裝的App資訊

獲取未安裝的App資訊

本篇文章屬於進階篇,純技術分享,用來獲取一個未安裝的app的相關資訊也就是一個apk檔案。我們可以獲取到應用名稱,包名,應用圖示,版本號,版本名稱,含有四大元件中的哪些等等一切資訊,聽起來是不是有點小激動?沒錯,就是這麼簡單粗暴,下面就來動手實現吧!

本節知識你需要了解的類及其常用方法:

PackageItemInfo:

一般作為父類,很少直接使用,都是用其子類。

繼承關係:

java.lang.Object
  android.content.pm.PackageItemInfo
直接已知子類:ApplicationInfo, ComponentInfo, InstrumentationInfo, PermissionGroupInfo, PermissionInfo間接子類有:
ActivityInfo、ProviderInfo、ServiceInfo。 屬性:


方法:


PackageManager:

可以通過Context的getPackageManager得到,可以用來安裝、解除安裝應用,查詢許可權資訊,查詢已經安裝的應用,4大元件資訊,增加、刪除許可權,清楚使用者資料、緩衝、程式碼段等。

常用方法:









PackageInfo:

繼承關係:

java.lang.Object

  android.content.pm.PackageInfo
屬性:



方法:


ApplicationInfo:

ApplicationInfo是從一個特定的應用得到的資訊。這些資訊是從相對應的Androdimanifest.xml的< application>標籤中收集到的。

繼承關係:

java.lang.Object
  android.content.pm.PackageItemInfo
      android.content.pm.ApplicationInfo

屬性:



方法:



ActivityInfo:

繼承關係:

java.lang.Object
  android.content.pm.PackageItemInfo
      android.content.pm.ComponentInfo
          android.content.pm.ActivityInfo

屬性:





方法:


ServiceInfo:

常用屬性及方法:





ResolveInfo:

ResolveInfo這個類是通過解析一個與IntentFilter相對應的intent得到的資訊。它部分地對應於從AndroidManifest.xml的< intent>標籤收集到的資訊。

繼承關係:

java.lang.Object
  android.content.pm.ResolveInfo

屬性:


方法:


上面的類基本都是從manifest檔案中獲取的,來一張關係圖:


ok,東西有點多,以後我會寫幾篇文章關於使用這些類的,大家只需要大致的看一遍就好了,以後如果不清楚的可以來這裡查。今天我們就來演示一個從網上下載一個apk檔案,然後在不安裝的情況下獲取該apk檔案的應用名稱、包名、版本號、版本名稱、icon圖示。這裡我們使用前面學過的觀察者模式來監聽下載,使用handler來發送訊息,還包括檔案的操作,我們以後的文章都會慢慢的把前面的知識融合起來,如果的android知識不是很紮實或者你對那塊不懂,可以檢視我們的前面的部落格,基本都有涉及到相關知識,以後的文章都會是融合性的不再是單一的知識點。先來看下效果圖:


下面我們以效果圖來切入,一步步開發出完整的APK,首先是Activity的佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/ll"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/startAnalyze"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="開始分析" />

        <Button
            android:id="@+id/delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="刪除下載檔案" />
    </LinearLayout>

    <TextView
        android:id="@+id/infoText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/ll"
        android:layout_marginTop="20dp"
        android:lineSpacingExtra="5dp"
        android:textSize="20dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/infoText"
        android:layout_marginTop="20dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginRight="20dp"
            android:gravity="center"
            android:text="App圖示:"
            android:textColor="#976"
            android:textSize="20sp"
            android:textStyle="bold" />

        <ImageView
            android:id="@+id/iconImgv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" />
    </LinearLayout>
</RelativeLayout>

還記得觀察者模式嗎?android中的觀察者用到了兩個類Observer和Obserable,前一個是觀察者,後一個是被觀察者,前一個是介面,後一個是類。ok,我們定義一個工具類用來下載資料,我們不使用非同步任務,直接使用一個執行緒下載,在下載完成的時候通過觀察者通知Activity進行下一步的操作。下面是工具類的完整程式碼:
public class DownAPK extends Observable {
    public static final int EXISTS = 1, ERRORRESPONDED = -1, SUCCESS = 0, MAX = 2, PROGRESS = -2;
    public static File file;
    public int progress, max;

    public void startLoadAPK(final String apkUrl, final String savePath, String savaName) {
        file = new File(savePath + File.separator + savaName);
        Log.e("檔案儲存路徑:", file.getAbsolutePath());
        if (!file.exists()) {
            try {
                file.createNewFile();
                downThread(apkUrl, file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (file.length() > 0) {
            Log.e("檔案存在:", savaName);
            setChanged();
            notifyObservers(EXISTS);
            return;
        } else {
            downThread(apkUrl, file);
        }

    }

    //下載執行緒
    private void downThread(final String apkUrl, final File file) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                FileOutputStream fos = null;
                InputStream data = null;
                try {
                    HttpURLConnection conn = (HttpURLConnection) new URL(apkUrl).openConnection();
                    conn.setRequestMethod("GET");
                    conn.setReadTimeout(5 * 1000);
                    conn.setConnectTimeout(5 * 1000);
                    //傳送最大值
                    {
                        setChanged();
                        notifyObservers(conn.getContentLength());
                    }
                    //判斷是否能正常下載
                    if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
                        setChanged();
                        notifyObservers(ERRORRESPONDED);
                        return;
                    }
                    max = conn.getContentLength();
                    setChanged();
                    notifyObservers(MAX);
                    data = conn.getInputStream();
                    fos = new FileOutputStream(file);
                    byte[] buf = new byte[1024];
                    int len = 0;
                    while ((len = data.read(buf)) != -1) {
                        progress += len;
                        //傳送進度
                        setChanged();
                        notifyObservers(PROGRESS);
                        fos.write(buf, 0, len);
                        fos.flush();
                    }
                    //下載成功
                    setChanged();
                    notifyObservers(SUCCESS);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    //關流操作
                    if (data != null)
                        try {
                            data.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    if (fos != null) {
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }
}

裡面就兩個方法,一個是公共的用來給外部呼叫下載,另一個是私有的方法,我們在公共方法中主要判斷檔案是否已經存在,如果存在我們就呼叫私有的方法去下載。程式碼不難,大家稍微的看一下就懂了。關鍵點是Activity裡面的程式碼,我們需要Activity實現obverser介面,從寫update方法,這個方法程式碼如下:
  //觀察到資料變化就呼叫這個方法
    @Override
    public void update(Observable observable, Object data) {
        Message message = handler.obtainMessage();
        message.obj = data;
        handler.sendMessage(message);
    }

這裡我們很簡單的呼叫一個handler傳送一個訊息,下面來看看handler裡面的程式碼:
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch ((int) msg.obj) {
                case DownAPK.ERRORRESPONDED:
                    Toast.makeText(MainActivity.this, "下載失敗!", Toast.LENGTH_SHORT).show();
                    break;
                case DownAPK.EXISTS:
                    Toast.makeText(MainActivity.this, "檔案存在!", Toast.LENGTH_SHORT).show();
                    analyzing();
                    break;
                case DownAPK.SUCCESS:
                    Toast.makeText(MainActivity.this, "下載成功!", Toast.LENGTH_SHORT).show();
                    dialog.dismiss();
                    analyzing();
                    break;
                case DownAPK.PROGRESS:
                    dialog.setProgress(observerable.progress);
                    break;
                case DownAPK.MAX:
                    initDialog(observerable.max);
                    break;
            }

        }
    };

根據不同的狀態實現不同的邏輯,如果觀察到檔案存在就直接分析apk檔案,如果是更新dialog的進度條就更新進度條,這裡程式碼也很簡單,關鍵是方法analyzing(),這個方法是用來分析apk檔案的,來看看裡面的程式碼:
  private void analyzing() {
        //得到包管理者
        PackageManager packageManager = getPackageManager();
        //apk的路徑
        String savePath = getFilesDir().toString() + File.separator + fileName;
        //得到包的訊息
        PackageInfo archiveInfo = packageManager.getPackageArchiveInfo(savePath, PackageManager.GET_ACTIVITIES);
        //獲取icon圖示
        if (archiveInfo != null) {
            ApplicationInfo applicationInfo = archiveInfo.applicationInfo;
            applicationInfo.sourceDir = savePath;
            applicationInfo.publicSourceDir = savePath;
            Drawable drawable = applicationInfo.loadIcon(packageManager);
            if (drawable != null) {
                iconImgv.setImageDrawable(drawable);
            }
            buffer = new StringBuffer();
            {
                //應用名稱
                CharSequence appName = applicationInfo.loadLabel(packageManager);
                //包名
                String packageName = applicationInfo.packageName;
                //版本名稱
                String versionName = archiveInfo.versionName;
                //版本號
                int versionCode = archiveInfo.versionCode;
                buffer.append("應用名稱:" + appName + "\r\n").append("包名:" + packageName + "\r\n")
                        .append("版本號:" + versionName + "\r\n").append("版本:" + versionCode + "\r\n");
                //顯示分析出的資訊
                infoTxtv.setText(buffer.toString());
            }

        } else {
            Toast.makeText(MainActivity.this, "獲取失敗!", Toast.LENGTH_SHORT).show();
        }
    }

註釋很詳細就不多說了。最後貼出Activity的完整程式碼:
public class MainActivity extends AppCompatActivity implements Observer {
    private String apkPath = "http://www.apk3.com/uploads/soft/201504/jk.apk";
    private String fileName;
    private TextView infoTxtv;
    private Button startBtn;
    private ImageView iconImgv;
    private ProgressDialog dialog;
    private DownAPK observerable;
    private StringBuffer buffer;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch ((int) msg.obj) {
                case DownAPK.ERRORRESPONDED:
                    Toast.makeText(MainActivity.this, "下載失敗!", Toast.LENGTH_SHORT).show();
                    break;
                case DownAPK.EXISTS:
                    Toast.makeText(MainActivity.this, "檔案存在!", Toast.LENGTH_SHORT).show();
                    analyzing();
                    break;
                case DownAPK.SUCCESS:
                    Toast.makeText(MainActivity.this, "下載成功!", Toast.LENGTH_SHORT).show();
                    dialog.dismiss();
                    analyzing();
                    break;
                case DownAPK.PROGRESS:
                    dialog.setProgress(observerable.progress);
                    break;
                case DownAPK.MAX:
                    initDialog(observerable.max);
                    break;
            }

        }
    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //新增觀察者
        observerable = new DownAPK();
        observerable.addObserver(this);
        fileName = apkPath.substring(apkPath.lastIndexOf("/") + 1);
        init();
    }

    private void init() {
        //初始化控制元件
        infoTxtv = (TextView) findViewById(R.id.infoText);
        iconImgv = (ImageView) findViewById(R.id.iconImgv);
        startBtn = (Button) findViewById(R.id.startAnalyze);
        startBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //開始下載
                observerable.startLoadAPK(apkPath, getFilesDir().toString(), fileName);
            }
        });
        findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                File file = new File(getFilesDir().getAbsolutePath());
                File[] files = file.listFiles();
                boolean isDeleted = false;
                for (int i = 0; i < files.length; i++) {
                    if (files[i].getName().equals(fileName)) {
                        files[i].delete();
                        isDeleted = true;
                        infoTxtv.setText("");
                        Toast.makeText(MainActivity.this, fileName + ",刪除成功!", Toast.LENGTH_SHORT).show();
                    }
                }
                if (!isDeleted) {
                    Toast.makeText(MainActivity.this, "檔案不存在!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    //觀察到資料變化就呼叫這個方法
    @Override
    public void update(Observable observable, Object data) {
        Message message = handler.obtainMessage();
        message.obj = data;
        handler.sendMessage(message);
    }


    private void initDialog(int Max) {
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setMax(Max);
        dialog.setMessage("下載中請稍後...");
        dialog.show();
    }

    private void analyzing() {
        //得到包管理者
        PackageManager packageManager = getPackageManager();
        //apk的路徑
        String savePath = getFilesDir().toString() + File.separator + fileName;
        //得到包的訊息
        PackageInfo archiveInfo = packageManager.getPackageArchiveInfo(savePath, PackageManager.GET_ACTIVITIES);
        //獲取icon圖示
        if (archiveInfo != null) {
            ApplicationInfo applicationInfo = archiveInfo.applicationInfo;
            applicationInfo.sourceDir = savePath;
            applicationInfo.publicSourceDir = savePath;
            Drawable drawable = applicationInfo.loadIcon(packageManager);
            if (drawable != null) {
                iconImgv.setImageDrawable(drawable);
            }
            buffer = new StringBuffer();
            {
                //應用名稱
                CharSequence appName = applicationInfo.loadLabel(packageManager);
                //包名
                String packageName = applicationInfo.packageName;
                //版本名稱
                String versionName = archiveInfo.versionName;
                //版本號
                int versionCode = archiveInfo.versionCode;
                buffer.append("應用名稱:" + appName + "\r\n").append("包名:" + packageName + "\r\n")
                        .append("版本號:" + versionName + "\r\n").append("版本:" + versionCode + "\r\n");
                //顯示分析出的資訊
                infoTxtv.setText(buffer.toString());
            }

        } else {
            Toast.makeText(MainActivity.this, "獲取失敗!", Toast.LENGTH_SHORT).show();
        }
    }
}

掃描關注我的微信公眾號:


到這裡都完成了,我們開發都是用別人封裝好的API就可以了,能否開發出來一個好的app檔案關鍵是我們隊API的熟練程度和我們的知識面,沒開發一個app都會遇到各種的問題,我們在解決問題的時候慢慢的成長起來,為了做一個優秀的軟體工程師而不是一隻程式設計師,我們的路還有很長,不驕不躁,少玩遊戲多看書,相信我們定能得到我們想要的那份榮耀!最後送上demo福利:demo