Android Studio插件开发入门篇

转载自:http://blog.csdn.net/qq_27258799 https://blog.csdn.net/qq_27258799/article/details/78093734

前言

本篇是AS插件开发的入门篇,旨在了解整个开发流程,代码比较粗糙

AS插件绝对是我们开发道路上的一把利器,这里首先推荐几款我常用的插件。

Json转Java类

快速生成findViewById代码

CodeGlance

这里写图片描述

开发前准备

工欲善其事,必先利其器。首先我们要先准备IDE,这里使用 Intellij,我下载的Community版。

安装完毕后,新建一个插件开发工程:

这里写图片描述

关于project SDK,要说一下。如果没有sdk,就点击New,选择软件附带的,但是这里可能会提示没有Java jdk,在自己本机上,随便找一个版本的jdk就行,我用的1.8。

新建好的工程目录如下:

这里写图片描述

我们接下来模仿ECTranslation写一个翻译插件,因为原理简单,使用方便,实现起来很容易。大家如果看明白原理,都可以自己动手做一个。

因为要用到有道翻译的API,所以我们要先在有道智云上注册开发者,注册完申请自然语言翻译服务,创建应用。

这里写图片描述

请求API的过程有道翻译已经给我们准备好了Demo

我直接贴出来Java的请求过程:

public class Demo {

    public static void main(String[] args) throws Exception {
        String appKey ="您的appKey";
        String query = "good";
        String salt = String.valueOf(System.currentTimeMillis());
        String from = "EN";
        String to = "zh-CHS";
        String sign = md5(appKey + query + salt+ "您的密钥");
        Map params = new HashMap();
        params.put("q", query);
        params.put("from", from);
        params.put("to", to);
        params.put("sign", sign);
        params.put("salt", salt);
        params.put("appKey", appKey);
        System.out.println(requestForHttp("https://openapi.youdao.com/api", params));
    }

    public static String requestForHttp(String url,Map requestParams) throws Exception{
        String result = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        /**HttpPost*/
        HttpPost httpPost = new HttpPost(url);
        List params = new ArrayList();
        Iterator> it = requestParams.entrySet().iterator();
        while (it.hasNext()) {
            Entry en = it.next();
            String key = en.getKey();
            String value = en.getValue();
            if (value != null) {
                params.add(new BasicNameValuePair(key, value));
            }
        }
        httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));
        /**HttpResponse*/
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        try{
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity, "utf-8");
            EntityUtils.consume(httpEntity);
        }finally{
            try{
                if(httpResponse!=null){
                    httpResponse.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
        return result;
    }

    /** * 生成32位MD5摘要 * @param string * @return */
    public static String md5(String string) {
        if(string == null){
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'A', 'B', 'C', 'D', 'E', 'F'};

        try{
            byte[] btInput = string.getBytes("utf-8");
            /** 获得MD5摘要算法的 MessageDigest 对象 */
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            /** 使用指定的字节更新摘要 */
            mdInst.update(btInput);
            /** 获得密文 */
            byte[] md = mdInst.digest();
            /** 把密文转换成十六进制的字符串形式 */
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        }catch(NoSuchAlgorithmException | UnsupportedEncodingException e){
            return null;
        }
    }

    /** * 根据api地址和参数生成请求URL * @param url * @param params * @return */
    public static String getUrlWithQueryString(String url, Map params) {
        if (params == null) {
            return url;
        }

        StringBuilder builder = new StringBuilder(url);
        if (url.contains("?")) {
            builder.append("&");
        } else {
            builder.append("?");
        }

        int i = 0;
        for (String key : params.keySet()) {
            String value = params.get(key);
            if (value == null) { // 过滤空的key
                continue;
            }

            if (i != 0) {
                builder.append('&');
            }

            builder.append(key);
            builder.append('=');
            builder.append(encode(value));

            i++;
        }

        return builder.toString();
    }
    /** * 进行URL编码 * @param input * @return */
    public static String encode(String input) {
        if (input == null) {
            return "";
        }

        try {
            return URLEncoder.encode(input, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return input;
    }
}

开发前,先讲下翻译的原理。整个翻译的过程很简单,获取鼠标选择的单词,调用翻译API,整理翻译结果并显示。

这里使用的是有道翻译的API,新版有道智云的自然语言翻译API已经不免费了,不过用户注册以后会送100块的体验金,其收费标准如下:

这里写图片描述

如果是我们自己个人使用,100块也足够我们折腾了。

接下来进入正式的开发过程。

插件开发流程

首先新建一个Action文件:

这里写图片描述

然后填入Action ID、Class Name、快捷键:

这里写图片描述 
上图中我们把新加的翻译按钮放在Edit命令下,命令的名字叫translation,快捷键是Alt+E

确定以后,看一眼plugin.xml里多了这样一段代码:


  
    "zttranslation" class="ZTTranslation" text="Translation" description="translate word" icon="/icons/ic_logo.png">
      "EditMenu" anchor="first"/>
      "$default" first-keystroke="alt E"/>
    
  

这是我们新建的翻译Action对应的配置文件。这里扩展一下,上面配置文件中,我给自己的Action前加了个图标,就像这样: 
这里写图片描述

我们可以在resources目录下新建icons目录,并添加icon图片(后缀为)

这里写图片描述

然后在配置文件中添加对应的配置即可:icon="/icons/ic_logo.png"·。当AS的主题变为黑色时,Action的logo会自动使用后缀为dark的logo图。

下面我们看一眼新建的ZTTranslation类:

public class ZTTranslation extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
    }

}

接下来我们要获取鼠标选择的单词并弹框显示:

public class ZTTranslation extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        final Editor mEditor = e.getData(PlatformDataKeys.EDITOR);
        if (null == mEditor) {
            return;
        }
        SelectionModel model = mEditor.getSelectionModel();
        final String selectionTxt = model.getSelectedText();
        if (TextUtils.isEmpty(selectionTxt)) {
            return;
        }
         Messages.showMessageDialog(selectionTxt,"TestWord",Messages.getInformationIcon());
    }

}

运行一下看看效果。

运行以后会重新打开一个新窗口,随便新建一个工程,看下Edit命令下:

这里写图片描述

我们新建的Translation在第一行,下面测试下,选择单词:

这里写图片描述

一切正常,接下来我们继续往下接入翻译。

 @Override
    public void actionPerformed(AnActionEvent e) {
        final Editor mEditor = e.getData(PlatformDataKeys.EDITOR);
        if (null == mEditor) {
            return;
        }
        SelectionModel model = mEditor.getSelectionModel();
        final String selectionTxt = model.getSelectedText();
        if (TextUtils.isEmpty(selectionTxt)) {
            return;
        }
        //调用有道翻译Demo
        String result = HttpUtils.findTranslation(selectionTxt);
        StringBuilder sb = buildResult(result);
        String translation;
        if (sb.length() == 0) {
            translation = "暂无此翻译!";
        } else {
            translation = sb.toString();
        }
        showTranslationPopup(mEditor, translation);
    }

HttpUtils.findTranslation(selectionTxt)就是上面我贴出来的有道官网上的请求Demo,然后将返回的字符串按照设计的思路重新排版一下,翻译请求的返回结果示例如下:

{
  "errorCode":"0",
  "query":"good", //查询正确时,一定存在
  "translation": [ //查询正确时一定存在
      "好"
  ],
  "basic":{ // 有道词典-基本词典,查词时才有
      "phonetic":"gʊd"
      "uk-phonetic":"gʊd" //英式发音
      "us-phonetic":"ɡʊd" //美式发音
      "explains":[
          "好处",
          "好的"
          "好"
      ]
  },
  "web":[ // 有道词典-网络释义,该结果不一定存在
      {
          "key":"good",
          "value":["良好","善","美好"]
      },
      {...}
  ]
  ],
  "dict":{
      "url":"yddict://m.youdao.com/dict?le=eng&q=good"
  },
  "webdict":{
      "url":"http://m.youdao.com/dict?le=eng&q=good"
  },
  "l":"EN2zh-CHS"
}

我这里只取了“explains”的翻译结果,然后弹出一个popup显示:

 private void showTranslationPopup(final Editor editor, final String result) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                JBPopupFactory factory = JBPopupFactory.getInstance();
                factory.createHtmlTextBalloonBuilder(result, null, new JBColor(new Color(186, 238, 186), new Color
                        (73, 117, 73)), null).setFadeoutTime(5000).createBalloon().show(factory
                        .guessBestPopupLocation(editor), Balloon.Position.below);
            }
        });
    }

然后再运行看看,英译汉:

这里写图片描述

汉译英:

这里写图片描述

这里只是一个练习的例子,所以没考虑更多细节,不过以后看源码应付下生僻单词还是很给力的,哈哈。

最后就是编译并导出插件包,运行到我们的Android Studio上去。

这里写图片描述

这里写图片描述

插件zip包就导出到工程路径下了,然后我们赶紧打开AS安装插件试试。

这里写图片描述

安装完成以后重启AS就可以了。

这里写图片描述

发布流程

插件信息配置

现在我们准备好了插件,如果相认更多的人搜索到它,就得把它发布到插件仓库中,下面我们看看发布流程。

首先我们要完善插件信息: 
这里写图片描述

默认的配置文件长这样:

<idea-plugin version="2">
  <id>com.your.company.unique.plugin.idid>
  <name>Plugin display name herename>
  <version>1.0version>
  <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompanyvendor>

  
  <description> most HTML tags may be used ]]>description>

  
  <change-notes> most HTML tags may be used ]]>
  change-notes>

  
  <idea-version since-build="141.0"/>


  
  


  <extensions defaultExtensionNs="com.intellij">
    
  extensions>


  <actions>
    
  actions>

idea-plugin>

我们需要填写一些关键信息,其中 description必须用英文,否则提交到市场后,会被官方打回。description支持html标签,不熟悉的可以直接使用在线Html编辑器

还要需要注意的是这段:

  
  

这里是配置你的插件发布到jetbrains插件仓库的产品类型。

我们都知道jetbrains公司产品很多,比如我们常用的Android Studio,Intellij等,这些都支持插件。

如果我们注释上面的代码,那么我们的插件会默认上传到Intellij仓库,也就是说别人只能在Intellij中搜索到我们的插件,在AS中找不到……所以我们把注释去掉,就可以默认上传到所有仓库啦。

配置好信息后,我们需要重新编译生成插件。

注册Jetbrains仓库账号

准备好了插件信息以后,我们需要去注册仓库账号:Jetbrains Plugins Repository

这里写图片描述

上传以后需要官方审核,然后会给我们的注册邮箱发送反馈信息,如果我们的配置文件格式不对,比如我最开始使用了中文描述,就会被市场拒掉……

如果一切正常,一两个工作日后就会收到官方恭喜的邮件~然后我们就可以在仓库中搜索到啦。

这里写图片描述