【Android基础】Android中遇到的坑集合

xiaoxiao2021-02-28  34

前言

遇到了挺多的问题,但是每个问题都写篇文章感觉有点不实在,所以还是选择将这些小知识点都汇集到这一篇文章,方便自己再次查看吧。好多问题解决了之后没有及时记录下来,现在忘得差不多了。

替换Fragment的问题

这个问题遇到过好几次了,但是还是没有很快地解决它.不过最终还是解决了~所以还是记下来踩过的坑吧


现象

代码如下. 在我们看来这可能是再平常不过的代码了,但是它就是报错了,而且就是在replace这个函数这里. 它需要的就是一个Fragment呀,我的fragment也是一个继承了Fragment类, 为什么就不能完成类型匹配呢? 所以很是纠结

getFragmentManager().beginTransaction() .replace(R.id.container, fragment) .commit();

但是,我心里清楚,我的fragment是继承自android.support.v4.app.Fragment, 而且我还记得之前使用过一个叫做getSupportFragmentManager()的方法, 但是为什么在这个Activity里面就是调用不出来! 气愤啊, 但是想到了一个叫做AppCompatActivity的适用性高的类, 因此只能想到是不是只有support类型的Activity才有getSupportFragmentManager(). 让宿主Activity继承AppCompatActivity, 最后调用出了getSupportFragmentManager(), 解决了这个莫名其妙的问题!

总结

FragmentManager也有两种, 一个是android.support.v4.app包下的,一个是android.app包下的.继承自Activity的活动里面,只能获取到android.app.FragmentManager; 继承自AppCompatActivity才可以获得android.support.v4.app.FragmentManager不同包下面的FragmentManager只能替换继承自同一个包下面的Fragment.两个不同包下面的具体类如下所示

Calendar中获取到的月份比实际月份少1

不算是bug吧。就像数组一样,月份也从0开始算。

/** * Field number for <code>get</code> and <code>set</code> indicating the * month. This is a calendar-specific value. The first month of * the year in the Gregorian and Julian calendars is * <code>JANUARY</code> which is 0; the last depends on the number * of months in a year. * * @see #JANUARY * @see #FEBRUARY * @see #MARCH * @see #APRIL * @see #MAY * @see #JUNE * @see #JULY * @see #AUGUST * @see #SEPTEMBER * @see #OCTOBER * @see #NOVEMBER * @see #DECEMBER * @see #UNDECIMBER */

SQLite中有两张表时出现SQLiteLog: (1) no such table

其实这个问题的出现,是对SQLiteOpenHelper没有了解清楚的一种表现。这段回答确实是醍醐灌顶。

SQLiteOpenHelper onCreate() and onUpgrade() callbacks are invoked when the database is actually opened, for example by a call to getWritableDatabase(). The database is not opened when the database helper object itself is created.

SQLiteOpenHelper versions the database files. The version number is the int argument passed to the constructor. In the database file, the version number is stored in PRAGMA user_version.

onCreate() is only run when the database file did not exist and was just created. If onCreate()returns successfully (doesn’t throw an exception), the database is assumed to be created with the requested version number. As an implication, you should not catch SQLExceptions in onCreate()yourself.

onUpgrade() is only called when the database file exists but the stored version number is lower than requested in constructor. The onUpgrade() should update the table schema to the requested version.

When changing the table schema in code (onCreate()), you should make sure the database is updated. Two main approaches:

Delete the old database file so that onCreate() is run again. This is often preferred at development time where you have control over the installed versions and data loss is not an issue. Some ways to to delete the database file: Uninstall the application. Use the application manager or adb uninstall your.package.name from shell.Clear application data. Use the application manager.Increment the database version so that onUpgrade() is invoked. This is slightly more complicated as more code is needed. For development time schema upgrades where data loss is not an issue, you can just use execSQL("DROP TABLE IF EXISTS <tablename>") in to remove your existing tables and call onCreate() to recreate the database.For released versions, you should implement data migration in onUpgrade() so your users don’t lose their data.

出现这样的错误的情景为:

有两张表,开始的时候创建了一个**Helper继承自SQLiteOpenHelper,然后又需要创建一张表的时候,又创建了一个类继承自SQLiteOpenHelper,里面的数据库名相同,版本号相同,只有表名、创建的SQL语句不同。这样做的原因是因为以为每次都会执行onCreate(),然后表就被创建了。这样的想法是错误的,根本就不是这么一回事。没有好好看过数据库相关的啊~

在没看到这个回答之前,有过两次尝试,都解决了问题,但是为什么解决了,我竟然不知道!!

尝试一:把两个数据库名改成不同的。这样就会在/data/data/**/databases/下面存在两个数据库文件。解决了问题。

尝试二:后来感觉可能与数据库的版本有关系,所以这次不改数据库名,但是将后者的版本号提高,并重载onDowngrade()方法,让它不干任何事情。

最终方案:将两个继承自SQLiteOpenHelper的类全部写到一个类里面,将另外一个删除掉。我觉得这是比较完美的解决方案,也大致明白了这背后的原因。好好地又上了一课。如下所示:

WebView显示中文网页乱码

很久之前也遇到过这个问题,但是到现在记得的也就是可以通过设置某些参数,然后就可以正常显示中文了。

这次还是直接把这个它的设置方法贴出来吧,让自己不用再找了。

webView.getSettings().setDefaultTextEncodingName("UTF -8");//设置默认为utf-8 //webView.loadData(data, "text/html", "UTF -8"); //API提供的标准用法,无法解决乱码问题 webView.loadData(data, "text/html; charset=UTF-8", null);//这种写法可以正确解码

String.replace()无法替换成功

其实这个问题挺奇怪的,但是也算不上一个问题吧。

这个方法并不会改变调用这个方法的String,而是返回一个替换了之后的String

写着写着忘记了这个,结果浪费了好久的时间。

RecyclerView如何创建ContextMenu

先上成功创建并获取到了所需信息的链接吧!

网上的说话基本上是ListView的,但是RecyclerView与它又不相同。因此按照网上的说法,基本上通过getMenuInfo()获取到的是空,好伤。如果不是空的话,那么会得到AdapterView.AdapterContextMenuInfo,这个里面包含了一些信息如position,应该是该项在整个RecyclerView中的位置吧。

网上的做法有两种,一种是:

为ViewHolder设置setOnCreateContextMenuListener(),但是这样还是无法直接将所需要的信息传递进来,所以还需要设置setOnMenuItemClickListener(),用来处理点击该项后需要进行的事项,因此,这这里可以直接获取当前RecyclerView中的item并对其进行相关的操作。这个做法来自链接。 itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> { menu.add("稍后阅读").setOnMenuItemClickListener(item -> { Logger.e(easyNews.getNewsId()); return false; }); });

可以参考这段代码,没有尝试过,但是mark一下吧!链接,这种做法挺靠谱的感觉。

关键是下面这段代码,其余的可以按照ListView的那样进行操作。

public class RecyclerViewImplementsContextMenu extends RecyclerView { private AdapterView.AdapterContextMenuInfo contextMenuInfo; public RecyclerViewImplementsContextMenu(Context context) { super(context); } public RecyclerViewImplementsContextMenu(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public RecyclerViewImplementsContextMenu(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public AdapterView.AdapterContextMenuInfo getContextMenuInfo() { return contextMenuInfo; } @Override public boolean showContextMenuForChild(View originalView) { int position = getChildAdapterPosition(originalView); long longId = getChildItemId(originalView); contextMenuInfo = new AdapterView.AdapterContextMenuInfo(originalView, position, longId); return super.showContextMenuForChild(originalView); } }

Intent中如何传递一个普通对象

在做小应用的时候遇到了这种问题,网上的解答也比较完整。 方式一:Serializable 方式 使用Intent 来传递对象通常有两种实现方式,Serializable 和Parcelable,我们先来学习一下第一种的实现方式。 Serializable 是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个类去实现Serializable 这个接口就可以了。 比如说有一个Person 类,其中包含了name 和age 这两个字段,想要将它序列化就可以这样写:

public class Person implements Serializable{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

其中get、set 方法都是用于赋值和读取字段数据的,最重要的部分是在第一行。这里让Person 类去实现了Serializable 接口,这样所有的Person 对象就都是可序列化的了。 接下来在FirstActivity 中的写法非常简单:

Person person = new Person(); person.setName("Tom"); person.setAge(20); Intent intent = new Intent(FirstActivity.this, SecondActivity.class); intent.putExtra("person_data", person); startActivity(intent);

可以看到,这里我们创建了一个Person 的实例,然后就直接将它传入到putExtra()方法中了。由于Person 类实现了Serializable 接口,所以才可以这样写。 接下来在SecondActivity 中获取这个对象也很简单,写法如下:

Person person = (Person) getIntent().getSerializableExtra("person_data");

这里调用了getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成Person 对象,这样我们就成功实现了使用Intent 来传递对象的功能了。

方式二:Parcelable 除了Serializable 之外,使用Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable 方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent 所支持的数据类型,这样也就实现传递对象的功能了。 下面我们来看一下Parcelable 的实现方式,修改Person 中的代码,如下所示:

public class Person implements Parcelable { private String name; private int age; @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // TODO Auto-generated method stub dest.writeString(name); dest.writeInt(age); } public static final Parcelable.Creator<Person> CREATOR=new Parcelable.Creator<Person>() { @Override public Person createFromParcel(Parcel source) { // TODO Auto-generated method stub Person person=new Person(); person.name=source.readString(); person.age=source.readInt(); return person; } @Override public Person[] newArray(int size) { // TODO Auto-generated method stub return new Person[size]; } }; }

Parcelable 的实现方式要稍微复杂一些。可以看到,首先我们让Person 类去实现了Parcelable 接口,这样就必须重写describeContents()和writeToParcel()这两个方法。其中describeContents()方法直接返回0 就可以了,而writeToParcel()方法中我们需要调用Parcel的writeXxx()方法将Person 类中的字段一一写出。注意字符串型数据就调用writeString()方法,整型数据就调用writeInt()方法,以此类推。 除此之外,我们还必须在Person 类中提供一个名为CREATOR 的常量,这里创建了Parcelable.Creator 接口的一个实现,并将泛型指定为Person。接着需要重写createFromParcel()和newArray()这两个方法,在createFromParcel()方法中我们要去读取刚才写出的name 和age字段,并创建一个Person 对象进行返回,其中name 和age 都是调用Parcel 的readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺序完全相同。而newArray()方法中的实现就简单多了,只需要new 出一个Person 数组,并使用方法中传入的size 作为数组大小就可以了。 接下来在FirstActivity 中我们仍然可以使用相同的代码来传递Person 对象,只不过在SecondActivity 中获取对象的时候需要稍加改动,如下所示:

Person person = (Person) getIntent().getParcelableExtra("person_data");

注意这里不再是调用getSerializableExtra()方法,而是调用getParcelableExtra()方法来获取传递过来的对象了,其他的地方都完全相同。这样我们就把使用Intent 来传递对象的两种实现方式都学习完了,对比一下,Serializable的方式较为简单,但由于会把整个对象进行序列化,因此效率方面会比Parcelable 方式低一些,所以在通常情况下还是更加推荐使用Parcelable 的方式来实现Intent 传递对象的功能。

转载请注明原文地址: https://www.6miu.com/read-1600281.html

最新回复(0)