标签: Android Studio

Android 做一个简单记事本app

1、引用了Android studio自带的*个模板(右下角带小圆按钮的),用这个按钮做点击事件用来跳到编辑笔记界面。项目创建会多带两个java文件(FirstFragment、SecondFragment)以及对应的两个xml文件,这里我尝试将这几个文件删掉结果运行会崩,然后我将这几个文件的主题代码注释掉又没问题。暂时不管那么多。

2、创建一个编辑笔记的activity和对应的xml,新建的activity要在androidManifest.xml文件里面注册。然后在自建Activity的标签里面添加android:windowSoftInputMode=“adjustResize”,这样弹出输入法的时候就不会遮挡文本输入框。(这行代码意思作用是当弹出输入法时以更小的布局去显示界面,相当于输入法把界面挤上去了。还有一个adjustPan,能使当前内容自动移动避免被输入法遮挡)。xml文件里面很简单就一个EditView和一个Button,EditView里面填入的提示文字用android:hint,这样输入时就不用像android:text一样再删一次提示文字了。然后设置android:inputType=“textMultiLine” android:singleLine=“false”,这样就可以像文本文档一样一直输入多行了。然后背景色android:background设置一个颜色以区分,虽然简陋但还是要有个样子哈哈。以前设置颜色的时候都要去网上搜颜色代码,这次才发现这一行设置左边行数旁边会有一个小颜色框显示的当前颜色,点一下就可以调颜色了。我就说这么功能全面的开发软件怎么会没有调色板呢!

%title插图%num
然后就是Button的点击事件了,首先判断EditView里面不为空,然后就是痛苦的数据库环节。但好在AndroidStudio里面自带的SQLite操作简单且方便,提供了一系列方法避免了直接写sql语句。

3,要操作数据库,首先就要先创建一个类继承SQLiteOpenHelper类,看名字就知道,数据库开启助手,专门用来进行数据库操作的类,虽然我了解的不多,但是要进行数据库操作应该是离不了它了。创建好我的NoteDatabasehelper后,首先当然是实现父类的两个抽象方法,一个onCreate(SQLiteDatabase db){},一个onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){}。前一个当然就是建表等sql语句的执行地方了。后面一个则是升级数据库用的,只要传进去的新版本号大于老版本号,就可以在里面执行升级逻辑。然后我们还要一个构造函数,传一个Context对象进来。这个Context我搞得不是很清楚,反正就是到处都有用。然后构造方法里面会有一个父类的构造实现,传进四个参数,一个就是Context,然后数据库名,factory(一般都为null),数据库版本号。数据库名和版本在前面定义一个static final的就好了,然后再onCreate里面执行建表语句。这样当我们调用这个构造方法的时候就会建库,然后会自动调用onCreate方法建表(这里我是自己写的建表语句然后在onCreate里面执行的,之前老是建库建表傻傻分不清楚)。但是如果后续想要再加表的话貌似会失败?因为数据库已经存在,不会再调用onCreate方法了,这里就需要onUpgrade方法来更新数据库了。

4、写好NoteDataBaseHelper类以后,我们就可以在之前的Activity的点击事件里面调用了,首先当然是new 一个NoteDatabaseHelper类,传进一个当前的Context对象(当前Activity.this),然后就是声明一个SQLitDatabase对象 = 我们new出来的NoteDataBaseHelper对象.getReadableDatabase()。一直到这里数据库才算真正创建出来。这里注意其实有两个方法都可以创建数据,即getReadableDatabase()和getWritableDatabase()。这两个都可以创建数据库,数据库如果已经存在就会自动打开数据库,然后返回一个可对数据库进行操作的对象。**区别在于当数据库不可写入(如磁盘满了)的情况下,getReadableDatabase()方法返回的对象将以只读的方式打开数据库,而getWritableDatabase()会出现异常。**这里我使用的前者。然后就是往表里存数据了(建库的时候表就已经一起建好了)。存数据当然也是有快捷方法的。

创建好数据库之后可以setting-plugins-marketplace里面搜索database navigator插件并下载,再重启,然后就可以用这个工具查看数据库了。具体步骤首先要点击view-tools windows-device file explorer,在右边弹出的窗口浏览设备文件,data-data-包名-databases下面会有一个database文件,右键另存为,然后在Android Studio*左边应该会有一个DB Browser,点进去然后点击绿色加号,浏览刚刚存储database文件的目录打开文件就可以了。

5,首先new出一个ContentValues对象,这个对象会帮助我们存储数据到时候一并插入表。用.put()方法向对象里面传入数据,这里有两个参数,前一个是String类型的,即插入表中的数据的key名,后面一个则是对应的value了。这里我传入了两个,一个是获取的EidtView里面的数据,对应我表里的note,第二个是当前时间,对应表里的time。(我的表里就三项数据,id,note和time)。再然后就是用之前get的数据库操作对象的insert方法插入数据,insert方法有三个参数,*个表名,第二个不知道是啥填null,第三个就是ContentValues对象了。然后我调用了一个startActivity方法跳回了MainActicity界面,这样就直接刷新MainActivity界面了,就能直接看到笔记的显示记录。

%title插图%num
6,想要在主界面看到笔记记录,当然不可能就这样就好了,MainActivity的界面都还没做呢!到activity_main.xml文件里面,会看到已经有一些代码了,这是这个模板自带的,把没用的去掉(我不记得这个xml有没有没用的组件了,反正留一个右下角的小圆按钮就行了)。然后在*上面写一个ListView就ok了。ListView的属性里面加一个id,高度选匹配内容wrap_content,然后添加上下左右的约束就好。*好用layout_marginXXX加点边距,显得好看一点。这里有一点,上边距(marginTop)要设一个合适的数值,因为应用的label会占用屏幕上方部分空间,如果上边距不设或者设的太少,后面会发现*行视图少了一截。这个之前看到过有解决方式但是我没有去深究,在这里记一下!

7、然后就是很很很很让人头疼的ListView的使用了,我是在网上看了好多别人做的,越看越头疼,可能是我脑子不太好使吧。反正*后我是照着实体书(《Android*行代码》)的入门再结合网上别人做的才整出来。

这个是先写了一个存一条数据的bean类,里面有两个属性即note和time。然后在MainActivity里面声明一个ArrayList数组,数据类型存的这个bean类的对象(我写的类名为ItemInfo)。然后再写了一个initItemInfo()方法,故名思意用来向ArrayList里面存储数据。说到数据当然还是要数据库操作了。老样子NoteDatabaseHelper DBHelper = new NoteDatabaseHelper(this);
SQLiteDatabase db = DBHelper.getReadableDatabase();获取数据库操作对象实例之后,声明一个Cursor对象存储db.query()方法查询出来的数据。这个query方法应该是数据库增删查改里*复杂的了,*少都有七个参数当然这里我们查询整张表只需要*个参数填表名,其他统统null就好。但还是要说一下其他几个参数,后面有的会用到。第二个是columns,即指定查询的列名,第三个是seletion,选择条件,相当于where,填入比如note=? , ?当然就是占位符,第四个就是选择条件的数据,用来替换占位符。

获取到数据库的数据之后,就是要将之存入ArrayList里面了。先if(cursor.moveToFirst)判断一下,里面好像是让cursor从*行开始读取?实体书上用的是kotlin,我记得kotlin的if与java的if用法好像是基本相同的,所以我就照着写了,具体为什么判断一下,暂时不了解。然后在if里面做一个do{}while()循环。循环里面调用ArrayList的add方法,循环存入ItemInfo对象,即add(new ItemInfo( cursor.getString(cursor.getColumnIndex(“note”)) , cursor.getString(cursor.getColumnIndex(“time”)) ))。这里忘了说了,我的ItemInfo的构造方法传了note和time进去的。然后就可以循环把所有的item存入ArrayList里面了。每一条item信息都会分别显示在listview里面的每一个item组件里。

%title插图%num
8、那么要怎么显示呢?首先当然是为item组件创建一个属于它自己的界面,即新建一个xml文件,这个文件也不需要很复杂,只需要两个Textview,分别展示记的内容和记录时间,以及给它限制一个高度。然后,很重要!我们需要一个装载器Adapter,负责将item组件一个个装载进ListView的展示列表。我是做完再回头看才终于了解了一些ListView和装载器的关系的。之前一直被几个实体类,xml,实现类砸的头晕。

这里我先写了一个NoteAdapter的实体类继承系统的Adapter,这里有好几个可选,我用的是ArrayAdapter。然后重写构造函数,我选的*后一个,三个参数。*个context没什么好说的,第二个是一个int类型的resource,待会儿用来传入之前写的那个属于item自己的xml文件,也是这里我才知道,原来R.layout.组件文件ID原来是int类型啊!*后一个参数List类型当然就是用来传入之前存储数据的ArrayList了。然后重写getView方法,也是三个参数,这里并不需要我们动手调用这个方法,我也没去弄明白参数意思。在这个方法里面,先声明一个View view= LayoutInflaterfrom(context).inflate(resource, parent, false);resource就是刚说的要传入xml文件的。另外两个不懂,跟着写的。这样item的组件布局就被填充进装载器了。然后我们就可以声明TextView把item组件里的两个TextView根据id声明出来。如TextView noteInfo = (TextView)view.findViewById(R.id.novteInfo);跟在MainActivity里面声明组件也差不多。然后要声明Item itemInfo = (ItemInfo) getItem(position);所以这里还得重写一个getItem()方法,position指的是当前item在ListView中的坐标位置(从0开始),getView会传一个进来。再就是setText将对应item的内容显示出来了。这里判断一下,如果note大于一定长度就只显示一截,毕竟空间有限嘛!*后return view。

9、接下来就是在主界面显示条目了。首先执行一次initItem方法把数据都依次加载进ArrayList里面来,然后new一个刚刚写的装载器adapter的实例,再执行listview的setAdapter方法,传入一个adapter参数进去。(这里可能要转一次型,跟着代码提示就行)。然后运行app就可以看到笔记记录都加载进来了

%title插图%num

10、光能显示记录可还不行,我们要给他一个点击事件和一个长按点击事件。点击事件用来进入界面修改笔记,这里我用的笨方法,直接再写了一个UpdateNote的Activity用来更新笔记。绑定的xml文件与之前用来写笔记的Activity的xml文件一样,只是去除了android:hintText文字提示,因为要在这个EditView里面添加note数据嘛。回到点击事件,跟Button差不多,调用listView.setOnItemClickListener方法,然后在里面new 一个OnItemCilickListener(),然后会自动重写一个onItemClick方法。这个方法会传进来四个参数,不用我们管,应该是点击的时候系统自动传进来的。其中pisition即之前所说的每个条目在ListView中的位置,这个我们会用得到。在这个方法里面,我先实例化了一个ItemInfo(即之前的条目信息bean类)对象,用来接listView的.getItemAtPosition()方法返回的数据。这个方法即根据位置获取单个item信息,传进去position。然后强转一下。再然后要用到一个Bundle函数。我们先实例化Bundle,然后用.putString()方法传入数据。这里传入的是一个键值对,key随便写就好了,后面根据这个key取出数据的。value即传入对应的数据,调用先前创建的ItemInfo对象的get方法获取数据。如:bundle.putString(“note”, ii.getNote());。那么用这个Bundle存入数据是干嘛的呢?

当点击ListView条目的时候,我们要跳到相应的Acitivity(即之前创建的updateNote)以修改笔记内容。要跳界面当然要用到Intent i = new Intent(MainActivity.this, UpdateNote.class);
startActivity(i);这两个方法。但是这样是无法把数据传过去。如果我们要在UpdateNote修改数据肯定要先获得相应的数据,这里就会有点麻烦,没有搜索条件怎样取到对应的数据呢?所以我们这里就可以用Intent的.putExtras()让Intent对象帮我们传递额外的数据信息过去。而这个方法传递的就是这个Bundle对象了。所以这里就是先在点击时获取相应条目的数据,因为有position这个参数和getItemAtPosition方法,获取起来就十分简单。然后将数据传到UpdateNote这个Activity。这样既可以直接获取note信息预显示在更新笔记界面,又能在之后更新数据库的时候有条件可用。

%title插图%num
11、然后来到UpdateNoteActivity界面,首先声明一个Bundle对象来接Intent额外传过来的Bundle参数。如:Bundle b = this.getIntent().getExtras();然后用Bundle的getString方法根据自己之前的key获取对应数据,再EditText.setText显示出来。这样当我们点进对应的条目时,就会预先显示之前写的笔记数据了。然后就是做笔记修改后的点击事件。在按钮点击事件里,先判断一下现在getText获取到的EditText的数据是否等于之前的值也就是Bundle传进来的那个数据。如果不等于就进行数据库操作修改note数据。
跟之前存储数据一样,先打开数据库,再new一个ContentValues对象,把修改后的数据put进去,列名一定要跟数据库一样。再然后根据bundle传进来的数据选一个有代表性的作为条件(我用的time,因为肯定不会重复。)。调用db.update方法,传入四个参数:表名,ContentValues对象,条件(time=?),替换占位符的数据(这个一定要看清是String数组,要new一个然后把time数据放进去)。然后就更新成功了。跳一个界面回到MainAcitivity,这样主界面会再加载一次,就能实时更新ListView的条目信息了。(这里如果不跳界面,手机手动返回不知道能不能达到实时更新ListView信息的效果,感觉应该是可以的)。

%title插图%num
12、*后就是长按点击事件删除内容了,ListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener()),然后也会自动重写一个OnLongClick方法。在这个方法里,首先要实例化一个可互动的警告框,这里我使用的AlertDialog.Builder,实例化时照例传入一个当前Context对象。这里这个Builder我看别人的资料说是一种设计模式,当你想用一个控件或者对象却又无法直接new出来时,那么这个对象多半就是用到了Builder模式,至于为什么不能直接new出来,一般可能是太过复杂吧(Builder详解)。new出这个对象后,调用setTitle和setMessage两个方法为警告框添加标题和警告内容。然后调用setPositiveButton做确认事件,setNegativiButton做取消事件。这两个事件都要传入一个text,即按钮的内容(yes/no),然后new一个DialogInterface.OnClickListener()。它会自动重写一个onClick方法,在取消事件setNegativeButton的OnClick里面我们什么都不用写,或者可以写一个Toast弹出提示。而在确认事件setPositiveButto的OnClickn里面要进行数据库操作删除信息数据。这跟之前差不多,条件语句的条件通过获取当前item的ItemInfo对象然后get一个数据就可以。db.delete相比之前的更新操作db.update还要少了一个ContentValues参数。删除完以后要记得重新加载页面,即在执行一个获取页面的代码。但是这里要注意,在加载场景之前一定要将存储item信息的ArrayList通过removeAll方法清空,然后再重新initItemInfo往ArrayList里面存新的数据库信息再加载界面场景。不然会发现ListView之前的界面没删除,然后下面接着的又是新的删除指定信息后的item界面(就是会重复显示内容)。做完两个点击事件之后,记得要执行AlertDialog.Builder对象的create方法和show方法,不然警告框不会显示。*后return true。

%title插图%num

%title插图%num

13、*后有两个点要注意,我之前在adapter设置的是note数据超过十个字符时就省略后面的,但是实际过程如果一个字符换一行的话会导致该信息的item界面显示异常。还有一个问题就是如果有几十条几百条甚至更多的话,这个记事本是要一次性全部加载完再一次性装载进Listview的,这个的优化涉及到到了adapter的getView里面的convertView参数。这个参数好像是可以实现未显示的条目释放资源,当显示新的条目时自动实时加载内容。这样可以大大减轻资源负担。

14、到此一个简单的记事本就实现了,能力有限只能做到这样了。说实话确实费了我很大功夫,结果回头再看发现其实也没我做的时候想的那么难。主要还是自己平时积累太少,很多东西照着别的人用一遍就不管了,这样的话根本就得不到任何收获。个人感觉还是要多写博客,虽然记的东西可能不是什么高技术含量的东西,但是过一遍总归能加深印象,下次再做就算忘了,照着自己写的东西看也比看别人的东西更容易也更有成就感。因此,这次特地下决心一定要把这个博客写出来,这可能比我目前为止其他的博客加起来还长了。可能本文排版做的不好,写的很乱废话也多,不过这个初衷本来就是为自己所写的,所以也就不管那么多了。

如果有幸被各位大佬看到了,也欢迎指点一二。

今天解决的当少于十个字符换行会导致item界面显示出问题的问题。加一个判断就好了,判断要显示的字符是否存在回车,存在就分割字符串,再拿出分割出来的*个字符串判断长度是否大于预定要显示的长度。

关于装载器里的view复用,只需要在给view赋值时判断是否为空,为空则创建。
给每个view传一个id方便识别:

%title插图%num

页面加载完毕:

%title插图%num

当往下翻页时,*开始id为0的view被向上遮挡后又转到下面来显示下一条信息:

%title插图%num

简易安卓APP项目

简介
现在来分享期末做的安卓大作业——生活百科。
本项目只是单纯的一个大作业,没有考虑实际的需求,所以有设计不合理的地方,请见谅。
这个项目有三大功能(因为是使用了侧边栏所以是可以继续往里面添加功能的),首先有单词查询,其次是天气查询,后来是机器聊天功能。单词查询是使用了扇贝单词提供的免费API;天气查询是使用了聚合数据提供的天气API,这里需要注册使用(有限的免费使用);机器聊天是使用了图灵机器人的API。所以,总结一下,这里我的主要工作不会很多,主要是调用API然后进行数据的分析和显示工作。
本次使用的IDE是idea(Android studio)。

文章的*后将会给出源码,但是一些API接口需要自己去申请Key

在项目开始前的学习阶段
安卓基础入门 http://www.runoob.com/android/android-tutorial.html
安卓省市联动(天气的位置选择) http://blog.csdn.net/qq_20521573/article/details/51914180
安卓fragment的使用 http://www.jikexueyuan.com/course/708.html
机器聊天界面的HTML源码 http://www.lanrenzhijia.com/js/3930.html
以上便是我着手项目的累积步骤,希望有帮助

1. 项目准备阶段
1.1. 新建项目
使用Android Studio(IDEA)新建安卓项目->选择SDK版本(Android 4.0为好)->选择Navigation Drawer Activity模板->完成项目创建。项目新建之后就会得到我们基本的界面模板了。而后,只需要删除右侧边和底部的元素就可得到下图所示的界面

%title插图%num
当然,这里使用Android ADT也是可以的,不过好像新建出来的模板不一样,比较丑一点(自己对于Android的UI不太行),所以就使用了Android Studio。

1.2. 添加依赖
由于需要使用省市联动的功能,所以需要添加外部依赖,在上面给出的链接上有详细说明,因此需要在app文件夹下的build.gradle里面的dependencies加入
compile ‘com.contrarywind:Android-PickerView:3.2.5’//自定义控件
compile ‘com.google.code.gson:gson:2.7’//解析JSON

做完了准备工作之后就进入主题

2. 单词查询功能
考虑文章的篇幅过长,所以有些详细的过程可能会省略(像界面的修改工作等等)。

%title插图%num

当我们输入了单词之后点击查询是需要访问网络的,这里采用了异步的任务机制去访问网络并且得出结果,部分代码如下:
//这是继承OnQueryTextListener 的内部类,用于处理搜索框监听事件
public class SearchViewClickListener implements SearchView.OnQueryTextListener {

@Override
public boolean onQueryTextSubmit(String s) {
if (!lastSearchResult.equals(s)){//判断上一个结果和目前查询的是否相同
System.out.println(“上个结果:”+lastSearchResult);
SearchWordTask task = new SearchWordTask();//新建查询任务
task.execute(“https://api.shanbay.com/bdc/search/?word=”+s);//访问网络的地址
searchResult.setVisibility(View.VISIBLE);//设置查询结果的TextView可见
}
lastSearchResult = s;//更新*后查询结果
System.out.println(“*新结果:”+s);
return true;
}

@Override
public boolean onQueryTextChange(String s) {
return true;
}
}

在访问网络而后传回数据我这里将这个功能提取到了一个工具类,代码如下:

public class HttpUtil {

/**
* 获取访问网络后传回的数据
* @param urlString URL
* @return String
*/
public static String getJSONResult(String urlString){
try {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(“GET”);
conn.setRequestProperty(“accept”, “*/*”);
conn.setRequestProperty(“connection”, “Keep-Alive”);
conn.setRequestProperty(“user-agent”, “Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36”);

InputStream is = conn.getInputStream();
byte[] buff = new byte[1024];
int hasRead;
StringBuilder result = new StringBuilder(“”);
while ((hasRead = is.read(buff)) > 0) {
result.append(new String(buff, 0, hasRead));
}
return result.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

在上面中SearchWordTask是一个继承了AsyncTask的内部类,主要是实现了访问扇贝提供的英文单词查询API,部分代码如下:

class SearchWordTask extends AsyncTask<String, Void, String> {

@Override
protected String doInBackground(String… arg0) {
//arg0是执行AsyncTask的execute函数传入的可变参数
//这里arg0[0]是”https://api.shanbay.com/bdc/search/?word=”+s
return HttpUtil.getJSONResult(arg0[0]);
}

@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (result == null || “”.equals(result)){
Toast.makeText(getActivity(), “查询出错!”, Toast.LENGTH_LONG).show();
}else
fillResultForJSON(result);

}

/**
* 解析查询的结果
* @param JSON JSON数据
*/
private void fillResultForJSON(String JSON){
try {
JSONObject object = new JSONObject(JSON);
if (“SUCCESS”.equals(object.getString(“msg”))){
final JSONObject dataObject = object.getJSONObject(“data”);
paraphrase.setText(“基本释义:”+dataObject.getString(“definition”));
final String uk_audio = dataObject.getString(“uk_audio”);
final String us_audio = dataObject.getString(“us_audio”);
detail.setOnClickListener(new View.OnClickListener() {
//如果详细释义按钮点击则访问如下页面
@Override
public void onClick(View view) {

webView.setVisibility(View.VISIBLE);
webView.loadUrl(“https://www.shanbay.com/bdc/mobile/preview/word?word=”+lastSearchResult);
webView.setWebViewClient(new WebViewClient());
}
});
//以下是发音按钮被点击时的监听事件
UKButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
//使用UKMediaPlayer 播放声音
UKMediaPlayer = new MediaPlayer();
UKMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
UKMediaPlayer.setDataSource(uk_audio);
UKMediaPlayer.prepare(); // 这个过程可能需要一段时间,例如网上流的读取
UKMediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
});

USButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//同上面的播放声音步骤

});

JSONObject pronunciations =dataObject.getJSONObject(“pronunciations”);
UKPronunciation.setText(“英式发音:[“+pronunciations.getString(“uk”)+”]”);
USPronunciation.setText(“美式发音:[“+pronunciations.getString(“us”)+”]”);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}

以上便是单词查询的主体功能了,当然,考虑文章篇幅所以不能一一解析。

3. 天气查询功能
在天气查询功能中比较重要的就是使用了网友所写的省市联动功能,这里请参考上面链接中的文章。下面我将不会涉及这方面的讲解,这里主要是说一下访问聚合数据所提供的API,这个功能的效果如下图:
%title插图%num

%title插图%num

在上面功能中的业务代码主要是跟上面的单词查询差不多,都是使用了异步查询,由内部类实现,代码如下:
class WeatherTask extends AsyncTask<String, Void, String> {

@Override
protected String doInBackground(String… arg0) {

try {
return HttpUtil.getJSONResult(arg0[0] + URLEncoder.encode(arg0[1], “UTF-8”));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (result != null){
parseWeatherJSON(result);
}
}

/**
* 根据JSON数据解析
* @param result JSON
*/
private void parseWeatherJSON(String result){
try {
JSONObject object = new JSONObject(result);
if (object.getInt(“error_code”) == 0){
JSONObject resultObj = object.getJSONObject(“result”);
JSONObject todayObj = resultObj.getJSONObject(“today”);
String weatherResult = “温度:”+todayObj.getString(“temperature”)+”\n”;
weatherResult += “天气状况:”+todayObj.getString(“weather”)+”\n”;
weatherResult += “风向:”+todayObj.getString(“wind”)+”\n”;
weatherResult += “穿衣建议:”+todayObj.getString(“dressing_advice”);
todayWeather.setText(weatherResult);
}else {
todayWeather.setText(“请求出错!”);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}

4. 机器聊天功能
机器聊天功能是使用了HTML页面来进行人机交互,所以,这里基本上没有涉及到Java上面的问题,主要是加载HTML页面以及开启JavaScript功能,代码如下:

WebView webView = (WebView) rootView.findViewById(R.id.chat_robot);
//加载本地的HTML页面(将文件置于src/main/assets/) webView.loadUrl(“file:///android_asset/chat_robot.html”);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());

功能的实现效果如图:

%title插图%num

当然了,要是用以上的聊天功能需要注册图灵机器人申请Key,源码中不会提供Key,所以请自行注册。
在接收图灵机器人返回的数据时,它会有代码来确定返回的是什么类型,所以,需要我们来判断类型来进行相应的解析(这里的JS代码就不贴出来了)。

5. 总结
这次的是一个安卓的期末作业,但是却没有很认真的对待的样子,而且项目还是不完善的,比如,在查询了单词后做其他功能的操作再次返回单词查询功能则之前的数据不能够保存,用户体验不好。这里当然我也知道一些解决的方案,保存当前的fragment状态,但是,我还是偷懒了。懒惰真的是宿敌。
文章上写的详细程度不够,但是主要的代码都已经写出。我知道,有时候解决一个功能并不是功能本身,而是要防止功能附带出来的bug,当然了,这就是我的经历。
还有就是可能我对于面向对象还是理解上有所偏差,对于抽象还是做得很烂,接下来希望看看别人的源码来改善这个问题。

Android Studio 模拟器卡慢、占内存解决方法

Android Studio 模拟器卡慢、占内存解决方法

Android Studio 模拟器卡慢、占内存解决方法
在使用Android virtual device来进行调试运行时会比较慢,性能也不是很好。原生模拟器比较吃电脑配置,常常几分钟都跑不完一个程序。

这里推荐几种解决方法
1.本身自带的Android virtual device(intel处理器)
2.本身自带的Android virtual device(18年更新支持的AMD处理器)
3.使用安卓真机调试
4.使用Genymotion模拟器
在我学习Android中,我尝试了几种模拟器的使用方法。希望在你选择和配置模拟器时有所帮助:
###

一. Android virtual device(自带模拟器Intel)
这里讲一下Android Studio 原生模拟器的改善吧。为 Android 模拟器配置硬件加速,Android 模拟器可以使用硬件加速功能来提升性能,有时甚至可以大幅提升性能。
1.创建新的AVD时修改Graphics Rendering选项

创建新的 AVD 官方链接:
https://developer.android.google.cn/studio/run/managing-avds#createavd.

在 AVD 管理器中创建 Android 虚拟设备 (AVD) 时,你可以指定模拟器应使用硬件还是软件来模拟 AVD 的 GPU。
Graphics Rendering即图形渲染方式,如下有三种可选:自动选择、硬件、软件。右侧标注处解释了其优缺点:
硬件:使用你电脑的显卡得到更快的渲染。
软件:使用电脑中的软件来模拟图形,以此解决电脑显卡的问题。
模拟器将使用软件加速(使用计算机的 CPU)来模拟 GPU 处理。

%title插图%num

默认情况下,模拟器会根据你的计算机设置来决定是使用硬件图形加速还是软件图形加速。建议使用硬件加速,因为硬件加速通常速度更快。

2.在 Windows 上使用 Intel HAXM 配置虚拟机加速
计算机必须满足以下要求,您才能安装和使用 Intel HAXM:

启用了虚拟化技术 (VT-x)、Intel EM64T (Intel 64) 功能和 Execute Disable (XD) Bit 功能的 Intel 处理器

64 位 Windows 10、Windows 8 或 Windows 7(或者 64 位处理器上的 32 位版本的操作系统)

要在 Windows 10 或 Windows 8 上使用 Intel HAXM,您必须在 Windows 控制面板中关闭 Hyper-V。

注意:安装某些软件可能会重新开启 Hyper-V。在可能的情况下,Android Studio 会尝试检测是否重新启用了 Hyper-V,并显示一个可让您再次关闭 Hyper-V 的选项。

①打开 SDK 管理器。

Android Studio SDK 管理器链接:
https://developer.android.google.cn/studio/intro/update#sdk-manager.

②点击 SDK Update Sites 标签,然后选择 Intel HAXM。
③点击 OK。
④下载完成后,运行安装程序。通常,您可以在以下位置找到安装程序:sdk\extras\intel\Hardware_Accelerated_Execution_Manager\intelhaxm-android.exe
⑤使用向导完成安装。

2.设置你的电脑在使用Android Studio时使用独显
如下图,我的电脑是NIVIDA,就以NIVIDA为例。打开NIVIDA控制面板,选择管理3D设置,在界面中间选择程序设置,然后把Android Studio添加进来。下方图形处理器选择高性能NIVIDA处理器。

%title插图%num

再点击设置Surround、PhysX配置,选择独显。如下图,我的是Geforce GTX1050Ti。

%title插图%num

做完以上修改以后,以高性能显卡运行AVD,会有一定的流畅性提升。但是同样的,长时间高性能使用显卡会出现意想不到的问题。(我在配置的时候发热严重、风扇如战斗机一样开转,我电脑太渣了!)

二. Android virtual device(自带模拟器AMD)
对于使用英特尔 x86 处理器的设备来说,默认情况下 Android 模拟器将继续使用硬件加速执行管理器技术 (Intel HAXM)。该技术是英特尔开发的一款较为成熟的开源虚拟化技术解决方案。此外,由于英特尔在创新研发方面的持续投入,HAXM 依旧是目前市面上*快的 Android 模拟器加速技术。

若您的设备使用的是 AMD 处理器,需同时满足以下条件:

AMD 处理器 —— 推荐使用 AMD 锐龙系列处理器;

Android Studio 3.2 Beta 或更高版本,点击前往 Android Studio 预览版下载页面;

Android 模拟器 v27.3.8 +,点击前往 Android Studio SDK 管理器页面下载;

x86 Android 虚拟设备 (AVD),创建虚拟设备;

Windows 10 Version 1803 四月更新版;

①在 Windows 桌面上,右键点击 Windows 图标,然后选择应用程序和功能。
②在相关设置下,点击程序和功能。
③点击打开或关闭 Windows 功能。
④选中 Windows Hypervisor Platform。

%title插图%num

⑤点击 OK。

⑥安装完成后,重启计算机。

三. 使用真机调试
Android Studio是支持使用自己的安卓手机进行调试的。真机调试性能不错,效果较好。需要用数据线将手机与电脑连接。如下图我示范一下真机调试过程:
1.设置开发者选项
在设备上,打开设置应用,选择开发者选项,然后启用 USB 调试。
2.USB 驱动程序。
在 Windows 上开发并且想要连接设备进行测试,则需要安装合适的 USB 驱动程序。
USB驱动链接:
https://developer.android.google.cn/studio/run/oem-usb.

3.在一个创建好的项目中,在上方工具栏中找到APP这个按钮,选择Edit Configurations。如图所示:

%title插图%num

4.在弹出的对话框中,找到“ Deployment Target Options” 并选择“ USB Device ”,然后点击确定。如图所示:

%title插图%num

做完以上操作后,就可以在真机进行调试了。*次执行需要一些时间,后续就快起来了。缺点就是需要随时携带数据线,然后真机调试的配置也较麻烦。但是真机的效率是真的高,很多问题也是在真机上更容易发现。真机调试我还是很推荐的。

四. Genymotion模拟器调试
这是我*推荐的,也是目前比较受欢迎的虚拟机。Genymotion虚拟机可以模拟Galaxy、SAMSUNG、SONY、HTC等主流手机,效率很高,使用简单方便。
1.安装Genymotion前需要安装VirtualBox(官网有提供VirtualBox和Genymotion整合包)
下载地址:https://www.virtualbox.org/wiki/Downloads
傻瓜式安装,不赘述。
2.安装Genymotion
下载地址:https://www.genymotion.com/download
如图,有withVirtualBox版本,我们选择这个合集包,就不用单独下载上面说的VirtualBox。

%title插图%num

安装完后在Genymotion中新建一个模拟器。

%title插图%num

模拟器建完后,保持模拟器开机状态。我的Android Studio是可以自动识别到Genymotion的模拟器的。

%title插图%num

%title插图%num

如果你的无法自动识别到,可以下载Android Studio中Genymotion的插件。左上角File–>setting–>Plugins–>Genymotion

%title插图%num
总结
由于我正在学习Andrio开发,经常需要调试。刚开始学习的时候,确实使用的是AVD,后面写得多了,比较卡顿。老师、同学都推荐使用Genymotion,它占用内存不多,效率比较高,使用便捷,所以一直都在用它。后期如果有实训项目,可能会考虑更多地使用真机开发,毕竟真实的环境还是有优势的。

Android Studio模拟器联网

利用Android Studio自带的模拟器联网。
Android Studio自带的模拟器本身默认是不能上网的,因为默认DNS为10.0.2.3,使用这个DNS是不能上网的。

网上很多帖子已经给出了解决办法,但是会遇到下面这种情况:

%title插图%num

通过继续查询得知:通过shell命令设置(获取)IP、网关、dns信息,需要获取root权限。

下面给出打开Android Studio自带的模拟器联网功能的完整步骤(亲测可行):
首先把模拟器打开,然后需要把SDK文件夹下的platform-tools添加到系统变量中,至于怎么添加系统变量,请自行百度。

怎么检查自己是否添加成功呢,在cmd中输入:adb shell 回车,如果是下图的话代表添加成功。如果成功的话,输入exit即可退出adb。

%title插图%num

重点来了,进入到cmd后,需要输入adb root 将模拟器root一下,才能更改DNS。然后再输入adb shell。

接下来输入getprop获取模拟器系统属性。找到[net.eth0.dns1]这一项,发现后面为10.0.2.3,接下来我们输入setprop net.eth0.dns1 192.168.1.1.后面的ip也可为其他,不过我没尝试。(没有[net.eth0.dns1]的话因为系统是Android9.0及以上,建议换到8.0及以下)

网上很多帖子是[net.dns1],不过我的模拟器上面dns1前面有eth0,这个大家根据自己的模拟器情况决定。

然后打开模拟器的数据连接。即可使用模拟器的谷歌浏览器上网。

网友出现的问题:

1、adbd cannot run as root in production builds
解决办法:原因是模拟器的Android系统是Google Play,应该选用Google API。

%title插图%num %title插图%num
PS:我记得我之前root过一次,但好像是每次打开电脑要修改模拟器重要配置的话就需要root,总之一句话,root就完事了。

 

翠花上酸菜丶:adbd cannot run as root in production builds
翠花上酸菜丶回复BaldStrong:嗯嗯,谢谢
BaldStrong回复:找到原因了 https://icetutor.com/question/adb-root-is-not-working-on-emulator-cannot-run-as-root-in-production-builds/
BaldStrong回复:给你找了几个教程不知能否帮到你 https://blog.csdn.net/hlllmr1314/article/details/52217128 https://www.jianshu.com/p/e0646fbb72ed
AnyPlayer~:这样重新打开模拟器都需要进行设置,有没有其他的方法可以一劳永逸 ?
方峰同学回复:这个我按照文中方法试了,只需要设置一次,重启也还有效。
BaldStrong回复: 这个我之前就设置了一次,现在不搞Android了,我刚查了下,你可以试试把模拟器adb永久root后再设置dns,我没试,仅提供一个思路。
AnyPlayer~回复BaldStrong:没有,我重新启动模拟器就失效了
BaldStrong回复: 如果不删除这个模拟器的话,只设置一次就行。
weixin_41232585:我是Windows10,不知道是否和系统有关,getprop后能得到很多数据,但根本看不见模拟器的dns,root也不行,更别说设置了
BaldStrong回复shirwee_xu:没有[net.eth0.dns1]的话因为系统是Android9.0及以上,建议换到8.0及以下
shirwee_xu回复BaldStrong: 同没有模拟器的dns
BaldStrong回复Shirlyna:输入getprop,返回很多行,你仔细看一下有没有dns开头的,如果确实没有可以加我QQ给我截图看下:1421692663
Shirlyna回复BaldStrong:配置了sdk环境变量,进入adb一样没有dns
qq_42696259回复:我也是,根本看不见dns,呜呜呜
BaldStrong回复:你可以试着把SDK文件夹下的platform-tools添加到系统变量,不行的话百度下 进入adb 的步骤。
weixin_41232585回复BaldStrong:没加,在cmd用cd跳进去的,一样的效果吧
BaldStrong回复:你之前在Android Studio里面运行过模拟器吧?把SDK文件夹下的platform-tools添加到系统变量了吧?

Android Studio 模拟器的选择和安装

一、Android Studio 自带的AVD模拟器

Android Studio 程序可以在真机上调试运行,Android Studio 也提供了模拟器来调试运行,这时需要配置 AVD 来选择你调试程序的模拟环境。
%title插图%num1. 在 Intel CPU 的主机上启用 HAXM
在 Intel CPU 的主机上,为了加速AVD模拟器的运行速度,需要启用 HAXM 。如果在没有启用 HAXM 时就运行程序,调用AVD模拟器时会报如下错误:

emulator: ERROR: x86 emulation currently requires hardware acceleration!
Please ensure Intel HAXM is properly installed and usable.
CPU acceleration status: HAX kernel module is not installed!

出现此警告的原因是AVD模拟器调用X86架构的安卓虚拟机需要使用到Intel HAXM 引擎,而本机尚未进行安装导致。
此时,应该先进入 BIOS 启用 Virtualization Technology 选项。然后从 https://software.intel.com/en-us/android/articles/intel-hardware-accelerated-execution-manager/ 下载,也可以直接在 Android Studio 的 SDK 中下载,再安装即可。
%title插图%num
%title插图%num

2. 对于非 Intel CPU 的主机
对于非 Intel CPU 的主机或不能安装 HAXM 的主机(像我的云主机),则只能选择 arm 模拟器 或 选择其它第三方的模拟器 (像Genymotion)。
arm 模拟器的设置如下,不过一般情况下, arm 模拟器速度很慢 (像我的云主机启动它需要个吧小时),所以,应该尽量选用Genymotion等虚拟机。
%title插图%num

3. 设置
如果以上配置无误,就可以运行程序试试。如果出现类似如下面的警告:
emulator: WARNING: Requested RAM size of 1536MB is too large for your environment, and is reduced to 1152MB.
emulator: device fd:596
HAXM is not working and emulator runs in emulation mode
emulator: The memory needed by this AVD exceeds the max specified in your HAXM configuration.
emulator: AVD RAM size = 1152 MB
emulator: HAXM max RAM size = 1024 MB
emulator: You might want to adjust your AVD RAM size and/or HAXM configuration to run in fast virt mode.
Cannot set up guest memory ‘pc.ram’: Invalid argument
警告提示模拟器RAM过大,需要在 AVD 中将模拟器的RAM改为512~1024,这样模拟器才能正常启动。
%title插图%num

 

二、android studio外挂Genymotion模拟器

Genymotion虚拟机可以模拟Galaxy、SAMSUNG、SONY、HTC等主流手机,运行速度快,是现在广受欢迎的虚拟机。Genymotion依赖 VirtualBox 加载手机虚拟机。
1. 安装 VirtualBox
从 https://www.virtualbox.org/wiki/Downloads
下载 VirtualBox ,再安装,过程从略。
2. 安装 Genymotion
从 https://www.genymotion.com/download/ 下载 Genymotion ,下载是要用邮箱注册账号后才能下载。
官网提供了两个版本,带有VirtualBox的Genymotion整合包和不带VirtualBox的Genymotion安装包,可以根据需要下载相应版本安装。安装过程从略。
3. 下载 .ova 虚拟设备
启动 Genymotion,添加对应手机的 Virtual device ,如下图。
%title插图%num

不过如果因为墙的阻挡,也许会下载出错。如果Genymotion添加 Virtual device 时出现如下的错误:

Failed to deploy virtual device.
Unable to create virtual device:
Connection timeout occurred.

那就只好手工下载 Virtual device 的离线.ova文件了。
方法一:下载官方离线.ova文件
即使刚才下载失败了,但是它已经在 “C:\Users\用户主目录\AppData\Local\Genymobile\genymotion.log” 文件里保留了官方.ova文件的地址,打开该文件,找到类似 “http://files2.genymotion.com/dists/6.0.0/ova/genymotion_vbox86p_6.0_160114_090449.ova” 的路径,即您想要下载的.ova镜像文件URL;复制到浏览器或用第三方下载工具下载该文件。
方法二:贴吧下载
有些网友下载了一些.ova文件,大家可以在网上搜搜,也可以到百度贴吧里找找。像下面的地址里就有一些:
链接: http://pan.baidu.com/s/1jHfuJNg 密码: 222g
4. 安装下载的.ova 离线文件
下载好后拷贝到 “C:\Users\用户主目录\AppData\Local\Genymobile\Genymotion\ova” 文件夹下。
然后打开 VirtualBox -> 管理 -> 导入虚拟电脑 (快捷键:Ctrl+I ) -> 选择下载好的 .ova 文件 -> 下一步 -> 导入 。导入完成之后就OK了。这时候打开Genymotion就看到可以使用了。
5. 将Genymotion加入 Android Studio
如下图,在 Android Studio 的 Setting 中加入 Genymotion 的插件即可。
%title插图%num
%title插图%num
%title插图%num

6. 运行 Genymotion
现在可以点击 Android Studio 上的 Genymotion 图标来运行虚拟机,如果此时出现如下错误说明需要重装或升级显卡驱动。
make sure that your video card supports OpenGL 2.0 and update the drivers.
%title插图%num

五、运行
正常运行后,可以通过虚拟机的
Settings -> Language & input -> Language -> 中文(简体) 将虚拟手机中文化。
%title插图%num

然后,可以在虚拟机里调试程序了。
%title插图%num
 

三、真机模拟器

对于我这刚入门的菜鸟来说,我在配置Android的开发环境中,遇到的问题实在是太多了,都快花费我一个星期的时间了,在这期间出了*下载SDK之外,其他的世间都在搞Android virtual device出现的问题,由于本人的能力有限以及电脑配置的不给力,Android virtual device中出现的问题始终都没有得到解决,后来看了网上很多人都在说使用Google自带的Android virtual device来进行调试运行时会比较慢,性能也不是很好,然后使用真机模拟的话速度是比较快的,效果也不错。那么接下来我要讲的就是如何在Android studio中使用真机进行调试以及在这过程中遇到的一个问题

 

首先,你要先创建好一个项目

%title插图%num

 

 

接着在工具栏中找到APP这个按钮,选择Edit Configurations

%title插图%num

 

 

在弹出的对话框中,找到“ Deployment Target Options” 并选择“ USB Device ”,然后点击确定

%title插图%num

 

以上配置完之后,我们就可以进行调试了,在工具栏中找到绿色的三角符号按钮(或者按快捷键Shift+F10)运行项目

%title插图%num

 

启动run后,软件开始生成apk安装包,并自动安装到手机上,*次执行的话会慢一些,往后就快了。然后我们识别出来的设备可以再Android Monitor这一栏中看到

%title插图%num

 

安卓手机也自动安装好app并自动执行

%title插图%num

 

 

 

在以上的操作过程中,出现了一个问题,在Android Monitor这栏中,没有识别出我们的手机设备,显示No Connected Devices

 

%title插图%num

 

出现以上的原因是电脑中的驱动没有安装好,Android studio不能识别出我们的设备,然而解决方法很简单,我们只需要这我们的电脑中安装一个豌豆荚就可以了,这个应用可以帮我们直接搞定设备驱动安装的问题。

单独启动android studio模拟器

单独启动android studio的模拟器,需要自己写一个脚本,需要两个命令

*个:进入emulator.exe的目录,比如我的目录,他就在你的SDK目录下的tools下

E:\learn\androidSdk\tools\emulator.exe
第二个:指定你要启动的模拟器,如Pixel_API_25

-netdelay none -netspeed full -avd Pixel_API_25

%title插图%num
所以在左面创建一个runEmulator.bat的文件,里面输入下面命令即可,其中Pixel_API_25是你自己要启动的模拟器名字,

E:\learn\androidSdk\tools\emulator.exe -netdelay none -netspeed full -avd Pixel_API_25
提示:所有创建的虚拟手机均在C:\Users\Amarao\.android\avd下

%title插图%num

Android Studio如何自动 import

Eclipse自动添加import语句, 使用Ctrl + Shift + o组合, 可以自动查找java的import语句进行添加;
Android默认是Alt+Enter单个添加import语句, 可以修改IDE, 使其自动添加, 所使用的java库;
位置: Files ->Settings-> IDE Settings-> Editor -> Auto Import

关于Auto Import的设置也有好几项,估计很多人看着也萌,我这边解释下,如果我解释了你还是觉得不懂,那你就不用管它各项是干啥的,直接都勾上就好了。如下图:

逐一解释下吧:

XML

  •  Show import popup,这个是用于编辑XML时,自动会弹出一个import的对话框,问你是否需要导入。

Java

  • Insert imports on paste:(All Ask None),这个其实就是你在复制代码的时候,对于导入的包是否需要进行询问的一个选项。
     All:选择这项的时候,你黏贴的代码,有需要导入的包名时,会自动导入,不会弹提示框
     ASK:选择这项的时候,你黏贴的代码,有需要导入的包名时,会弹提示框,问你要不要导入
     None:选择这项的时候,你黏贴的代码,有需要导入的包名时,不会弹提示框,也不会自动导入。
  • Show import popup:这个是和上面的Insert imports on paste是不同的项了哈,不要混一起,这个是指当你输入的类的声明没被导入时,会弹出一个选择的对话框。但是这边需要注意下,这个选项其实是有点问题的。不管你勾还是不勾,反正对话框是不会弹出来的,在你输完类名后,声明都自动导入了。所以我估计这个可能是Android Studio的bug。
  • Optimize imports on fly:这个其实和快捷键Ctrl+Shift+O/Ctrl+Alt+O是一样的,就是把不用的声明移除掉。
  • Add unambiguous imports on the fly:这个就是自动导入功能了,当你输入类名后,声明就被自动导入了。
  • Exclude from Import and Completion:这个其实就是你自定义import。可以不用关注,一般来说你是用不上的。

%title插图%num

 

Android安全攻防战,反编译与混淆技术完全解析(下)

混淆

本篇文章中介绍的混淆技术都是基于Android Studio的,Eclipse的用法也基本类似,但是就不再为Eclipse专门做讲解了。
我们要建立一个Android Studio项目,并在项目中添加一些能够帮助我们理解混淆知识的代码。这里我准备好了一些,我们将它们添加到Android Studio当中。
首先新建一个MyFragment类,代码如下所示:

  1. public class MyFragment extends Fragment {
  2. private String toastTip = “toast in MyFragment”;
  3. @Nullable
  4. @Override
  5. public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  6. View view = inflater.inflate(R.layout.fragment_layout, container, false);
  7. methodWithGlobalVariable();
  8. methodWithLocalVariable();
  9. return view;
  10. }
  11. public void methodWithGlobalVariable() {
  12. Toast.makeText(getActivity(), toastTip, Toast.LENGTH_SHORT).show();
  13. }
  14. public void methodWithLocalVariable() {
  15. String logMessage = “log in MyFragment”;
  16. logMessage = logMessage.toLowerCase();
  17. System.out.println(logMessage);
  18. }
  19. }

 

可以看到,MyFragment是继承自Fragment的,并且MyFragment中有一个全局变量。onCreateView()方法是Fragment的生命周期函数,这个不用多说,在onCreateView()方法中又调用了methodWithGlobalVariable()和methodWithLocalVariable()方法,这两个方法的内部分别引用了一个全局变量和一个局部变量。
接下来新建一个Utils类,代码如下所示:

  1. public class Utils {
  2. public void methodNormal() {
  3. String logMessage = “this is normal method”;
  4. logMessage = logMessage.toLowerCase();
  5. System.out.println(logMessage);
  6. }
  7. public void methodUnused() {
  8. String logMessage = “this is unused method”;
  9. logMessage = logMessage.toLowerCase();
  10. System.out.println(logMessage);
  11. }
  12. }

 

这是一个非常普通的工具类,没有任何继承关系。Utils中有两个方法methodNormal()和methodUnused(),它们的内部逻辑都是一样的,唯一的据别是稍后methodNormal()方法会被调用,而methodUnused()方法不会被调用。
下面再新建一个NativeUtils类,代码如下所示:

  1. public class NativeUtils {
  2. public static native void methodNative();
  3. public static void methodNotNative() {
  4. String logMessage = “this is not native method”;
  5. logMessage = logMessage.toLowerCase();
  6. System.out.println(logMessage);
  7. }
  8. }

 

这个类中同样有两个方法,一个是native方法,一个是非native方法。
*后,修改MainActivity中的代码,如下所示:

  1. public class MainActivity extends AppCompatActivity {
  2. private String toastTip = “toast in MainActivity”;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. getSupportFragmentManager().beginTransaction().add(R.id.fragment, new MyFragment()).commit();
  8. Button button = (Button) findViewById(R.id.button);
  9. button.setOnClickListener(new View.OnClickListener() {
  10. @Override
  11. public void onClick(View v) {
  12. methodWithGlobalVariable();
  13. methodWithLocalVariable();
  14. Utils utils = new Utils();
  15. utils.methodNormal();
  16. NativeUtils.methodNative();
  17. NativeUtils.methodNotNative();
  18. Connector.getDatabase();
  19. }
  20. });
  21. }
  22. public void methodWithGlobalVariable() {
  23. Toast.makeText(MainActivity.this, toastTip, Toast.LENGTH_SHORT).show();
  24. }
  25. public void methodWithLocalVariable() {
  26. String logMessage = “log in MainActivity”;
  27. logMessage = logMessage.toLowerCase();
  28. System.out.println(logMessage);
  29. }
  30. }

 

可以看到,MainActivity和MyFragment类似,也是定义了methodWithGlobalVariable()和methodWithLocalVariable()这两个方法,然后MainActivity对MyFragment进行了添加,并在Button的点击事件里面调用了自身的、Utils的、以及NativeUtils中的方法。注意调用native方法需要有相应的so库实现,不然的话就会报UnsatisefiedLinkError,不过这里其实我也并没有真正的so库实现,只是演示一下让大家看看混淆结果。点击事件的*后一行调用的是LitePal中的方法,因为我们还要测试一下引用第三方Jar包的场景,到LitePal项目的主页去下载*新的Jar包,然后放到libs目录下即可。
完整的build.gradle内容如下所示:

  1. apply plugin: ‘com.android.application’
  2. android {
  3. compileSdkVersion 23
  4. buildToolsVersion “23.0.2”
  5. defaultConfig {
  6. applicationId “com.example.guolin.androidtest”
  7. minSdkVersion 15
  8. targetSdkVersion 23
  9. versionCode 1
  10. versionName “1.0”
  11. }
  12. buildTypes {
  13. release {
  14. minifyEnabled false
  15. proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
  16. }
  17. }
  18. }
  19. dependencies {
  20. compile fileTree(dir: ‘libs’, include: [‘*.jar’])
  21. compile ‘com.android.support:appcompat-v7:23.2.0’
  22. }

 

好的,到这里准备工作就已经基本完成了,接下来我们就开始对代码进行混淆吧。

混淆APK

在Android Studio当中混淆APK实在是太简单了,借助SDK中自带的Proguard工具,只需要修改build.gradle中的一行配置即可。可以看到,现在build.gradle中minifyEnabled的值是false,这里我们只需要把值改成true,打出来的APK包就会是混淆过的了。如下所示:

  1. release {
  2. minifyEnabled true
  3. proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
  4. }

 

其中minifyEnabled用于设置是否启用混淆,proguardFiles用于选定混淆配置文件。注意这里是在release闭包内进行配置的,因此只有打出正式版的APK才会进行混淆,Debug版的APK是不会混淆的。当然这也是非常合理的,因为Debug版的APK文件我们只会用来内部测试,不用担心被人破解。
那么现在我们来打一个正式版的APK文件,在Android Studio导航栏中点击Build->Generate Signed APK,然后选择签名文件并输入密码,如果没有签名文件就创建一个,*终点击Finish完成打包,生成的APK文件会自动存放在app目录下。除此之外也可以在build.gradle文件当中添加签名文件配置,然后通过gradlew assembleRelease来打出一个正式版的APK文件,这种方式APK文件会自动存放在app/build/outputs/apk目录下。
那么现在已经得到了APK文件,接下来就用上篇文章中学到的反编译知识来对这个文件进行反编译吧,结果如下图所示:

%title插图%num

很明显可以看出,我们的代码混淆功能已经生效了。
下面我们尝试来阅读一下这个混淆过后的代码,*顶层的包名结构主要分为三部分,*个a.a已经被混淆的面目全非了,但是可以猜测出这个包下是LitePal的所有代码。第二个android.support可以猜测出是我们引用的android support库的代码,第三个com.example.guolin.androidtest则很明显就是我们项目的主包名了,下面将里面所有的类一个个打开看一下。
首先MainActivity中的代码如下所示:

%title插图%num

可以看到,MainActivity的类名是没有混淆的,onCreate()方法也没有被混淆,但是我们定义的方法、全局变量、局部变量都被混淆了。
再来打开下一个类NativeUtils,如下所示:

%title插图%num

NativeUtils的类名没有被混淆,其中声明成native的方法也没有被混淆,但是非native方法的方法名和局部变量都被混淆了。
接下来是a类的代码,如下所示:

%title插图%num

很明显,这个是MainActivity中按钮点击事件的匿名类,在onClick()方法中的调用代码虽然都被混淆了,但是调用顺序是不会改变的,对照源代码就可以看出哪一行是调用的什么方法了。
再接下来是b类,代码如下所示:

%title插图%num

虽然被混淆的很严重,但是我们还是可以看出这个是MyFragment类。其中所有的方法名、全局变量、局部变量都被混淆了。
*后再来看下c类,代码如下所示:

%title插图%num

c类中只有一个a方法,从字符串的内容我们可以看出,这个是Utils类中的methodNormal()方法。
我为什么要创建这样的一个项目呢?因为从这几个类当中很能看出一些问题,接下来我们就分析一下上面的混淆结果。
首先像Utils这样的普通类肯定是会被混淆的,不管是类名、方法名还是变量都不会放过。除了混淆之外Utils类还说明了一个问题,就是minifyEnabled会对资源进行压缩,因为Utils类中我们明明定义了两个方法,但是反编译之后就只剩一个方法了,因为另外一个方法没有被调用,所以认为是多余的代码,在打包的时候就给移除掉了。不仅仅是代码,没有被调用的资源同样也会被移除掉,因此minifyEnabled除了混淆代码之外,还可以起到压缩APK包的作用。
接着看一下MyFragment,这个类也是混淆的比较彻底的,基本没有任何保留。那有些朋友可能会有疑问,Fragment怎么说也算是系统组件吧,就算普通方法名被混淆了,至少像onCreateView()这样的生命周期方法不应该被混淆吧?其实生命周期方法会不会被混淆和我们使用Fragment的方式有关,比如在本项目中,我使用的是android.support.v4.app.Fragment,support-v4包下的,就连Fragment的源码都被一起混淆了,因此生命周期方法当然也不例外了。但如果你使用的是android.app.Fragment,这就是调用手机系统中预编译好的代码了,很明显我们的混淆无法影响到系统内置的代码,因此这种情况下onCreateView()方法名就不会被混淆,但其它的方法以及变量仍然会被混淆。
接下来看一下MainActivity,同样也是系统组件之一,但MainActivity的保留程度就比MyFragment好多了,至少像类名、生命周期方法名都没有被混淆,这是为什么呢?根据我亲身测试得出结论,凡是需要在AndroidManifest.xml中去注册的所有类的类名以及从父类重写的方法名都自动不会被混淆。因此,除了Activity之外,这份规则同样也适用于Service、BroadcastReceiver和ContentProvider。
*后看一下NativeUtils类,这个类的类名也没有被混淆,这是由于它有一个声明成native的方法。只要一个类中有存在native方法,它的类名就不会被混淆,native方法的方法名也不会被混淆,因为C++代码要通过包名+类名+方法名来进行交互。 但是类中的别的代码还是会被混淆的。
除此之外,第三方的Jar包都是会被混淆的,LitePal不管是包名还是类名还是方法名都被完完全全混淆掉了。
这些就是Android Studio打正式APK时默认的混淆规则。
那么这些混淆规则是在哪里定义的呢?其实就是刚才在build.gradle的release闭包下配置的proguard-android.txt文件,这个文件存放于<Android SDK>/tools/proguard目录下,我们打开来看一下:

 

  1. # This is a configuration file for ProGuard.
  2. # http://proguard.sourceforge.net/index.html#manual/usage.html
  3. -dontusemixedcaseclassnames
  4. -dontskipnonpubliclibraryclasses
  5. -verbose
  6. # Optimization is turned off by default. Dex does not like code run
  7. # through the ProGuard optimize and preverify steps (and performs some
  8. # of these optimizations on its own).
  9. -dontoptimize
  10. -dontpreverify
  11. # Note that if you want to enable optimization, you cannot just
  12. # include optimization flags in your own project configuration file;
  13. # instead you will need to point to the
  14. # “proguard-android-optimize.txt” file instead of this one from your
  15. # project.properties file.
  16. -keepattributes *Annotation*
  17. -keep public class com.google.vending.licensing.ILicensingService
  18. -keep public class com.android.vending.licensing.ILicensingService
  19. # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
  20. -keepclasseswithmembernames class * {
  21. native <methods>;
  22. }
  23. # keep setters in Views so that animations can still work.
  24. # see http://proguard.sourceforge.net/manual/examples.html#beans
  25. -keepclassmembers public class * extends android.view.View {
  26. void set*(***);
  27. *** get*();
  28. }
  29. # We want to keep methods in Activity that could be used in the XML attribute onClick
  30. -keepclassmembers class * extends android.app.Activity {
  31. public void *(android.view.View);
  32. }
  33. # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
  34. -keepclassmembers enum * {
  35. public static **[] values();
  36. public static ** valueOf(java.lang.String);
  37. }
  38. -keepclassmembers class * implements android.os.Parcelable {
  39. public static final android.os.Parcelable$Creator CREATOR;
  40. }
  41. -keepclassmembers class **.R$* {
  42. public static <fields>;
  43. }
  44. # The support library contains references to newer platform versions.
  45. # Dont warn about those in case this app is linking against an older
  46. # platform version. We know about them, and they are safe.
  47. -dontwarn android.support.**

 

这个就是默认的混淆配置文件了,我们来一起逐行阅读一下。
-dontusemixedcaseclassnames 表示混淆时不使用大小写混合类名。
-dontskipnonpubliclibraryclasses 表示不跳过library中的非public的类。
-verbose 表示打印混淆的详细信息。
-dontoptimize 表示不进行优化,建议使用此选项,因为根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
-dontpreverify 表示不进行预校验。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
-keepattributes *Annotation* 表示对注解中的参数进行保留。

  1. -keep public class com.google.vending.licensing.ILicensingService
  2. -keep public class com.android.vending.licensing.ILicensingService

 

表示不混淆上述声明的两个类,这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。

  1. -keepclasseswithmembernames class * {
  2. native <methods>;
  3. }

 

表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致的。

  1. -keepclassmembers public class * extends android.view.View {
  2. void set*(***);
  3. *** get*();
  4. }

 

表示不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。

  1. -keepclassmembers class * extends android.app.Activity {
  2. public void *(android.view.View);
  3. }

 

表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了。

  1. -keepclassmembers enum * {
  2. public static **[] values();
  3. public static ** valueOf(java.lang.String);
  4. }

 

表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了。

  1. -keepclassmembers class * implements android.os.Parcelable {
  2. public static final android.os.Parcelable$Creator CREATOR;
  3. }

 

表示不混淆Parcelable实现类中的CREATOR字段,毫无疑问,CREATOR字段是*对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。

  1. -keepclassmembers class **.R$* {
  2. public static <fields>;
  3. }

 

表示不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-dontwarn android.support.** 表示对android.support包下的代码不警告,因为support包中有很多代码都是在高版本中使用的,如果我们的项目指定的版本比较低在打包时就会给予警告。不过support包中所有的代码都在版本兼容性上做足了判断,因此不用担心代码会出问题,所以直接忽略警告就可以了。
好了,这就是proguard-android.txt文件中所有默认的配置,而我们混淆代码也是按照这些配置的规则来进行混淆的。经过我上面的讲解之后,相信大家对这些配置的内容基本都能理解了。不过proguard语法中还真有几处非常难理解的地方,我自己也是研究了好久才搞明白,下面和大家分享一下这些难懂的语法部分。
proguard中一共有三组六个keep关键字,很多人搞不清楚它们的区别,这里我们通过一个表格来直观地看下:

关键字 描述
keep 保留类和类中的成员,防止它们被混淆或移除。
keepnames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。
keepclassmembers 只保留类中的成员,防止它们被混淆或移除。
keepclassmembernames 只保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。
keepclasseswithmembers 保留类和类中的成员,防止它们被混淆或移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆。
keepclasseswithmembernames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆。

除此之外,proguard中的通配符也比较让人难懂,proguard-android.txt中就使用到了很多通配符,我们来看一下它们之间的区别:

通配符 描述
<field> 匹配类中的所有字段
<method> 匹配类中的所有方法
<init> 匹配类中的所有构造函数
* 匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.example.test.MyActivity,使用com.*,或者com.exmaple.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.exmaple.*.*,或者com.exmaple.test.*,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西。
** 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包。
*** 匹配任意参数类型。比如void set*(***)就能匹配任意传入的参数类型,*** get*()就能匹配任意返回值的类型。
匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法。

虽说上面表格已经解释的很详细了,但是很多人对于keep和keepclasseswithmembers这两个关键字的区别还是搞不懂。确实,它们之间用法有点太像了,我做了很多次试验它们的结果都是相同的。其实唯一的区别就在于类中声明的成员存不存在,我们还是通过一个例子来直接地看一下,先看keepclasseswithmember关键字:

  1. -keepclasseswithmember class * {
  2. native <methods>;
  3. }

 

这段代码的意思其实很明显,就是保留所有含有native方法的类的类名和native方法名,而如果某个类中没有含有native方法,那就还是会被混淆。
但是如果改成keep关键字,结果会完全不一样:

  1. -keep class * {
  2. native <methods>;
  3. }

 

使用keep关键字后,你会发现代码中所有类的类名都不会被混淆了,因为keep关键字看到class *就认为应该将所有类名进行保留,而不会关心该类中是否含有native方法。当然这样写只会保证类名不会被混淆,类中的成员还是会被混淆的。
比较难懂的用法大概就这些吧,掌握了这些内容之后我们就能继续前进了。
回到Android Studio项目当中,刚才打出的APK虽然已经成功混淆了,但是混淆的规则都是按照proguard-android.txt中默认的规则来的,当然我们也可以修改proguard-android.txt中的规则,但是直接在proguard-android.txt中修改会对我们本机上所有项目的混淆规则都生效,那么有没有什么办法只针对当前项目的混淆规则做修改呢?当然是有办法的了,你会发现任何一个Android Studio项目在app模块目录下都有一个proguard-rules.pro文件,这个文件就是用于让我们编写只适用于当前项目的混淆规则的,那么接下来我们就利用刚才学到的所有知识来对混淆规则做修改吧。
这里我们先列出来要实现的目标:

  • 对MyFragment类进行完全保留,不混淆其类名、方法名、以及变量名。
  • 对Utils类中的未调用方法进行保留,防止其被移除掉。
  • 对第三方库进行保留,不混淆android-support库,以及LitePal库中的代码。

下面我们就来逐一实现这些目标。
首先要对MyFragment类进行完全保留可以使用keep关键字,keep后声明完整的类名,然后保留类中的所有内容可以使用*通配符实现,如下所示:

  1. -keep class com.example.guolin.androidtest.MyFragment {
  2. *;
  3. }

 

然后保留Utils类中的未调用方法可以使用keepclassmembers关键字,后跟Utils完整类名,然后在内部声明未调用的方法,如下所示:

  1. -keepclassmembers class com.example.guolin.androidtest.Utils {
  2. public void methodUnused();
  3. }

 

*后不要混淆第三方库,目前我们使用了两种方式来引入第三方库,一种是通过本地jar包引入的,一种是通过remote引入的,其实这两种方式没什么区别,要保留代码都可以使用**这种通配符来实现,如下所示:

  1. -keep class org.litepal.** {
  2. *;
  3. }
  4. -keep class android.support.** {
  5. *;
  6. }

 

所有内容都在这里了,现在我们重新打一个正式版的APK文件,然后再反编译看看效果:

%title插图%num

可以看到,现在android-support包中所有代码都被保留下来了,不管是包名、类名、还是方法名都没有被混淆。LitePal中的代码也是同样的情况:

%title插图%num

再来看下MyFragment中的代码,如下所示:

%title插图%num

可以看到,MyFragment中的代码也没有被混淆,按照我们的要求被完全保留下来了。
*后再来看一下Utils类中的代码:

%title插图%num

很明显,Utils类并没有被完全保留下来,类名还是被混淆了,methodNormal()方法也被混淆了,但是methodUnused()没有被混淆,当然也没有被移除,因为我们的混淆配置生效了。
经过这些例子的演示,相信大家已经对Proguard的用法有了相当不错的理解了,那么根据自己的业务需求来去编写混淆配置相信也不是什么难事了吧?
Progaurd的使用非常灵活,基本上能够覆盖你所能想到的所有业务逻辑。这里再举个例子,之前一直有人问我使用LitePal时的混淆配置怎么写,其实真的很简单,LitePal作为开源库并不需要混淆,上面的配置已经演示了如何不混淆LitePal代码,然后所有代码中的Model是需要进行反射的,也不能混淆,那么只需要这样写就行了:

 

  1. -keep class * extends org.litepal.crud.DataSupport {
  2. *;
  3. }

 

因为LitePal中所有的Model都是应该继承DataSupport类的,所以这里我们将所有继承自DataSupport的类都进行保留就可以了。
关于混淆APK的用法就讲这么多,如果你还想继续了解关于Proguard的更多用法,可以参考官方文档:http://proguard.sourceforge.net/index.html#manual/usage.html

混淆Jar

在本篇文章的第二部分我想讲一讲混淆Jar包的内容,因为APK不一定是我们交付的唯一产品。就比如说我自己,我在公司是负责写SDK的,对于我来说交付出去的产品就是Jar包,而如果Jar包不混淆的话将会很容易就被别人反编译出来,从而泄漏程序逻辑。
实际上Android对混淆Jar包的支持在很早之前就有了,不管你使用多老版本的SDK,都能在 <Android SDK>/tools目录下找到proguard这个文件夹。然后打开里面的bin目录,你会看到如下文件:

%title插图%num

其中proguardgui.bat文件是允许我们以图形化的方式来对Jar包进行混淆的一个工具,今天我们就来讲解一下这个工具的用法。
在开始讲解这个工具之前,首先我们需要先准备一个Jar包,当然你从哪里搞到一个Jar包都是可以的,不过这里为了和刚才的混淆逻辑统一,我们就把本篇文章中的项目代码打成一个Jar包吧。
Eclipse中导出Jar包的方法非常简单,相信所有人都会,可是Android Studio当中就比较让人头疼了,因为Android Studio并没有提供一个专门用于导出Jar包的工具,因此我们只能自己动手了。
我们需要知道,任何一个Android Studio项目,只要编译成功之后就会在项目模块的build/intermediates/classes/debug目录下生成代码编译过后的class文件,因此只需通过打包命令将这些class文件打包成Jar包就行了,打开cmd,切换到项目的根目录,然后输入如下命令:

 

jar -cvf androidtest.jar -C app/build/intermediates/classes/debug .

 

在项目的根目录下就会生成androidtest.jar这个文件,这样我们就把Jar包准备好了。
现在双击proguardgui.bat打开混淆工具,如果是Mac或Ubuntu系统则使用sh proguardgui.sh命令打开混淆工具,界面如下图所示:

%title插图%num

其实从主界面上我们就能看出,这个Proguard工具支持Shrinking、Optimization、Obfuscation、Preverification四项操作,在左侧的侧边栏上也能看到相应的这些选项。Proguard的工作机制仍然还是要依赖于配置文件,当然我们也可以通过proguardgui工具来生成配置文件,不过由于配置选项太多了,每个都去一一设置太复杂,而且大多数还都是我们用不到的配置。因此*简单的方式就是直接拿现有的配置文件,然后再做些修改就行了。
那么我们从<Android SDK>/tools/proguard目录下将proguard-android.txt文件复制一份出来,然后点击主界面上的Load configuration按钮来加载复制出来的这份proguard-android.txt文件,完成后点击Next将进入Input/Output界面。
Input/Output界面是用于导入要混淆的Jar包、配置混淆后文件的输出路径、以及导入该Jar包所依赖的所有其它Jar包的。我们要混淆的当然就是androidtest.jar这个文件,那么这个Jar包又依赖了哪些Jar包呢?这里就需要整理一下了。

 

  • 首先我们写的都是Java代码,Java代码的运行要基于Jre基础之上,没有Jre计算机将无法识别Java的语法,因此*个要依赖的就是Jre的rt.jar。
  • 然后由于我们导出的Jar包中有Android相关的代码,比如Activity、Fragment等,因此还需要添加Android的编译库,android.jar。
  • 除此之外,我们使用的AppCompatActivity和Fragment分别来自于appcompat-v7包和support-v4包,那么这两个Jar包也是需要引入的。
  • *后就是代码中还引入了litepal-1.3.1.jar。

整理清楚了之后我们就来一个个添加,Input/Output有上下两个操作界面,上面是用于导入要混淆的Jar包和配置混淆后文件的输出路径的,下面则是导入该Jar包所依赖的所有其它Jar包的,全部导入后结果如下图所示:

%title插图%num

这些依赖的Jar包所存在的路径每台电脑都不一样,你所需要做的就是在你自己的电脑上成功找到这些依赖的Jar包并导入即可。
不过细心的朋友可能会发现,我在上面整理出了五个依赖的Jar包,但是在图中却添加了六个。这是我在写这篇文章时碰到的一个新的坑,也是定位了好久才解决的,我觉得有必要重点提一下。由于我平时混淆Jar包时里面很少会有Activity,所以没遇到过这个问题,但是本篇文章中的演示Jar包中不仅包含了Activty,还是继承自AppCompatActivity的。而AppCompatActivity的继承结构并不简单,如下图所示:

%title插图%num

其中AppCompatActivity是在appcompat-v7包中的,它的父类FragmentActivity是在support-v4包中的,这两个包我们都已经添加依赖了。但是FragmentActivity的父类就坑爹了,如果你去看BaseFragmentActivityHoneycomb和BaseFragmentActivityDonut这两个类的源码,你会发现它们都是在support-v4包中的:

%title插图%num

 

%title插图%num

可是如果你去support-v4的Jar包中找一下,你会发现压根就没有这两个类,所以我当时一直混淆报错就是因为这两个类不存在,继承结构在这里断掉了。而这两个类其实被规整到了另外一个internal的Jar包中,所以当你要混淆的Jar包中有Activity,并且还是继承自AppCompatActivity或FragmentActivity的话,那么就一定要记得导入这个internal Jar包的依赖,如下图所示:

%title插图%num

接下来点击Next进入Shrink界面,这个界面没什么需要配置的东西,但记得要将Shrink选项钩掉,因为我们这个Jar包是独立存在的,没有任何项目引用,如果钩中Shrink选项的话就会认为我们所有的代码都是无用的,从而把所有代码全压缩掉,导出一个空的Jar包。
继续点击Next进入Obfuscation界面,在这里可以添加一些混淆的逻辑,和混淆APK时不同的是,这里并不会自动帮我们排除混淆四大组件,因此必须要手动声明一下才行。点击*下方的Add按钮,然后在弹出的界面上编写排除逻辑,如下图所示:

%title插图%num

很简单,就是在继承那一栏写上android.app.Activity就行了,其它的组件原理也相同。
继续点击Next进入Optimiazation界面,不用修改任何东西,因为我们本身就不启用Optimization功能。继续点击Next进入Information界面,也不用修改任何东西,因为我们也不启用Preverification功能。
接着点击Next,进入Process界面,在这里可以通过点击View configuration按钮来预览一下目前我们的混淆配置文件,内容如下所示:

 

  1. -injars /Users/guolin/AndroidStudioProjects/AndroidTest/androidtest.jar
  2. -outjars /Users/guolin/androidtest_obfuscated.jar
  3. -libraryjars /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/rt.jar
  4. -libraryjars /Users/guolin/Library/Android/sdk/platforms/android-23/android.jar
  5. -libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.2.0/jars/classes.jar
  6. -libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.0/jars/classes.jar
  7. -libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.0/jars/libs/internal_impl-23.2.0.jar
  8. -libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/libs/litepal-1.3.1.jar
  9. -dontshrink
  10. -dontoptimize
  11. -dontusemixedcaseclassnames
  12. -keepattributes *Annotation*
  13. -dontpreverify
  14. -verbose
  15. -dontwarn android.support.**
  16. -keep public class com.google.vending.licensing.ILicensingService
  17. -keep public class com.android.vending.licensing.ILicensingService
  18. # keep setters in Views so that animations can still work.
  19. # see http://proguard.sourceforge.net/manual/examples.html#beans
  20. -keepclassmembers public class * extends android.view.View {
  21. void set*(***);
  22. *** get*();
  23. }
  24. # We want to keep methods in Activity that could be used in the XML attribute onClick
  25. -keepclassmembers class * extends android.app.Activity {
  26. public void *(android.view.View);
  27. }
  28. -keepclassmembers class * extends android.os.Parcelable {
  29. public static final android.os.Parcelable$Creator CREATOR;
  30. }
  31. -keepclassmembers class **.R$* {
  32. public static <fields>;
  33. }
  34. -keep class * extends android.app.Activity
  35. -keep class * extends android.app.Service
  36. -keep class * extends android.content.BroadcastReceiver
  37. -keep class * extends android.content.ContentProvider
  38. # Also keep – Enumerations. Keep the special static methods that are required in
  39. # enumeration classes.
  40. -keepclassmembers enum * {
  41. public static **[] values();
  42. public static ** valueOf(java.lang.String);
  43. }
  44. # Keep names – Native method names. Keep all native class/method names.
  45. -keepclasseswithmembers,allowshrinking class * {
  46. native <methods>;
  47. }

 

恩,由此可见其实GUI工具只是给我们提供了一个方便操作的平台,背后工作的原理还是通过这些配置来实现的,相信上面的配置内容大家应该都能看得懂了吧。
接下来我们还可以点击Save configuration按钮来保存一下当前的配置文件,这样下次混淆的时候就可以直接Load进来而不用修改任何东西了。
*后点击Process!按钮来开始混淆处理,中间会提示一大堆的Note信息,我们不用理会,只要看到*终显示Processing completed successfully,就说明混淆Jar包已经成功了,如下图所示:

%title插图%num

混淆后的文件我将它配置在了/Users/guolin/androidtest_obfuscated.jar这里,如果反编译一下这个文件,你会发现和刚才反编译APK得到的结果是差不多的:MainActivity的类名以及从父类继承的方法名不会被混淆,NativeUtils的类名和其中的native方法名不会被混淆,Utils的methodUnsed方法不会被移除,因为我们禁用了Shrink功能,其余的代码都会被混淆。由于结果实在是太相似了,我就不再贴图了,参考本篇文章*部分的截图即可。

 

Android Studio 将 support库 改成 Androidx

方法一:在IDE中Refactor栏选择 Migrate to Androidx

%title插图%num

这个操作会提醒我们备份数据

方法二:修改gradle.properties,加入一下两行

%title插图%num
android.useAndroidX=true
android.enableJetifier=true

如果你有包名命名不规范的现象存在,可能会出现转化错误,需要你手动修改不规范的包名

Android Studio生成.so库与调用

新建androidstudio工程,如图:

%title插图%num
build.gradle :

apply plugin: ‘com.android.application’

android {
compileSdkVersion 26
defaultConfig {
applicationId “com.mediatek.camera.myapplication”
minSdkVersion 23
targetSdkVersion 26
versionCode 1
versionName “1.0”
testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
externalNativeBuild {
cmake {
cppFlags “”
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
}
externalNativeBuild {
cmake {
path “CMakeLists.txt”
}
}
}

dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation ‘com.android.support:appcompat-v7:26.1.0’
implementation ‘com.android.support.constraint:constraint-layout:1.1.0’
testImplementation ‘junit:junit:4.12’
androidTestImplementation ‘com.android.support.test:runner:1.0.2’
androidTestImplementation ‘com.android.support.test.espresso:espresso-core:3.0.2’
}

CMakeLists.txt文件:

 

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
jni_FaceDetect

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/c/facedetect_main.c
src/main/cpp/c/native-lib.cpp
)

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
jni_FaceDetect

# Links the target library to the log library
# included in the NDK.
${log-lib} )

facedetect_main.c:

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the “License”);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#define TAG “FaceDetect_JNI”
#include <com_android_camera_zf_FaceDetect.h>
#include <facedetect.h>
#include <android/log.h>
#include <stddef.h>
#include <stdio.h>

#define LOGI(…) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGW(…) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
#define LOGE(…) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
// Log 开关
#define LOG_NDEBUG 0

/*
* Class: com_android_camera_zf_FaceDetect
* Method: tiredDetFree
* Signature: ()V
*/
void JNICALL Java_com_android_camera_zf_FaceDetect_tiredDetFree(JNIEnv *env, jclass obj){
//LOGI( ” jni tiredDetFree() “);
tiredDetFree();
}

/*
* Class: com_android_camera_zf_FaceDetect
* Method: tiredDetInit
* Signature: ()V
*/
void JNICALL Java_com_android_camera_zf_FaceDetect_tiredDetInit(JNIEnv *env, jclass obj){
LOGI( ” jni tiredDetInit() “);
tiredDetInit();
}

/*
* Class: com_android_camera_zf_FaceDetect
* Method: tiredDetProcess_new
* Signature: ([BII[I[I[I[I[I[I[I)I
*/
jint JNICALL Java_com_android_camera_zf_FaceDetect_tiredDetProcess_1new
(JNIEnv *env, jclass obj, jbyteArray jsrcData, jint jwidth, jint jheight, jintArray jopeneyes_count, jintArray jcloseeyes_count,
jintArray jfaces_count, jintArray jfaceRect, jintArray joutPts, jintArray jfacePosStatus, jintArray jfaceEdgePos){

//LOGI( ” jni tiredDetProcess_1new() “);

//srcData数据为图像yv12格式帧数据,如果只取y分量,请从srcData中截取 0—(jwidth*jheight-1)大小
//其余int*参数, size均为int[1]
jbyte* srcData = (jbyte*) (*env)->GetPrimitiveArrayCritical(env, jsrcData, 0);
jint* openeyes_count = (jint*) (*env)->GetPrimitiveArrayCritical(env, jopeneyes_count, 0);
jint* closeeyes_count = (jint*) (*env)->GetPrimitiveArrayCritical(env, jcloseeyes_count, 0);
jint* faces_count = (jint*) (*env)->GetPrimitiveArrayCritical(env, jfaces_count, 0);
jint* facePosStatus = (jint*) (*env)->GetPrimitiveArrayCritical(env, jfacePosStatus, 0);
jint* faceEdgePos = (jint*) (*env)->GetPrimitiveArrayCritical(env, jfaceEdgePos, 0);

int result = tiredDetProcess_new(srcData,jwidth,jheight,
openeyes_count, closeeyes_count,
faces_count,
NULL,//CvRect* faceRect,
NULL, //FacePts *outPts,
facePosStatus,
faceEdgePos);
(*env)->ReleasePrimitiveArrayCritical(env, jsrcData, srcData, 0);
(*env)->ReleasePrimitiveArrayCritical(env, jopeneyes_count, openeyes_count, 0);
(*env)->ReleasePrimitiveArrayCritical(env, jcloseeyes_count, closeeyes_count, 0);
(*env)->ReleasePrimitiveArrayCritical(env, jfaces_count, faces_count, 0);
(*env)->ReleasePrimitiveArrayCritical(env, jfacePosStatus, facePosStatus, 0);
(*env)->ReleasePrimitiveArrayCritical(env, jfaceEdgePos, faceEdgePos, 0);
return result;

}

//内存释放函数,只调用一次
void tiredDetFree(){
//LOGI( ” tiredDetFree() “);
}

//初始化函数,在程序启动前只调用一次
void tiredDetInit(){
LOGI( ” tiredDetInit() 111″);
}

//主处理函数
int tiredDetProcess_new(unsigned char* srcData,int width,int height,
int *openeyes_count, int *closeeyes_count,
int *faces_count,
int *faceRect,//CvRect* faceRect,
int *outPts, //FacePts *outPts,
int *facePosStatus,
int *faceEdgePos){
LOGI( ” tiredDetProcess_new() 111″);
return 0;
}



其他部分(省略)
将生成一个jni_FaceDetect.so lib文件;并在mainactivity中调用

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速