cordova 簡單趟坑文檔二(android相關)

新增補充css

插件的配置信息地址: cordova.apache.org/docs/en/lat… html


### cordova 分享文檔
#### 目的
能在android手機跑起來一個android App,顯示內容是咱們預期的內容,而且使用自定義的插件。

#### cordova
爲啥是cordova,不是weex,rn不是其餘技術。
大前端環境下,直面客戶的均可以說是前端的內容,不過是web端,桌面端,移動端仍是嵌入式其餘系統等,均可以概括爲前端。cordova在移動端和外部交互的歷史上,可算是穩如老狗的老。其實沒有其餘的太多緣由,cordova簡單。hybird這些原理都是一個,weex rn這些還更詳細的作了其餘的原生渲染處理,好比說web端渲染的dom樹,用原生的節點渲染等,優化上是作到了,也複雜了。對於咱們如今目標,玩起來再說,其實客戶端用前端寫,也差很少是這個技術了。簡單好用纔是硬道理。

cordova 安裝過程再也不詳細描述,部分能夠參照原有文檔[http://confluence.51baiwang.com/pages/viewpage.action?pageId=15665622](http://confluence.51baiwang.com/pages/viewpage.action?pageId=15665622)

評論有安裝遇到的一些問題。
    
在這裏再次說說項目目錄結構的內容。

```
// tree -L 1
├── config.xml
├── hooks
├── node_modules
├── package.json
├── platforms
├── plugins
├── res
└── www
```

主要的文件夾。    
**/www**
如同大多數項目的結構同樣,/www 目錄下面的是網頁端的代碼。    
```
// tree -L 2
├── css
│   └── index.css
├── img
│   └── logo.png
├── index.html
└── js
    └── index.js
```
熟悉的前端代碼,再也不多說。

**/plugins**
接着再來看插件文件目錄。插件目錄屬於不常手動修改的目錄。
```
//▶ tree -L 1
.
├── android.json
├── cordova-plugin-whitelist
├── fetch.json
├── ios.json
└── org.lee.cordova.qrcodeanalysis
```
這塊主要是下載的cordova plugin文件目錄,和一些平臺相關的json文件,引入某些插件或者其餘。**插件的引入刪除操做建議直接用指令來操做,影響到的文件很多,很容易出問題**
儘可能直接只用cordova plugin add / remove 操做。除非某些插件,在直接使用指令引入會產生報錯的時候,能夠稍作處理。
這裏涉及到一個平臺引入,也就是生成平臺代碼的過程當中。按照測試的流程,平臺插件的代碼,是從plugin裏面獲取的,也就是說,在我添加了plugin,尚未添加platform的狀況下,修改了plugin裏面的代碼,再添加platform,所引入的plugin代碼爲修改事後的代碼。因此當某一個插件添加,某個環境報錯的時候,能夠嘗試先刪除平臺,修改代碼,從新引入平臺。

**/platforms:各平臺構建(或者說自動生成的項目代碼)文件目錄**
好比說添加了以下ios和android兩個平臺,分別有xcode的ios項目文件,ios dir;和gradle構建的android項目文件,android dir。
使用對應的studio打開,便可對項目進行更多配置以外的操做。

PS:須要注意的是。platform文件夾,**至關於vue-cli默認build的dist文件夾**。cordova項目裏的HTML代碼修改了,沒有再次構建,在platform項目裏面是不包含的。同時,從新build會修改platform內容代碼。
```
// tree -L 2
.
├── android
│   ├── CordovaLib
│   ├── android.json
│   ├── app
│   ├── build.gradle
│   ├── cordova
│   ├── org.lee.cordova.qrcodeanalysis
│   ├── platform_www
│   ├── project.properties
│   ├── settings.gradle
│   └── wrapper.gradle
└── ios
    ├── CordovaLib
    ├── HelloCordova
    ├── HelloCordova.xcodeproj
    ├── HelloCordova.xcworkspace
    ├── cordova
    ├── ios.json
    ├── platform_www
    ├── pods-debug.xcconfig
    ├── pods-release.xcconfig
    └── www
```

在對應的項目目錄下,咱們均可以看到一個/www目錄
- `/platforms/android/app/src/main/assets/www`
- `/platforms/ios/www`
```
▶ tree -L 1
.
├── cordova-js-src
├── cordova.js
├── cordova_plugins.js
├── css
├── img
├── index.html
├── js
└── plugins
```
發現目錄結構和外面的www有點兒相像,多了些文件,也就是cordova 自動生成的部分js,詳情能夠打開這些js文件看看,經過cordova.js和插件的js相關聯,也是經過cordova.js以某種協議發送信息。詳細內容能夠自行尋找部分hybird原理的資料。hybird是打開一個webview(相似iframe),再經過這樣打開一個外部的網頁,原生部分能夠經過攔截url scheme,簡單說就是攔截請求,獲取到對應的內容,進行了原生部分的操做,而後原生經過webview能夠隨意調用js的函數,這時候調用到對應的callback函數,完成數據的交互。
交互的這個過程,封裝成Bridge文件,也就是這些cordova.js

### config.xml
config.xml能夠說是咱們修改得稍微多一點的文件了。
好比說app的惟一值id
`<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">`

描述、做者等,還有平臺定製化的內容,引入插件時候的插件添加等配置。    
最不能忽略的一點也就是入口。
`<content src="index.html" />`
顯而易見的,入口是指向的是www下的index。
**既然入口是能夠指定的,那麼,入口能夠指定爲某一個已經部署項目的服務端地址嗎?**
答案是固然的。
好比說將www目錄下部署到一個node服務上,把content的內容設置爲192.168.xxx.xxx:8080,訪問到這個服務的主頁面。一切都是這麼的美好,真機開發的時候,只須要安裝一次android apk或ipa就能夠在真機上看到開發的效果了。
而後你會發現,這些網站都是從瀏覽器打開的,一臉懵逼,生無可戀,每次開發驗證難道又得一遍又一遍的打包了?針對性的排查這個問題,咱們能夠修改了`allow-navigation
Controls` 就能夠容許應用內打開外部頁面了。更多參考文檔~[https://cordova.apache.org/docs/en/latest/config_ref/index.html](https://cordova.apache.org/docs/en/latest/config_ref/index.html)   
接着,頁面是顯示了,但是要怎麼打開原生的功能呢?若是不能調用原生的功能,在真機上開發又有什麼意義呢,還不如在瀏覽器開發。
拋開外部連接或者是內部文件,打開原生功能的流程是什麼呢,cordova.js啊,對,那就得了,咱們再在遠端連接給html引入cordova.js,cordovajs依賴了哪一個js呢?到這裏,能夠聯想到上面說的平臺內的www文件目錄。直接將依賴到的內容,放置到遠端部署的文件目錄內便可。
**注意:**,在web文件內引入了cordova相關文件,也只是引入了一半的【橋】,還有另外一半的尚未確認搭建,若是在原生模塊,尚未引入相關的功能,web端經過橋通知到原生,可是沒有這個功能的時候,是無法調用起來相關內容的。對應的前端報錯就是undefined。


```
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>HelloCordova</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <platform name="android">
        <allow-intent href="market:*" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
    <engine name="ios" spec="^4.5.5" />
    <plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
    <plugin name="org.lee.cordova.qrcodeanalysis" spec="/Users/lizhaowen/work/plugin/qrcodeanalysis" />
    <engine name="android" spec="~7.0.0" />
</widget>
```

到這裏cordova 更詳細的說明到一個段落了。

----
下面是android部分的一些簡單內容,針對的目標是沒有java基礎,沒有android開發基礎。請選擇性跳過。
#### Android
android studio打開/platforms/android 這個項目。
![develop type](http://aaa-test.51baiwang.com/images/markdown/developType.jpg)

(android studio打開的項目是會以gradle構建的,若是項目結構內沒有找到gradle文件,或者說直接打開的是cordova project,彈窗提示以後,讓自動生成gradle結構的文件目錄,studio就會去下載好多東西)

打開項目,如上圖developType,點擊左上角切換目錄結構類型,使用比較多的是android 和 project,project的目錄結構就是finder的目錄結構。

使用android模式打開

manifest文件,android設置activity,主入口和受權等內容的地方
![manifest](http://aaa-test.51baiwang.com/images/markdown/manifest.jpg)

接着看到java文件夾
按照包的路徑打開,咱們能夠看到MainActivity,程序的主入口。既然路過咱們就探頭看一眼主入口都幹了什麼吧
```
public class MainActivity extends CordovaActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // enable Cordova apps to be started in the background
        Bundle extras = getIntent().getExtras();
        if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
            moveTaskToBack(true);
        }

        // Set by <content src="index.html" /> in config.xml
        loadUrl(launchUrl);
    }
    ...
}

```
繼承自cordovaActivity
寫了這麼久的vue,相信都有生命週期的這個概念了吧,每個Activity不也就至關於一個vue麼,有它本身的生命週期。這裏只展現了一個onCreate,走進來就一點兒代碼,主要的是loadUrl這個方法。很明顯,咱們能夠猜想到是幹什麼用的了,就是用webview打開一個url,這個url也就是咱們web程序的主入口了。除了在config.xml 修改url入口,咱們也能夠在這裏修改成服務器地址的入口。

其餘java文件也就是插件的java文件了。後續再討論這塊。
接下來是一個/assets 資源文件。emmm上面說過的一個文件夾。web資源和生成的cordova web橋文件。

最後是/res文件夾,是android的一些靜態資源存放的文件夾,icon和img等,對咱們影響較大的就是下面xml配置相關的文件夾,這個config同等於cordova的config,獲取的配置信息就是在這裏。一樣的,修改它也會影響工程。

app同級下有個CordovaLib,能夠說是一個android的module project,相關工程導包在這裏不作延伸。

最後在看看Gradle script腳本。
![gradle](http://aaa-test.51baiwang.com/images/markdown/gradle.jpg)
能夠看到有好幾個的build.gradle,後面都有括號標出來後綴。簡單說一下這個,每個android project只有一個project的build腳本,這個工程引入了多個module,app也是做爲一個module被工程引入,只不過是同時也做爲了入口的module。cordovaLib做爲了其餘的依賴module。
這兩個又是怎麼樣區分的呢。勇敢打點開屬於app的這個build.gradle文件~
第一行代碼 `apply plugin: 'com.android.application'` 這個插件做爲android 的application。
而後咱們再打開cordova的,呀,什麼都沒。再往下看看。像這樣 `apply plugin: 'com.android.library'` 。當導入項目做爲依賴的時候,咱們就須要將項目的apply plugin設置爲lib啦。
而後再settings.gradle 把module include進來。
```
// GENERATED FILE - DO NOT EDIT
include ":"
include ":CordovaLib"
include ":app"
```

說到這兒,也沒說gradle腳本里面的內容,順序不太對呀嘿嘿。其實裏面就是一些json,也相似於xml的屬於配置項,接在第一行代碼之下的
```
buildscript {
    repositories {
        mavenCentral()
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
    }
}
```
資源庫,maven倉庫等一些構建項目的東西,dependencies,是項目須要引入的一些依賴,有遠端下載的依賴也有本地jar aar包的依賴等。

說了一大堆沒啥用的,還不如直接手機插到電腦上,配置好開發模式,按一下那個綠色的啓動按鈕,跑到手機上,遇到問題再查嘿嘿。

### plugin
終於到了感受挺複雜的,其實並無這麼複雜的這個插件介紹。
做爲JS和native溝通的橋樑。plugin都分別對接了兩方,溝通對接這個過程,永遠的都是一個難題。可是插件也起到的做用也只是單純的做爲一個管道的做用,流通js和native的數據。

定位清楚了,咱們在寫插件的時候,就完成了插件所應有的功能就行了。

下面是使用android 掃碼SDK,而後寫的一個插件。
插件的生成
[https://cordova.apache.org/docs/en/latest/plugin_ref/plugman.html](https://cordova.apache.org/docs/en/latest/plugin_ref/plugman.html)
相似vue-cli,自動生成插件文件(須要注意的是不會生成package.json,插件引入的時候須要這個文件的數據,直接用npm init生成,直接回車一直下去就能夠了)
```
▶ tree -L 3
.
├── package.json
├── plugin.xml
├── src
│   ├── android
│   │   ├── qrcodeanalysis.gradle
│   │   ├── qrcodeanalysis.java
│   │   └── zxinglibrary-debug.aar
│   └── ios
│       └── qrcodeanalysis.m
└── www
    └── qrcodeanalysis.js
```

從/www/qrcodeanalysis導出scan函數
```
var exec = require('cordova/exec');

exports.coolMethod = function (arg0, success, error) {
    exec(success, error, 'qrcodeanalysis', 'coolMethod', [arg0]);
};
exports.scan = function (arg0, success, error) {
    exec(success, error, 'qrcodeanalysis', 'scan', [arg0]);
};
```
exec調用的是cordova/exec方法。
調用到qrcodeanalysis這個execute => private scan
下面直接給出代碼,結合着看
```
/**
 * This class echoes a string called from JavaScript.
 */
public class qrcodeanalysis extends CordovaPlugin {
    private CallbackContext callbackContext = null;
    private int REQUEST_CODE_SCAN = 199;

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("coolMethod")) {
            String message = args.getString(0);
            this.coolMethod(message, callbackContext);
            return true;
        }
        if (action.equals("scan")) {
            String message = args.getString(0);
            this.scan(message, callbackContext);
            return true;
        }
        return false;
    }

    private void coolMethod(String message, CallbackContext callbackContext) {
        if (message != null && message.length() > 0) {
            callbackContext.success(message);
        } else {
            callbackContext.error("Expected one non-empty string argument.");
        }
    }

    private void scan(String message, CallbackContext callbackContext) {
        this.callbackContext = callbackContext;
        cordova.setActivityResultCallback(this);
        AndPermission.with(cordova.getActivity())
                        .permission(Permission.CAMERA, Permission.READ_EXTERNAL_STORAGE)
                        .onGranted(new Action() {
                            @Override
                            public void onAction(List<String> permissions) {
                                Intent intent = new Intent(cordova.getActivity(), CaptureActivity.class);
                                /*ZxingConfig是配置類
                                 *能夠設置是否顯示底部佈局,閃光燈,相冊,
                                 * 是否播放提示音  震動
                                 * 設置掃描框顏色等
                                 * 也能夠不傳這個參數
                                 * */
                                ZxingConfig config = new ZxingConfig();
                                // config.setPlayBeep(false);//是否播放掃描聲音 默認爲true
                                //  config.setShake(false);//是否震動  默認爲true
                                // config.setDecodeBarCode(false);//是否掃描條形碼 默認爲true
//                                config.setReactColor(R.color.colorAccent);//設置掃描框四個角的顏色 默認爲白色
//                                config.setFrameLineColor(R.color.colorAccent);//設置掃描框邊框顏色 默認無色
//                                config.setScanLineColor(R.color.colorAccent);//設置掃描線的顏色 默認白色
                                config.setFullScreenScan(false);//是否全屏掃描  默認爲true  設爲false則只會在掃描框中掃描
                                intent.putExtra(Constant.INTENT_ZXING_CONFIG, config);
                                cordova.getActivity().startActivityForResult(intent, REQUEST_CODE_SCAN);
                            }
                        })
                        .onDenied(new Action() {
                            @Override
                            public void onAction(List<String> permissions) {
                                Uri packageURI = Uri.parse("package:" + cordova.getActivity().getPackageName());
                                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                                cordova.getActivity().startActivity(intent);

                                Toast.makeText(cordova.getActivity(), "沒有權限沒法掃描呦", Toast.LENGTH_LONG).show();
                            }
                        }).start();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // 掃描二維碼/條碼回傳
        if (requestCode == REQUEST_CODE_SCAN) {
            if (data != null) {

                String content = data.getStringExtra(Constant.CODED_CONTENT);
                PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, content);
                pluginResult.setKeepCallback(true);
                callbackContext.sendPluginResult(pluginResult);
            }
        }
    }
}
```
咱們把調用掃碼的過程給劃分開。

SDK
- 喚起攝像頭
- 獲取視頻流
- 解析視頻流
- 獲得掃碼的數據

plugin
- 接收掃碼數據
- 返回掃碼數據

主要的步驟都在sdk上面了。
首先,咱們默認導入了掃碼SDK,能夠直接使用。直接在scan函數體`AndPermission.with(cordova.getActivity())....startActivityForResult(intent, REQUEST_CODE_SCAN);`調用起來掃碼。掃碼過程是個相似js異步的過程,須要等到數據解析完成以後才執行callback。在看過SDK的demo以後,使用android自帶的數據接收方式
```
@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // 掃描二維碼/條碼回傳
        if (requestCode == REQUEST_CODE_SCAN) {
            LOG.i("111", "111")
            ...
            }
        }
    }
```
咱們看到一個單詞@Override,重寫的自帶的函數。
再個就是,android調用Activity的時候,會用一個自定義的int數據來標記返回。REQUEST_CODE_SCAN。

接下來,咱們先測試一下這個步驟是否能走通。一步一步來嘛~
跑起來,掃碼~在控制檯Logcat一看,log info沒有輸出任何111相關的。   
撓頭.gif    
再來一次,肯定沒有輸出。
再看看demo,沒錯啊。   
懵逼.jpg
那就是數據沒到onActivity,爲啥呢?

搬運:
解決方法:

不是使用cordova.getActivity().startActivityForResult();這樣調試跟蹤後會發現被主Activity的OnActivityResult給攔截了。

解決方法使用 cordova.StartActivityForResult(cordovaplugin,Intent,int)

以下代碼

cordova.setActivityResultCallback(this);

cordova.setActivityForResult(this,intent,RESULT);

緣由是:plugin會經過CordovaInterface中的startActivityForResult(cordovaPlugin,intent,int)方法啓動該Activity。

當 Activity 結束後,系統將調用回調函數 onActivityResult(int requestCode, int resultCode, Intent intent)

原文:https://blog.csdn.net/xiangwang666/article/details/51699721 

因而咱們在調用掃碼以前set一下。哎,ok了。
接着就是最後一個步驟了。返回數據。喵了一眼自動生成的coolMethod,是使用一個傳進來的callbackContent裏面的方法。但是在異步仍是另外的函數,怎麼辦呢?
聯想了一下js的window,我也給這個android的東西掛到一個兩個函數均可以訪問的地方去唄。
因而定義了一個`private CallbackContext callbackContext = null;`再喵一眼coolMethod是怎麼返回數據的,測試一下。

完事兒啦


複製代碼
相關文章
相關標籤/搜索